<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>案例研究：設計模式實戰 on Tarragon</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/</link><description>Recent content in 案例研究：設計模式實戰 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 21 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/index.xml" rel="self" type="application/rss+xml"/><item><title>案例：快取生命週期管理</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/config_loader.py&lt;/code> 的實際程式碼，展示如何用 Context Manager 管理快取生命週期。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>config_loader.py&lt;/code> 使用全域變數實現快取：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 全域快取變數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">_quality_rules_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 載入代理人配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用全域快取避免重複讀取檔案。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">clear_config_cache&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除配置快取（用於測試或配置熱更新）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：全域變數是最簡單的快取方式&lt;/li>
&lt;li>&lt;strong>效能好&lt;/strong>：配置只讀取一次&lt;/li>
&lt;li>&lt;strong>API 簡潔&lt;/strong>：呼叫者不需要管理快取&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;h4 id="問題-1測試難以隔離">問題 1：測試難以隔離&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_load_agents_config&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試 A：預設配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_load_agents_config_custom&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試 B：自訂配置檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 問題：測試 A 的快取會影響測試 B！&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 可能拿到測試 A 的快取結果&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2快取生命週期不明確">問題 2：快取生命週期不明確&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">process_hooks&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 處理完成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 問題：什麼時候應該清除快取？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 如果配置檔案改了，這裡會用到舊的快取&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-3清除快取容易忘記">問題 3：清除快取容易忘記&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_something&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 設定測試環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/tmp/test&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 執行測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 問題：忘記清除快取！&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 下一個測試會用到這個測試的快取&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案context-manager-管理快取">進階解決方案：Context Manager 管理快取&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>明確的快取範圍&lt;/strong>：快取的生命週期有明確的開始和結束&lt;/li>
&lt;li>&lt;strong>自動清理&lt;/strong>：離開範圍時自動清除快取&lt;/li>
&lt;li>&lt;strong>測試友好&lt;/strong>：每個測試可以有獨立的快取&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立快取管理類別">步驟 1：建立快取管理類別&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">contextlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 用 Context Manager 控制快取的生命週期，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> 解決全域快取的問題。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> 初始化配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 專案根目錄，預設從環境變數讀取
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">project_root&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">config_dir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置目錄路徑&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 載入配置（使用快取）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="s2"> name: 配置名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;從檔案載入配置（內部方法）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">yaml_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">yaml_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">clear_cache&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除所有快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2加入-context-manager-支援">步驟 2：加入 Context Manager 支援&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 前面的程式碼 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">cached_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 快取範圍 Context Manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 在這個範圍內，配置會被快取。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 離開範圍時自動清除快取。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> Yields:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> ConfigManager: 自己，方便鏈式呼叫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> manager = ConfigManager()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> with manager.cached_scope():
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> config = manager.load_config(&amp;#34;agents&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> # ... 使用配置 ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> # 離開時快取自動清除
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">isolated_scope&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 隔離範圍 Context Manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建立一個完全隔離的配置環境，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="s2"> 適合用於測試。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 臨時的專案根目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> Yields:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> ConfigManager: 新的管理器實例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> with manager.isolated_scope(&amp;#34;/tmp/test&amp;#34;) as isolated:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2"> config = isolated.load_config(&amp;#34;agents&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> # 完全隔離，不影響原本的 manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated_manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">isolated_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-3加入便利方法">步驟 3：加入便利方法&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 前面的程式碼 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入代理人配置（帶預設值）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_get_default_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入品質規則配置（帶預設值）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_get_default_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_get_default_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;預設代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;basil&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;thyme&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;mint&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;agent_dispatch_rules&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_get_default_quality_rules&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;預設品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;trigger_conditions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;allowed_tools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;Write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Edit&amp;#34;&lt;/span>&lt;span class="p">]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;cache&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;ttl_minutes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="完整程式碼">完整程式碼&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="ch">#!/usr/bin/env python3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">快取生命週期管理 - 完整範例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2">展示如何用 Context Manager 管理配置快取的生命週期。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">contextlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl"> &lt;span class="n">HAS_YAML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="n">HAS_YAML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="s2"> 用 Context Manager 控制快取的生命週期。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">project_root&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">config_dir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== 載入方法 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入配置（使用快取）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;從檔案載入配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">yaml_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="n">json_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">yaml_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">HAS_YAML&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">json_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">clear_cache&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除所有快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== Context Manager =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">cached_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;快取範圍：離開時自動清除快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">isolated_scope&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;隔離範圍：建立獨立的配置環境&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">isolated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== 便利方法 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[],&lt;/span> &lt;span class="s2">&amp;#34;agent_dispatch_rules&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;trigger_conditions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{},&lt;/span> &lt;span class="s2">&amp;#34;cache&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;ttl_minutes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 全域便利函式（相容舊 API）=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl">&lt;span class="n">_default_manager&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;獲取預設的配置管理器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_default_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_default_manager&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl"> &lt;span class="n">_default_manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_default_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;相容舊 API：載入代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;相容舊 API：載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl">&lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">config_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置範圍 Context Manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建立一個有明確生命週期的配置環境。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 專案根目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl">&lt;span class="s2"> Yields:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl">&lt;span class="s2"> ConfigManager: 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl">&lt;span class="s2"> with config_scope(&amp;#34;/path/to/project&amp;#34;) as config:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl">&lt;span class="s2"> agents = config.load_agents_config()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl">&lt;span class="s2"> rules = config.load_quality_rules()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl">&lt;span class="s2"> # 離開時快取自動清除
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">140&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">141&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cached_scope&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">143&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">144&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 測試範例 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">146&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">147&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">tempfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">148&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 建立測試配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">150&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TemporaryDirectory&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">tmpdir&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">151&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">152&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">153&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">154&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 寫入測試配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">155&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;agents.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_text&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">156&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;{&amp;#34;known_agents&amp;#34;: [&amp;#34;test-agent-1&amp;#34;, &amp;#34;test-agent-2&amp;#34;]}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">157&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">158&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">159&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用 Context Manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">160&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">config_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">161&lt;/span>&lt;span class="cl"> &lt;span class="n">agents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">162&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;代理人配置: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">agents&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">163&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">164&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第二次呼叫會使用快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">165&lt;/span>&lt;span class="cl"> &lt;span class="n">agents2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">166&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;快取命中: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">agents&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">agents2&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">167&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">168&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 離開範圍後快取已清除&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">169&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;離開範圍，快取已清除&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">170&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">171&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試隔離範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">172&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">=== 測試隔離範圍 ===&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">173&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">174&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">175&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isolated_scope&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">isolated&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">176&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 隔離環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">177&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">178&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">isolated&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;nonexistent&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">179&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">180&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;預期的錯誤: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">181&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">182&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;隔離範圍結束&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用範例">使用範例&lt;/h3>
&lt;h4 id="基本使用">基本使用&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 建立配置管理器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 使用快取範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cached_scope&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">agents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">rules&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第二次呼叫會使用快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">agents2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">agents&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">agents2&lt;/span> &lt;span class="c1"># 同一個物件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 離開範圍後快取已清除&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="測試中使用">測試中使用&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pytest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tempfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@pytest.fixture&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">config_manager&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試用的配置管理器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TemporaryDirectory&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">tmpdir&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 準備測試配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;agents.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_text&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;{&amp;#34;known_agents&amp;#34;: [&amp;#34;test-agent&amp;#34;]}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用隔離範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isolated_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">isolated&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">isolated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_load_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_manager&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試載入代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;test-agent&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_cache_works&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_manager&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試快取功能&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">config1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">config2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">config1&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">config2&lt;/span> &lt;span class="c1"># 同一個物件&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="相容舊-api">相容舊 API&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 如果有既有程式碼使用舊 API&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">config_loader&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_scope&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 舊的呼叫方式仍然可用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 新的呼叫方式有明確的生命週期&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">config_scope&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="設計權衡">設計權衡&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>全域快取&lt;/th>
 &lt;th>Context Manager&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>簡單性&lt;/td>
 &lt;td>最簡單&lt;/td>
 &lt;td>需要理解 Context Manager&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生命週期&lt;/td>
 &lt;td>不明確（程式結束前一直存在）&lt;/td>
 &lt;td>明確（範圍結束時清除）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>測試隔離&lt;/td>
 &lt;td>困難（需要手動清除）&lt;/td>
 &lt;td>容易（自動隔離）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並行安全&lt;/td>
 &lt;td>不安全（共用全域變數）&lt;/td>
 &lt;td>可以安全（每個範圍獨立）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>記憶體管理&lt;/td>
 &lt;td>可能洩漏&lt;/td>
 &lt;td>自動清理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>API 相容性&lt;/td>
 &lt;td>N/A&lt;/td>
 &lt;td>可以相容舊 API&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="什麼時候該用-context-manager-管理快取">什麼時候該用 Context Manager 管理快取？&lt;/h2>
&lt;p>&lt;strong>適合使用&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/config_loader.py</code> 的實際程式碼，展示如何用 Context Manager 管理快取生命週期。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>config_loader.py</code> 使用全域變數實現快取：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 全域快取變數</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">_agents_config_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">_quality_rules_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    載入代理人配置
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    使用全域快取避免重複讀取檔案。
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">_agents_config_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">global</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">if</span> <span class="n">_quality_rules_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">_get_default_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">return</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">def</span> <span class="nf">clear_config_cache</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;清除配置快取（用於測試或配置熱更新）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span><span class="p">,</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="kc">None</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：全域變數是最簡單的快取方式</li>
<li><strong>效能好</strong>：配置只讀取一次</li>
<li><strong>API 簡潔</strong>：呼叫者不需要管理快取</li>
</ol>
<h3 id="這個設計的限制">這個設計的限制</h3>
<h4 id="問題-1測試難以隔離">問題 1：測試難以隔離</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">test_load_agents_config</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># 測試 A：預設配置</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">assert</span> <span class="s2">&#34;known_agents&#34;</span> <span class="ow">in</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">test_load_agents_config_custom</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 測試 B：自訂配置檔案</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 問題：測試 A 的快取會影響測試 B！</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 可能拿到測試 A 的快取結果</span></span></span></code></pre></div><h4 id="問題-2快取生命週期不明確">問題 2：快取生命週期不明確</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">process_hooks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># ... 處理完成</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="c1"># 問題：什麼時候應該清除快取？</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="c1"># 如果配置檔案改了，這裡會用到舊的快取</span></span></span></code></pre></div><h4 id="問題-3清除快取容易忘記">問題 3：清除快取容易忘記</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">test_something</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># 設定測試環境</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;/tmp/test&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="c1"># 執行測試</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="c1"># 問題：忘記清除快取！</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="c1"># 下一個測試會用到這個測試的快取</span></span></span></code></pre></div><h2 id="進階解決方案context-manager-管理快取">進階解決方案：Context Manager 管理快取</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>明確的快取範圍</strong>：快取的生命週期有明確的開始和結束</li>
<li><strong>自動清理</strong>：離開範圍時自動清除快取</li>
<li><strong>測試友好</strong>：每個測試可以有獨立的快取</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立快取管理類別">步驟 1：建立快取管理類別</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Iterator</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    配置管理器
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    用 Context Manager 控制快取的生命週期，
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    解決全域快取的問題。
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        初始化配置管理器
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            project_root: 專案根目錄，預設從環境變數讀取
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">config_dir</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;配置目錄路徑&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        載入配置（使用快取）
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">            name: 配置名稱
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            配置內容
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_from_file</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">def</span> <span class="nf">_load_from_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;從檔案載入配置（內部方法）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">yaml_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">if</span> <span class="n">yaml_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">                <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Config not found: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">def</span> <span class="nf">clear_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="s2">&#34;&#34;&#34;清除所有快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span></span></span></code></pre></div><h4 id="步驟-2加入-context-manager-支援">步驟 2：加入 Context Manager 支援</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># ... 前面的程式碼 ...</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">cached_scope</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        快取範圍 Context Manager
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        在這個範圍內，配置會被快取。
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        離開範圍時自動清除快取。
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        Yields:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">            ConfigManager: 自己，方便鏈式呼叫
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">            manager = ConfigManager()
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">            with manager.cached_scope():
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">                config = manager.load_config(&#34;agents&#34;)
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">                # ... 使用配置 ...
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            # 離開時快取自動清除
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="k">yield</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">isolated_scope</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        隔離範圍 Context Manager
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        建立一個完全隔離的配置環境，
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">        適合用於測試。
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            project_root: 臨時的專案根目錄
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">        Yields:
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">            ConfigManager: 新的管理器實例
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">            with manager.isolated_scope(&#34;/tmp/test&#34;) as isolated:
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">                config = isolated.load_config(&#34;agents&#34;)
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">                # 完全隔離，不影響原本的 manager
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="n">isolated_manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="n">project_root</span> <span class="ow">or</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated_manager</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="n">isolated_manager</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span></span></span></code></pre></div><h4 id="步驟-3加入便利方法">步驟 3：加入便利方法</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># ... 前面的程式碼 ...</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入代理人配置（帶預設值）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入品質規則配置（帶預設值）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_default_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="nf">_get_default_agents_config</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="s2">&#34;&#34;&#34;預設代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="s2">&#34;known_agents&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;basil&#34;</span><span class="p">,</span> <span class="s2">&#34;thyme&#34;</span><span class="p">,</span> <span class="s2">&#34;mint&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="s2">&#34;agent_dispatch_rules&#34;</span><span class="p">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">def</span> <span class="nf">_get_default_quality_rules</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;&#34;&#34;預設品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="s2">&#34;trigger_conditions&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;allowed_tools&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Write&#34;</span><span class="p">,</span> <span class="s2">&#34;Edit&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="s2">&#34;cache&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;ttl_minutes&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="p">}</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">快取生命週期管理 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 Context Manager 管理配置快取的生命週期。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Iterator</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    配置管理器
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    用 Context Manager 控制快取的生命週期。
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">config_dir</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="c1"># ===== 載入方法 =====</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入配置（使用快取）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_from_file</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="k">def</span> <span class="nf">_load_from_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;從檔案載入配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">yaml_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">json_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.json&#34;</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">if</span> <span class="n">yaml_path</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="ow">and</span> <span class="n">HAS_YAML</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">                <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">if</span> <span class="n">json_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">json_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">                <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Config not found: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">def</span> <span class="nf">clear_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="s2">&#34;&#34;&#34;清除所有快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="c1"># ===== Context Manager =====</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="k">def</span> <span class="nf">cached_scope</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="s2">&#34;&#34;&#34;快取範圍：離開時自動清除快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="k">yield</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="nf">isolated_scope</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;隔離範圍：建立獨立的配置環境&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="n">isolated</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span><span class="n">project_root</span> <span class="ow">or</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="n">isolated</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="c1"># ===== 便利方法 =====</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;known_agents&#34;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&#34;agent_dispatch_rules&#34;</span><span class="p">:</span> <span class="p">{}}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;trigger_conditions&#34;</span><span class="p">:</span> <span class="p">{},</span> <span class="s2">&#34;cache&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;ttl_minutes&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">}}</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="c1"># ===== 全域便利函式（相容舊 API）=====</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="n">_default_manager</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ConfigManager</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="k">def</span> <span class="nf">get_config_manager</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取預設的配置管理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">global</span> <span class="n">_default_manager</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="k">if</span> <span class="n">_default_manager</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">_default_manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="k">return</span> <span class="n">_default_manager</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="s2">&#34;&#34;&#34;相容舊 API：載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">return</span> <span class="n">get_config_manager</span><span class="p">()</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="s2">&#34;&#34;&#34;相容舊 API：載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">return</span> <span class="n">get_config_manager</span><span class="p">()</span><span class="o">.</span><span class="n">load_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">
</span></span><span class="line"><span class="ln">121</span><span class="cl"><span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="k">def</span> <span class="nf">config_scope</span><span class="p">(</span><span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ConfigManager</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="s2">    配置範圍 Context Manager
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">    建立一個有明確生命週期的配置環境。
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">        project_root: 專案根目錄
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">    Yields:
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        ConfigManager: 配置管理器
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="s2">        with config_scope(&#34;/path/to/project&#34;) as config:
</span></span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="s2">            agents = config.load_agents_config()
</span></span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="s2">            rules = config.load_quality_rules()
</span></span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="s2">        # 離開時快取自動清除
</span></span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">cached_scope</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="k">yield</span> <span class="n">manager</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="c1"># ===== 測試範例 =====</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">
</span></span><span class="line"><span class="ln">146</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="c1"># 建立測試配置</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">config_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">config_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="c1"># 寫入測試配置</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="p">(</span><span class="n">config_dir</span> <span class="o">/</span> <span class="s2">&#34;agents.json&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">            <span class="s1">&#39;{&#34;known_agents&#34;: [&#34;test-agent-1&#34;, &#34;test-agent-2&#34;]}&#39;</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="c1"># 使用 Context Manager</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="k">with</span> <span class="n">config_scope</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="k">as</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="n">agents</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;代理人配置: </span><span class="si">{</span><span class="n">agents</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">            <span class="c1"># 第二次呼叫會使用快取</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">            <span class="n">agents2</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取命中: </span><span class="si">{</span><span class="n">agents</span> <span class="ow">is</span> <span class="n">agents2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl">        <span class="c1"># 離開範圍後快取已清除</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;離開範圍，快取已清除&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="c1"># 測試隔離範圍</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== 測試隔離範圍 ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">isolated_scope</span><span class="p">()</span> <span class="k">as</span> <span class="n">isolated</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">        <span class="c1"># 隔離環境</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">            <span class="n">config</span> <span class="o">=</span> <span class="n">isolated</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;nonexistent&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預期的錯誤: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;隔離範圍結束&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 建立配置管理器</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 使用快取範圍</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">cached_scope</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">agents</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">rules</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 第二次呼叫會使用快取</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">agents2</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">assert</span> <span class="n">agents</span> <span class="ow">is</span> <span class="n">agents2</span>  <span class="c1"># 同一個物件</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 離開範圍後快取已清除</span></span></span></code></pre></div><h4 id="測試中使用">測試中使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@pytest.fixture</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">config_manager</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試用的配置管理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="c1"># 準備測試配置</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">config_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">config_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="p">(</span><span class="n">config_dir</span> <span class="o">/</span> <span class="s2">&#34;agents.json&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="s1">&#39;{&#34;known_agents&#34;: [&#34;test-agent&#34;]}&#39;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># 使用隔離範圍</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">isolated_scope</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="k">as</span> <span class="n">isolated</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">def</span> <span class="nf">test_load_agents_config</span><span class="p">(</span><span class="n">config_manager</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">assert</span> <span class="n">config</span><span class="p">[</span><span class="s2">&#34;known_agents&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="p">[</span><span class="s2">&#34;test-agent&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">def</span> <span class="nf">test_cache_works</span><span class="p">(</span><span class="n">config_manager</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試快取功能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">config1</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">config2</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">assert</span> <span class="n">config1</span> <span class="ow">is</span> <span class="n">config2</span>  <span class="c1"># 同一個物件</span></span></span></code></pre></div><h4 id="相容舊-api">相容舊 API</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 如果有既有程式碼使用舊 API</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">config_loader</span> <span class="kn">import</span> <span class="n">load_agents_config</span><span class="p">,</span> <span class="n">config_scope</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 舊的呼叫方式仍然可用</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 新的呼叫方式有明確的生命週期</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">with</span> <span class="n">config_scope</span><span class="p">()</span> <span class="k">as</span> <span class="n">manager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>全域快取</th>
          <th>Context Manager</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>簡單性</td>
          <td>最簡單</td>
          <td>需要理解 Context Manager</td>
      </tr>
      <tr>
          <td>生命週期</td>
          <td>不明確（程式結束前一直存在）</td>
          <td>明確（範圍結束時清除）</td>
      </tr>
      <tr>
          <td>測試隔離</td>
          <td>困難（需要手動清除）</td>
          <td>容易（自動隔離）</td>
      </tr>
      <tr>
          <td>並行安全</td>
          <td>不安全（共用全域變數）</td>
          <td>可以安全（每個範圍獨立）</td>
      </tr>
      <tr>
          <td>記憶體管理</td>
          <td>可能洩漏</td>
          <td>自動清理</td>
      </tr>
      <tr>
          <td>API 相容性</td>
          <td>N/A</td>
          <td>可以相容舊 API</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用-context-manager-管理快取">什麼時候該用 Context Manager 管理快取？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要明確的快取生命週期</li>
<li>測試需要隔離的快取</li>
<li>可能並行存取快取</li>
<li>快取資料量大，需要及時釋放</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>快取需要跨多個函式呼叫共享</li>
<li>快取很小，不需要特別管理</li>
<li>程式很簡單，不需要測試隔離</li>
</ul>
<h2 id="進階與-exitstack-結合">進階：與 ExitStack 結合</h2>
<p>當需要管理多個快取時：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">ExitStack</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">process_with_multiple_caches</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 ExitStack 管理多個快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># 進入多個快取範圍</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">config</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">config_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">db_cache</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">database_cache_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">api_cache</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">api_cache_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 使用各種快取</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">agents</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">users</span> <span class="o">=</span> <span class="n">db_cache</span><span class="o">.</span><span class="n">get_users</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">api_cache</span><span class="o">.</span><span class="n">fetch_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="n">process</span><span class="p">(</span><span class="n">agents</span><span class="p">,</span> <span class="n">users</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># ExitStack 會自動清理所有快取</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>為 <code>ConfigManager</code> 加入 <code>reload_config</code> 方法，強制重新載入配置</li>
<li>實作一個 <code>@cached_config</code> 裝飾器，讓函式自動使用快取範圍</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li>加入 TTL（Time To Live）支援，讓快取自動過期</li>
<li>實作一個執行緒安全的 <code>ConfigManager</code>，支援多執行緒存取</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li>參考 <code>config_loader.py</code> 的 Fallback Pattern（YAML → JSON → 預設值），用 Context Manager 實現「配置來源優先級」的管理</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/contextlib.html">contextlib 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/functools.html#functools.lru_cache">functools.lru_cache</a></li>
<li><a href="https://cachetools.readthedocs.io/">cachetools</a> - 更多快取策略</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/" data-link-title="案例研究：設計模式實戰" data-link-desc="基於 Hook 系統的進階設計模式實戰案例">案例研究索引</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">案例：插件架構設計</a></em></p>
]]></content:encoded></item><item><title>案例：插件架構設計</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Protocol 和註冊機制實現可擴展的插件系統。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">2.2 自動註冊機制&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的驗證邏輯直接寫在類別中：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證單個 Hook 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 讀取檔案 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 執行各項檢查 - 硬編碼的驗證規則&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">issues&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查命名規範&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查共用模組導入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查輸出格式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查測試檔案是否存在&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>所有邏輯集中管理，容易找到相關程式碼&lt;/li>
&lt;li>沒有額外的抽象層，直接明瞭&lt;/li>
&lt;li>對小型專案來說足夠使用&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要擴展時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>第三方無法新增自己的檢查項&lt;/strong>：想加入「Docstring 檢查」必須修改 &lt;code>HookValidator&lt;/code>&lt;/li>
&lt;li>&lt;strong>修改需要改動核心程式碼&lt;/strong>：每新增一個檢查都要改 &lt;code>validate_hook&lt;/code> 方法&lt;/li>
&lt;li>&lt;strong>違反開放封閉原則&lt;/strong>：對擴展不開放，對修改不封閉&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>定義清晰的插件介面&lt;/strong>：用 Protocol 描述檢查項該有的行為&lt;/li>
&lt;li>&lt;strong>支援第三方擴展&lt;/strong>：外部套件可以新增檢查項&lt;/li>
&lt;li>&lt;strong>插件可以獨立測試&lt;/strong>：每個檢查項是獨立的單元&lt;/li>
&lt;li>&lt;strong>支援插件的啟用/停用&lt;/strong>：可以動態控制要執行哪些檢查&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1用-protocol-定義插件介面">步驟 1：用 Protocol 定義插件介面&lt;/h4>
&lt;p>Protocol 讓我們定義「檢查項該有的行為」，而不強制繼承關係：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Protocol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">runtime_checkable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationIssue&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validation issue description&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># &amp;#34;error&amp;#34; | &amp;#34;warning&amp;#34; | &amp;#34;info&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CheckContext&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Context passed to each check&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="nd">@runtime_checkable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationCheck&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> Protocol for validation checks
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> Any class implementing this protocol can be used as a validation check.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> No inheritance required - just implement the methods.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Check name for identification&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Human-readable description&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">CheckContext&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> Execute the validation check
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> context: Check context with file info
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> List of validation issues found
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="為什麼用-protocol-而不是-abc">為什麼用 Protocol 而不是 ABC？&lt;/h5>
&lt;ul>
&lt;li>Protocol 是「鴨子型別」的靜態版本&lt;/li>
&lt;li>不強制繼承，現有類別只要有對應方法就能用&lt;/li>
&lt;li>&lt;code>@runtime_checkable&lt;/code> 讓我們可以用 &lt;code>isinstance()&lt;/code> 檢查&lt;/li>
&lt;/ul>
&lt;h4 id="步驟-2實作插件註冊機制">步驟 2：實作插件註冊機制&lt;/h4>
&lt;p>註冊機制提供兩種方式：裝飾器和明確註冊：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Protocol 和註冊機制實現可擴展的插件系統。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">2.2 自動註冊機制</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 的驗證邏輯直接寫在類別中：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># ... 讀取檔案 ...</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># 執行各項檢查 - 硬編碼的驗證規則</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_output_format</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_test_exists</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">check_lib_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查共用模組導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">def</span> <span class="nf">check_output_format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查輸出格式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查測試檔案是否存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li>所有邏輯集中管理，容易找到相關程式碼</li>
<li>沒有額外的抽象層，直接明瞭</li>
<li>對小型專案來說足夠使用</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要擴展時：</p>
<ul>
<li><strong>第三方無法新增自己的檢查項</strong>：想加入「Docstring 檢查」必須修改 <code>HookValidator</code></li>
<li><strong>修改需要改動核心程式碼</strong>：每新增一個檢查都要改 <code>validate_hook</code> 方法</li>
<li><strong>違反開放封閉原則</strong>：對擴展不開放，對修改不封閉</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>定義清晰的插件介面</strong>：用 Protocol 描述檢查項該有的行為</li>
<li><strong>支援第三方擴展</strong>：外部套件可以新增檢查項</li>
<li><strong>插件可以獨立測試</strong>：每個檢查項是獨立的單元</li>
<li><strong>支援插件的啟用/停用</strong>：可以動態控制要執行哪些檢查</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1用-protocol-定義插件介面">步驟 1：用 Protocol 定義插件介面</h4>
<p>Protocol 讓我們定義「檢查項該有的行為」，而不強制繼承關係：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation issue description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">CheckContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Context passed to each check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">content</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">project_root</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    Protocol for validation checks
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    Any class implementing this protocol can be used as a validation check.
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    No inheritance required - just implement the methods.
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check name for identification&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Human-readable description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">        Execute the validation check
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">            context: Check context with file info
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">            List of validation issues found
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><h5 id="為什麼用-protocol-而不是-abc">為什麼用 Protocol 而不是 ABC？</h5>
<ul>
<li>Protocol 是「鴨子型別」的靜態版本</li>
<li>不強制繼承，現有類別只要有對應方法就能用</li>
<li><code>@runtime_checkable</code> 讓我們可以用 <code>isinstance()</code> 檢查</li>
</ul>
<h4 id="步驟-2實作插件註冊機制">步驟 2：實作插件註冊機制</h4>
<p>註冊機制提供兩種方式：裝飾器和明確註冊：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Type</span><span class="p">,</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Type variable for check classes</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">C</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">ValidationCheck</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">CheckRegistry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Registry for validation checks
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Supports both decorator registration and explicit registration.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">check</span><span class="p">:</span> <span class="n">ValidationCheck</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        Register a check instance
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">            check: Check instance to register
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">            The same check instance (for chaining)
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            registry.register(NamingCheck())
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Check must implement ValidationCheck protocol, &#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">check</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]],</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">        Decorator for registering check classes
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">            name: Optional custom name (default: class name)
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">            Class decorator
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">            @registry.check()
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">            class MyCheck:
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">                ...
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="bp">cls</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">instance</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">name</span> <span class="ow">or</span> <span class="n">instance</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">instance</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">return</span> <span class="n">decorator</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="nf">get_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get a check by name&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">def</span> <span class="nf">get_enabled_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="n">check</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">            <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="k">def</span> <span class="nf">enable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">discard</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="k">def</span> <span class="nf">disable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all registered check names&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">
</span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="c1"># Global registry instance</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="n">default_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</span></span></span></code></pre></div><p><strong>兩種註冊方式的比較</strong>：</p>
<table>
  <thead>
      <tr>
          <th>方式</th>
          <th>使用時機</th>
          <th>優點</th>
          <th>缺點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>裝飾器 <code>@registry.check()</code></td>
          <td>類別定義時</td>
          <td>簡潔、宣告式</td>
          <td>模組載入時就註冊</td>
      </tr>
      <tr>
          <td>明確註冊 <code>registry.register()</code></td>
          <td>執行時</td>
          <td>更靈活、可延遲</td>
          <td>較囉嗦</td>
      </tr>
  </tbody>
</table>
<h4 id="步驟-3支援-entry_points-自動發現">步驟 3：支援 entry_points 自動發現</h4>
<p><code>entry_points</code> 是 Python 打包系統的標準機制，讓外部套件可以註冊插件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">importlib.metadata</span> <span class="kn">import</span> <span class="n">entry_points</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Iterator</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">discover_checks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">group</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;hook_validator.checks&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Discover checks from installed packages via entry_points
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        group: Entry point group name
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Yields:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        Discovered check instances
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        for check in discover_checks():
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            registry.register(check)
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># Python 3.10+ API</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="n">group</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># Python 3.9 compatibility</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">for</span> <span class="n">ep</span> <span class="ow">in</span> <span class="n">eps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="c1"># Load the check class/factory</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="n">check_factory</span> <span class="o">=</span> <span class="n">ep</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="c1"># Create instance</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">check_factory</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="c1"># Validate it implements the protocol</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="k">yield</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                <span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;Entry point </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> does not implement &#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;ValidationCheck protocol&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load check </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="k">def</span> <span class="nf">load_external_checks</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="n">CheckRegistry</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">    Load all external checks into a registry
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="s2">        registry: Target registry
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="s2">        Number of checks loaded
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="n">discover_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="n">registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="k">return</span> <span class="n">count</span></span></span></code></pre></div><h4 id="步驟-4實作插件載入與管理">步驟 4：實作插件載入與管理</h4>
<p>插件管理器整合了註冊、發現和執行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Calculate compliance status&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="k">class</span> <span class="nc">PluginValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">    Plugin-based hook validator
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">    Uses registered checks to validate hook files.
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="n">registry</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">CheckRegistry</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="n">auto_discover</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">        Initialize validator
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">            registry: Check registry (default: global registry)
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">            project_root: Project root directory
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">            auto_discover: Whether to auto-discover external checks
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">registry</span> <span class="o">=</span> <span class="n">registry</span> <span class="ow">or</span> <span class="n">default_registry</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="n">project_root</span> <span class="ow">or</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="k">if</span> <span class="n">auto_discover</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="n">load_external_checks</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">        Validate a single hook file
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">            hook_path: Path to hook file
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">            Validation result
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="c1"># Check file exists</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="c1"># Read content</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read hook file: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="c1"># Build context</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="n">context</span> <span class="o">=</span> <span class="n">CheckContext</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="c1"># Run all enabled checks</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">all_issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">get_enabled_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">check</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="n">checks_run</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Check </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="n">checks_run</span><span class="o">=</span><span class="n">checks_run</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="k">return</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Plugin-based Hook Validator
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">A validation system using Protocol and registry pattern,
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">allowing third-party extensions via entry_points.
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kn">from</span> <span class="nn">importlib.metadata</span> <span class="kn">import</span> <span class="n">entry_points</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">Callable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">Dict</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">Iterator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">List</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="n">Optional</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">Protocol</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">Type</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">TypeVar</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">runtime_checkable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1"># Data Classes</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation issue description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="k">class</span> <span class="nc">CheckContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Context passed to each check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">content</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="n">project_root</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="c1"># Protocol Definition</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Protocol for validation checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check name for identification&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Human-readable description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Execute the validation check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="c1"># Registry</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="n">C</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">ValidationCheck</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="k">class</span> <span class="nc">CheckRegistry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Registry for validation checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">check</span><span class="p">:</span> <span class="n">ValidationCheck</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Register a check instance&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Check must implement ValidationCheck protocol, &#34;</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">check</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]],</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Decorator for registering check classes&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="bp">cls</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">instance</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">name</span> <span class="ow">or</span> <span class="n">instance</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">instance</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">return</span> <span class="n">decorator</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">def</span> <span class="nf">get_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get a check by name&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">def</span> <span class="nf">get_enabled_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="n">check</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="k">def</span> <span class="nf">enable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">discard</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="k">def</span> <span class="nf">disable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all registered check names&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="c1"># Global registry</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="n">default_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="c1"># Entry Points Discovery</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="k">def</span> <span class="nf">discover_checks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">group</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;hook_validator.checks&#34;</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Discover checks from installed packages via entry_points&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="n">group</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">for</span> <span class="n">ep</span> <span class="ow">in</span> <span class="n">eps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">            <span class="n">check_factory</span> <span class="o">=</span> <span class="n">ep</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span><span class="p">()</span> <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">check_factory</span><span class="p">)</span> <span class="k">else</span> <span class="n">check_factory</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="k">yield</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">                <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;Entry point </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> does not implement &#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;ValidationCheck protocol&#34;</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load check </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl"><span class="k">def</span> <span class="nf">load_external_checks</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="n">CheckRegistry</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Load all external checks into a registry&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="n">discover_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">        <span class="n">registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="k">return</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">
</span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="c1"># Built-in Checks</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">
</span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="k">class</span> <span class="nc">NamingConventionCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check hook file naming convention&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">
</span></span><span class="line"><span class="ln">185</span><span class="cl">    <span class="n">VALID_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/03-design-patterns/case-studies/plugin-architecture/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;naming_convention&#34;</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">
</span></span><span class="line"><span class="ln">193</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook files follow naming conventions&#34;</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="n">valid</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_PATTERNS</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">
</span></span><span class="line"><span class="ln">206</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Invalid file name: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use snake_case or kebab-case naming&#34;</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl"><span class="k">class</span> <span class="nc">LibImportCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check that hooks import required libraries&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;lib_import&#34;</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hooks import hook_io module&#34;</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">
</span></span><span class="line"><span class="ln">234</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">
</span></span><span class="line"><span class="ln">237</span><span class="cl">        <span class="n">has_import</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">has_import</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing hook_io import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: from hook_io import read_hook_input, write_hook_output&#34;</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">
</span></span><span class="line"><span class="ln">251</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">
</span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl"><span class="k">class</span> <span class="nc">OutputFormatCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check hook output format&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">
</span></span><span class="line"><span class="ln">257</span><span class="cl">    <span class="n">GOOD_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="n">BAD_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">
</span></span><span class="line"><span class="ln">268</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;output_format&#34;</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hooks use proper output functions&#34;</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">
</span></span><span class="line"><span class="ln">276</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">has_bad</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">BAD_PATTERNS</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="k">if</span> <span class="n">has_bad</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Using print(json.dumps(...)) instead of write_hook_output()&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use write_hook_output() for proper output formatting&#34;</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">
</span></span><span class="line"><span class="ln">293</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">
</span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl"><span class="k">class</span> <span class="nc">TestExistsCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check that corresponding test file exists&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;test_exists&#34;</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">
</span></span><span class="line"><span class="ln">303</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a corresponding test file&#34;</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">
</span></span><span class="line"><span class="ln">307</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">308</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl">        <span class="n">hook_name</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">        <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="n">possible_paths</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">            <span class="n">context</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">            <span class="n">context</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">
</span></span><span class="line"><span class="ln">318</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">possible_paths</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;No test file found: </span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Create test at .claude/lib/tests/</span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">
</span></span><span class="line"><span class="ln">327</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">
</span></span><span class="line"><span class="ln">329</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl"><span class="c1"># Validator</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">
</span></span><span class="line"><span class="ln">333</span><span class="cl"><span class="k">class</span> <span class="nc">PluginValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Plugin-based hook validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">        <span class="n">registry</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">CheckRegistry</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">        <span class="n">auto_discover</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">registry</span> <span class="o">=</span> <span class="n">registry</span> <span class="ow">or</span> <span class="n">default_registry</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">344</span><span class="cl">            <span class="n">project_root</span> <span class="ow">or</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">
</span></span><span class="line"><span class="ln">347</span><span class="cl">        <span class="k">if</span> <span class="n">auto_discover</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">            <span class="n">load_external_checks</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">353</span><span class="cl">
</span></span><span class="line"><span class="ln">354</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">355</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">
</span></span><span class="line"><span class="ln">365</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">373</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read hook file: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">374</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">377</span><span class="cl">
</span></span><span class="line"><span class="ln">378</span><span class="cl">        <span class="n">context</span> <span class="o">=</span> <span class="n">CheckContext</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">
</span></span><span class="line"><span class="ln">384</span><span class="cl">        <span class="n">all_issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">385</span><span class="cl">        <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">386</span><span class="cl">
</span></span><span class="line"><span class="ln">387</span><span class="cl">        <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">get_enabled_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">388</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">389</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">check</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">390</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">391</span><span class="cl">                <span class="n">checks_run</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">392</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">393</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">394</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">395</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">396</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Check </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">397</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">398</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">399</span><span class="cl">
</span></span><span class="line"><span class="ln">400</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">401</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">402</span><span class="cl">            <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">403</span><span class="cl">            <span class="n">checks_run</span><span class="o">=</span><span class="n">checks_run</span>
</span></span><span class="line"><span class="ln">404</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">405</span><span class="cl">
</span></span><span class="line"><span class="ln">406</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">407</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate all hook files in a directory&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">408</span><span class="cl">        <span class="k">if</span> <span class="n">hooks_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">409</span><span class="cl">            <span class="n">hooks_dir</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">410</span><span class="cl">
</span></span><span class="line"><span class="ln">411</span><span class="cl">        <span class="n">hooks_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">412</span><span class="cl">
</span></span><span class="line"><span class="ln">413</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">414</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">415</span><span class="cl">                <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">416</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hooks_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">417</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">418</span><span class="cl">                        <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">419</span><span class="cl">                            <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">420</span><span class="cl">                            <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hooks directory not found: </span><span class="si">{</span><span class="n">hooks_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">421</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">422</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">423</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">424</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">425</span><span class="cl">
</span></span><span class="line"><span class="ln">426</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">427</span><span class="cl">        <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">428</span><span class="cl">            <span class="k">if</span> <span class="n">hook_file</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">429</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">430</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">431</span><span class="cl">
</span></span><span class="line"><span class="ln">432</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">433</span><span class="cl">
</span></span><span class="line"><span class="ln">434</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">435</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">436</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">437</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">438</span><span class="cl">
</span></span><span class="line"><span class="ln">439</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">440</span><span class="cl"><span class="c1"># Demo</span>
</span></span><span class="line"><span class="ln">441</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">442</span><span class="cl">
</span></span><span class="line"><span class="ln">443</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">444</span><span class="cl">    <span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln">445</span><span class="cl">
</span></span><span class="line"><span class="ln">446</span><span class="cl">    <span class="c1"># Show registered checks</span>
</span></span><span class="line"><span class="ln">447</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Registered checks:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">448</span><span class="cl">    <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">default_registry</span><span class="o">.</span><span class="n">list_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">449</span><span class="cl">        <span class="n">check</span> <span class="o">=</span> <span class="n">default_registry</span><span class="o">.</span><span class="n">get_check</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">450</span><span class="cl">        <span class="k">if</span> <span class="n">check</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">451</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  - </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">description</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">452</span><span class="cl">
</span></span><span class="line"><span class="ln">453</span><span class="cl">    <span class="c1"># Create test hook</span>
</span></span><span class="line"><span class="ln">454</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">455</span><span class="cl">        <span class="n">hooks_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span>
</span></span><span class="line"><span class="ln">456</span><span class="cl">        <span class="n">hooks_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">457</span><span class="cl">
</span></span><span class="line"><span class="ln">458</span><span class="cl">        <span class="c1"># Create a hook file</span>
</span></span><span class="line"><span class="ln">459</span><span class="cl">        <span class="n">hook_file</span> <span class="o">=</span> <span class="n">hooks_dir</span> <span class="o">/</span> <span class="s2">&#34;check-permissions.py&#34;</span>
</span></span><span class="line"><span class="ln">460</span><span class="cl">        <span class="n">hook_file</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">461</span><span class="cl"><span class="s2">#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln">462</span><span class="cl"><span class="s2">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln">463</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">464</span><span class="cl"><span class="s2">def main():
</span></span></span><span class="line"><span class="ln">465</span><span class="cl"><span class="s2">    data = read_hook_input()
</span></span></span><span class="line"><span class="ln">466</span><span class="cl"><span class="s2">    write_hook_output({&#34;decision&#34;: &#34;allow&#34;})
</span></span></span><span class="line"><span class="ln">467</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">468</span><span class="cl"><span class="s2">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln">469</span><span class="cl"><span class="s2">    main()
</span></span></span><span class="line"><span class="ln">470</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">471</span><span class="cl">
</span></span><span class="line"><span class="ln">472</span><span class="cl">        <span class="c1"># Validate</span>
</span></span><span class="line"><span class="ln">473</span><span class="cl">        <span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">project_root</span><span class="o">=</span><span class="n">tmpdir</span><span class="p">,</span> <span class="n">auto_discover</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">474</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">475</span><span class="cl">
</span></span><span class="line"><span class="ln">476</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Validation result for </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">477</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Compliant: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_compliant</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">478</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Checks run: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">checks_run</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">479</span><span class="cl">        <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">480</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">level</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="建立插件">建立插件</h4>
<p>建立自訂檢查項只需要實作 Protocol 定義的介面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">DocstringCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    Check that hooks have proper docstrings
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    A custom check that can be added to the validator.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    No inheritance required - just implement the protocol.
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;docstring&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a module docstring&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># Check for module docstring</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">docstring_pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">&#39;^(#!/.*\n)?[\s]*[&#34;</span><span class="se">\&#39;</span><span class="s1">\]</span><span class="si">{3}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">docstring_pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing module docstring&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s1">&#39;Add a docstring at the top: &#34;&#34;&#34;Description of hook&#34;&#34;&#34;&#39;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">class</span> <span class="nc">SecurityCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">    Check for potential security issues
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">    Looks for dangerous patterns like eval(), exec(), subprocess with shell=True
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">DANGEROUS_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\beval\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;eval() is dangerous, consider alternatives&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\bexec\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;exec() is dangerous, consider alternatives&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;subprocess.*shell\s*=\s*True&#39;</span><span class="p">,</span> <span class="s2">&#34;shell=True is a security risk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;os\.system\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;os.system() is insecure, use subprocess instead&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;security&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check for potential security vulnerabilities&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">message</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">DANGEROUS_PATTERNS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="n">message</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><h4 id="註冊插件">註冊插件</h4>
<p>有三種方式可以註冊插件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Method 1: Using decorator (at class definition time)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">MyCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;my_check&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># Method 2: Explicit registration (at runtime)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">docstring_check</span> <span class="o">=</span> <span class="n">DocstringCheck</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">security_check</span> <span class="o">=</span> <span class="n">SecurityCheck</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">default_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">docstring_check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">default_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">security_check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># Method 3: Create custom registry</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">custom_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">custom_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">DocstringCheck</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">custom_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">SecurityCheck</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">registry</span><span class="o">=</span><span class="n">custom_registry</span><span class="p">,</span> <span class="n">auto_discover</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></span></span></code></pre></div><h4 id="使用-entry_points">使用 entry_points</h4>
<p>對於要發布為獨立套件的插件，使用 <code>pyproject.toml</code> 設定 entry_points：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># pyproject.toml</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-hook-checks&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;1.0.0&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Custom hook validation checks&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c"># hook-validator is the core package</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">entry-points</span><span class="p">.</span><span class="s2">&#34;hook_validator.checks&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># Format: name = &#34;module:factory_or_class&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">docstring</span> <span class="p">=</span> <span class="s2">&#34;my_hook_checks:DocstringCheck&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">security</span> <span class="p">=</span> <span class="s2">&#34;my_hook_checks.security:SecurityCheck&#34;</span></span></span></code></pre></div><p>插件模組結構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># my_hook_checks/__init__.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">.docstring</span> <span class="kn">import</span> <span class="n">DocstringCheck</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">.security</span> <span class="kn">import</span> <span class="n">SecurityCheck</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;DocstringCheck&#34;</span><span class="p">,</span> <span class="s2">&#34;SecurityCheck&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># my_hook_checks/docstring.py</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">DocstringCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check for module docstrings&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;docstring&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a module docstring&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># ... implementation ...</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span></span></span></code></pre></div><p>安裝後，<code>PluginValidator</code> 會自動發現並載入這些檢查項：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Automatic discovery from entry_points</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">auto_discover</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Check what&#39;s loaded</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">list_checks</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># [&#39;naming_convention&#39;, &#39;lib_import&#39;, &#39;output_format&#39;, &#39;test_exists&#39;,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">#  &#39;docstring&#39;, &#39;security&#39;]  # &lt;- external checks discovered!</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>硬編碼方法</th>
          <th>插件架構</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>擴展性</strong></td>
          <td>差：需修改核心程式碼</td>
          <td>優秀：第三方可自由擴展</td>
      </tr>
      <tr>
          <td><strong>初始複雜度</strong></td>
          <td>低：直接寫邏輯</td>
          <td>中：需要理解 Protocol 和註冊機制</td>
      </tr>
      <tr>
          <td><strong>維護成本</strong></td>
          <td>隨功能增加而上升</td>
          <td>穩定：新增功能不改核心</td>
      </tr>
      <tr>
          <td><strong>第三方擴展</strong></td>
          <td>不支援</td>
          <td>支援：透過 entry_points</td>
      </tr>
      <tr>
          <td><strong>測試難度</strong></td>
          <td>需要 mock 整個類別</td>
          <td>容易：每個 check 獨立測試</td>
      </tr>
      <tr>
          <td><strong>執行時彈性</strong></td>
          <td>固定</td>
          <td>高：可動態啟用/停用</td>
      </tr>
      <tr>
          <td><strong>除錯難度</strong></td>
          <td>簡單：程式碼集中</td>
          <td>需要追蹤插件來源</td>
      </tr>
      <tr>
          <td><strong>效能開銷</strong></td>
          <td>無</td>
          <td>輕微：註冊和迭代成本</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要支援第三方擴展的工具（如 pytest、flake8、pre-commit）</li>
<li>功能模組化明確，各檢查項獨立</li>
<li>預期會頻繁新增功能</li>
<li>需要讓使用者自訂行為</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>內部使用的小工具</li>
<li>功能很少變動</li>
<li>團隊對 Protocol 和 entry_points 不熟悉</li>
<li>效能關鍵的熱路徑</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習實作格式檢查插件">基礎練習：實作格式檢查插件</h3>
<p>實作一個 <code>IndentationCheck</code>，檢查 Python 檔案是否使用一致的縮排（空格 vs Tab）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">IndentationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check for consistent indentation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;indentation&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check for consistent indentation style&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># 1. Check each line&#39;s leading whitespace</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># 2. Detect if mixing tabs and spaces</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 3. Report as warning if inconsistent</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>context.content.splitlines()</code> 取得每一行</li>
<li>檢查每行開頭的空白字元 <code>line[:len(line) - len(line.lstrip())]</code></li>
</ul>
<h3 id="進階練習新增優先順序和相依性">進階練習：新增優先順序和相依性</h3>
<p>擴展 <code>ValidationCheck</code> Protocol，支援：</p>
<ol>
<li><strong>優先順序</strong>：決定檢查項執行順序</li>
<li><strong>相依性</strong>：某些檢查必須在其他檢查之後執行</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">priority</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Lower number = higher priority (default: 100)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">depends_on</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Names of checks that must run before this one&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span> <span class="o">...</span></span></span></code></pre></div><p>然後修改 <code>PluginValidator.validate_hook()</code> 使用拓撲排序執行檢查。</p>
<h3 id="挑戰題實作插件的熱載入">挑戰題：實作插件的熱載入</h3>
<p>實作「不重啟程式就能載入新插件」的功能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HotReloadableRegistry</span><span class="p">(</span><span class="n">CheckRegistry</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Registry that supports hot-reloading plugins&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">watch_directory</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Watch a directory for new plugin files&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># TODO: Use watchdog or polling to detect new files</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">reload_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Reload a specific check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># TODO: Unload old module, load new version</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>importlib.reload()</code> 重新載入模組</li>
<li>使用 <code>watchdog</code> 套件監控檔案變化</li>
<li>注意處理模組快取 <code>sys.modules</code></li>
</ul>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/typing.html#typing.Protocol">Python typing.Protocol</a></li>
<li><a href="https://docs.python.org/3/library/importlib.metadata.html#entry-points">importlib.metadata entry_points</a></li>
<li><a href="https://docs.pytest.org/en/stable/how-to/writing_plugins.html">pytest 插件系統</a></li>
<li><a href="https://peps.python.org/pep-0544/">PEP 544 - Protocols: Structural subtyping</a></li>
<li><a href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">setuptools entry_points</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></em></p>
]]></content:encoded></item><item><title>案例：異常設計架構</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_io.py&lt;/code> 的實際程式碼，展示如何設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>.claude/lib/hook_io.py&lt;/code> 使用簡單的錯誤處理方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 從 stdin 讀取 Hook 輸入
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict: 解析後的 JSON 資料，解析失敗時返回空字典
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個設計有以下特點：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>捕獲所有異常&lt;/strong>：使用 &lt;code>except Exception&lt;/code> 確保不會崩潰&lt;/li>
&lt;li>&lt;strong>靜默失敗&lt;/strong>：錯誤時返回空字典，不報告錯誤原因&lt;/li>
&lt;li>&lt;strong>無法區分錯誤類型&lt;/strong>：JSON 解析錯誤和其他錯誤用同樣方式處理&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單可靠&lt;/strong>：Hook 不會因為輸入問題而崩潰&lt;/li>
&lt;li>&lt;strong>使用標準異常&lt;/strong>：不需要定義額外類別&lt;/li>
&lt;li>&lt;strong>API 簡潔&lt;/strong>：呼叫者不需要處理異常&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當錯誤處理變複雜時，這個設計會遇到問題：&lt;/p>
&lt;h4 id="問題-1無法區分不同來源的錯誤">問題 1：無法區分不同來源的錯誤&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">process_hook&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">input_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">input_data&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 問題：不知道是 JSON 解析失敗還是 stdin 讀取失敗&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 無法給出有意義的錯誤訊息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;unknown error&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2多個錯誤只能報告第一個">問題 2：多個錯誤只能報告第一個&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證 Hook 配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;missing tool_name&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 第一個錯誤後就停止&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;tool_input&amp;#34;&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;missing tool_input&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 永遠不會執行到&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-3錯誤恢復邏輯難以實作">問題 3：錯誤恢復邏輯難以實作&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">handle_hook_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;處理 Hook 錯誤&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 問題：只能用 isinstance 檢查，很難擴展&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;invalid json&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;file not found&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">)}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>建立清晰的異常階層&lt;/strong>：不同錯誤類型有不同的異常類別&lt;/li>
&lt;li>&lt;strong>支援多重錯誤收集&lt;/strong>：使用 ExceptionGroup 收集多個驗證錯誤&lt;/li>
&lt;li>&lt;strong>提供豐富的錯誤資訊&lt;/strong>：異常攜帶足夠的上下文資訊&lt;/li>
&lt;li>&lt;strong>支援錯誤恢復策略&lt;/strong>：可以根據錯誤類型決定恢復方式&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1設計異常階層">步驟 1：設計異常階層&lt;/h4>
&lt;p>設計異常階層時，要考慮錯誤的分類和繼承關係：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">Hook 異常階層設計
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">Exception hierarchy:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> HookError (base)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> ├── HookConfigError (configuration issues)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ ├── ConfigNotFoundError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ ├── ConfigParseError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ └── ConfigValidationError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">&lt;span class="s2"> ├── HookInputError (input processing)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ ├── InputReadError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ └── InputValidationError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="s2"> └── HookExecutionError (runtime issues)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="s2"> ├── ToolNotFoundError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">&lt;span class="s2"> └── PermissionDeniedError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">Exception&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">&lt;span class="s2"> Hook 異常基礎類別
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl">&lt;span class="s2"> 所有 Hook 相關的異常都繼承自這個類別，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">&lt;span class="s2"> 讓呼叫者可以用 `except HookError` 捕獲所有 Hook 錯誤。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__str__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl"> &lt;span class="n">ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;, &amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Configuration Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookConfigError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置相關錯誤的基礎類別&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置檔案不存在&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">search_paths&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search_paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">search_paths&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;search_paths&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search_paths&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigParseError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置檔案解析失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">detail&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">line&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">line&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">detail&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">detail&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Failed to parse config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; at line &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">detail&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">detail&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置內容驗證失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reason&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reason&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Invalid config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;: field &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">reason&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;field&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Input Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookInputError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;輸入相關錯誤的基礎類別&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">InputReadError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookInputError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;讀取輸入失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cause&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">source&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cause&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cause&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Failed to read from &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">source&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;source&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">cause&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">__cause__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cause&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">InputValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookInputError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;輸入驗證失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">expected&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">actual&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expected&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">expected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">actual&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">actual&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Invalid input: field &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">expected&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">actual&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;, got &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">actual&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;field&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;expected&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">expected&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Execution Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookExecutionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;執行時錯誤的基礎類別&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ToolNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookExecutionError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;工具不存在&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tool_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">available_tools&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tool_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">available_tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">available_tools&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Tool &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">tool_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">tool_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;available&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">available_tools&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PermissionDeniedError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookExecutionError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;權限不足&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">resource&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reason&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">action&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">action&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resource&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">resource&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reason&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Permission denied: cannot &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">action&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">reason&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">reason&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;resource&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">resource&lt;/span>&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2豐富的錯誤資訊">步驟 2：豐富的錯誤資訊&lt;/h4>
&lt;p>異常類別可以攜帶豐富的上下文資訊，幫助除錯和錯誤恢復：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_io.py</code> 的實際程式碼，展示如何設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>.claude/lib/hook_io.py</code> 使用簡單的錯誤處理方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    從 stdin 讀取 Hook 輸入
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        dict: 解析後的 JSON 資料，解析失敗時返回空字典
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span></span></span></code></pre></div><p>這個設計有以下特點：</p>
<ol>
<li><strong>捕獲所有異常</strong>：使用 <code>except Exception</code> 確保不會崩潰</li>
<li><strong>靜默失敗</strong>：錯誤時返回空字典，不報告錯誤原因</li>
<li><strong>無法區分錯誤類型</strong>：JSON 解析錯誤和其他錯誤用同樣方式處理</li>
</ol>
<h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單可靠</strong>：Hook 不會因為輸入問題而崩潰</li>
<li><strong>使用標準異常</strong>：不需要定義額外類別</li>
<li><strong>API 簡潔</strong>：呼叫者不需要處理異常</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當錯誤處理變複雜時，這個設計會遇到問題：</p>
<h4 id="問題-1無法區分不同來源的錯誤">問題 1：無法區分不同來源的錯誤</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">process_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">input_data</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">input_data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="c1"># 問題：不知道是 JSON 解析失敗還是 stdin 讀取失敗</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="c1"># 無法給出有意義的錯誤訊息</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;unknown error&#34;</span><span class="p">}</span></span></span></code></pre></div><h4 id="問題-2多個錯誤只能報告第一個">問題 2：多個錯誤只能報告第一個</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook_config</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證 Hook 配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;missing tool_name&#34;</span><span class="p">)</span>  <span class="c1"># 第一個錯誤後就停止</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;missing tool_input&#34;</span><span class="p">)</span>  <span class="c1"># 永遠不會執行到</span></span></span></code></pre></div><h4 id="問題-3錯誤恢復邏輯難以實作">問題 3：錯誤恢復邏輯難以實作</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">handle_hook_error</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理 Hook 錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># 問題：只能用 isinstance 檢查，很難擴展</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;invalid json&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="ne">FileNotFoundError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;file not found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="p">)}</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>建立清晰的異常階層</strong>：不同錯誤類型有不同的異常類別</li>
<li><strong>支援多重錯誤收集</strong>：使用 ExceptionGroup 收集多個驗證錯誤</li>
<li><strong>提供豐富的錯誤資訊</strong>：異常攜帶足夠的上下文資訊</li>
<li><strong>支援錯誤恢復策略</strong>：可以根據錯誤類型決定恢復方式</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1設計異常階層">步驟 1：設計異常階層</h4>
<p>設計異常階層時，要考慮錯誤的分類和繼承關係：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Hook 異常階層設計
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">Exception hierarchy:
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">    HookError (base)
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    ├── HookConfigError (configuration issues)
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    │   ├── ConfigNotFoundError
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    │   ├── ConfigParseError
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    │   └── ConfigValidationError
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    ├── HookInputError (input processing)
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    │   ├── InputReadError
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">    │   └── InputValidationError
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">    └── HookExecutionError (runtime issues)
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">        ├── ToolNotFoundError
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="s2">        └── PermissionDeniedError
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">class</span> <span class="nc">HookError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    Hook 異常基礎類別
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    所有 Hook 相關的異常都繼承自這個類別，
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">    讓呼叫者可以用 `except HookError` 捕獲所有 Hook 錯誤。
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">context</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="n">ctx</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">v</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">items</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">ctx</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1"># === Configuration Errors ===</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfigError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置相關錯誤的基礎類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigNotFoundError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置檔案不存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">search_paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span> <span class="o">=</span> <span class="n">search_paths</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;search_paths&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigParseError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置檔案解析失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">line</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">detail</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">detail</span> <span class="o">=</span> <span class="n">detail</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Failed to parse config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; at line </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">if</span> <span class="n">detail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;: </span><span class="si">{</span><span class="n">detail</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigValidationError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置內容驗證失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Invalid config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="c1"># === Input Errors ===</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="k">class</span> <span class="nc">HookInputError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="s2">&#34;&#34;&#34;輸入相關錯誤的基礎類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="k">class</span> <span class="nc">InputReadError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="s2">&#34;&#34;&#34;讀取輸入失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cause</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Failed to read from </span><span class="si">{</span><span class="n">source</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;source&#34;</span><span class="p">:</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">if</span> <span class="n">cause</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">__cause__</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="k">class</span> <span class="nc">InputValidationError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="s2">&#34;&#34;&#34;輸入驗證失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">expected</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">actual</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected</span> <span class="o">=</span> <span class="n">expected</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual</span> <span class="o">=</span> <span class="n">actual</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Invalid input: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="k">if</span> <span class="n">actual</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;, got </span><span class="si">{</span><span class="n">actual</span><span class="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">:</span> <span class="n">expected</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="c1"># === Execution Errors ===</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="k">class</span> <span class="nc">HookExecutionError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行時錯誤的基礎類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="k">class</span> <span class="nc">ToolNotFoundError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="s2">&#34;&#34;&#34;工具不存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">available_tools</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span> <span class="o">=</span> <span class="n">tool_name</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span> <span class="o">=</span> <span class="n">available_tools</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Tool &#39;</span><span class="si">{</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="n">tool_name</span><span class="p">,</span> <span class="s2">&#34;available&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="k">class</span> <span class="nc">PermissionDeniedError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="s2">&#34;&#34;&#34;權限不足&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">action</span> <span class="o">=</span> <span class="n">action</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">resource</span> <span class="o">=</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Permission denied: cannot </span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="s2"> &#39;</span><span class="si">{</span><span class="n">resource</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="n">reason</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; (</span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="n">action</span><span class="p">,</span> <span class="s2">&#34;resource&#34;</span><span class="p">:</span> <span class="n">resource</span><span class="p">})</span></span></span></code></pre></div><h4 id="步驟-2豐富的錯誤資訊">步驟 2：豐富的錯誤資訊</h4>
<p>異常類別可以攜帶豐富的上下文資訊，幫助除錯和錯誤恢復：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    錯誤上下文資訊
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    收集錯誤發生時的環境資訊，
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    幫助除錯和錯誤報告。
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">timestamp</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">hook_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">tool_input</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">environment</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">stack_info</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;轉換為字典，方便序列化&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="s2">&#34;timestamp&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">            <span class="s2">&#34;hook_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hook_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">            <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">            <span class="s2">&#34;environment&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">environment</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">RichHookError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    帶有豐富上下文的 Hook 異常
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    提供詳細的錯誤資訊，包含：
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">    - 錯誤發生的時間
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    - 相關的 Hook 和工具資訊
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    - 環境變數
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">    - 建議的修復方式
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="n">error_code</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="n">error_context</span><span class="p">:</span> <span class="n">ErrorContext</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">suggestions</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">recoverable</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_code</span> <span class="o">=</span> <span class="n">error_code</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span> <span class="o">=</span> <span class="n">error_context</span> <span class="ow">or</span> <span class="n">ErrorContext</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span> <span class="o">=</span> <span class="n">suggestions</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">recoverable</span> <span class="o">=</span> <span class="n">recoverable</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">def</span> <span class="nf">format_message</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="s2">&#34;&#34;&#34;格式化完整錯誤訊息&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">hook_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Hook: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">tool_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Tool: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Suggestions:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">            <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">suggestion</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">. </span><span class="si">{</span><span class="n">suggestion</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="k">def</span> <span class="nf">to_response</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="s2">&#34;&#34;&#34;轉換為 Hook 回應格式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="s2">&#34;error_code&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_code</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="s2">&#34;context&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">to_dict</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="s2">&#34;suggestions&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="s2">&#34;recoverable&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="c1"># Example: specific error with rich context</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidationError</span><span class="p">(</span><span class="n">RichHookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">    Hook 驗證錯誤
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="s2">    當 Hook 輸入驗證失敗時拋出，
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="s2">    提供詳細的錯誤資訊和修復建議。
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="n">actual_value</span><span class="p">:</span> <span class="n">Any</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="n">error_context</span><span class="p">:</span> <span class="n">ErrorContext</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual_value</span> <span class="o">=</span> <span class="n">actual_value</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Validation failed for &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">suggestions</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_generate_suggestions</span><span class="p">(</span><span class="n">field</span><span class="p">,</span> <span class="n">reason</span><span class="p">,</span> <span class="n">actual_value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">            <span class="n">message</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">            <span class="n">error_code</span><span class="o">=</span><span class="s2">&#34;HOOK_VALIDATION_ERROR&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">error_context</span><span class="o">=</span><span class="n">error_context</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">suggestions</span><span class="o">=</span><span class="n">suggestions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">recoverable</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">def</span> <span class="nf">_generate_suggestions</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="n">actual_value</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="s2">&#34;&#34;&#34;根據錯誤類型生成修復建議&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="n">suggestions</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="k">if</span> <span class="s2">&#34;required&#34;</span> <span class="ow">in</span> <span class="n">reason</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Add the required field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; to your input&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">if</span> <span class="s2">&#34;type&#34;</span> <span class="ow">in</span> <span class="n">reason</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Check the type of &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; - expected type may differ&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="k">if</span> <span class="n">actual_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Current value: </span><span class="si">{</span><span class="n">actual_value</span><span class="si">!r}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">return</span> <span class="n">suggestions</span></span></span></code></pre></div><h4 id="步驟-3使用-exceptiongrouppython-311">步驟 3：使用 ExceptionGroup（Python 3.11+）</h4>
<p>Python 3.11 引入的 <code>ExceptionGroup</code> 可以同時報告多個錯誤：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCollector</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">    驗證錯誤收集器
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    收集多個驗證錯誤，最後用 ExceptionGroup 一次報告。
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    這樣可以讓使用者一次看到所有錯誤，而不是修完一個才看到下一個。
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;validation&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context_name</span> <span class="o">=</span> <span class="n">context_name</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="ne">Exception</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;新增一個錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">        檢查條件，失敗時收集錯誤
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">            condition: 要檢查的條件
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">            error: 條件為 False 時要收集的錯誤
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">def</span> <span class="nf">has_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;是否有錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">raise_if_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        如果有錯誤，拋出 ExceptionGroup
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        Raises:
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">            ExceptionGroup: 包含所有收集到的錯誤
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">context_name</span><span class="si">}</span><span class="s2"> failed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> error(s)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">errors</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">    驗證 Hook 輸入，收集所有錯誤
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">    使用 ExceptionGroup 一次報告所有驗證錯誤，
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">    而不是只報告第一個錯誤。
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        data: 待驗證的輸入資料
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">        驗證後的資料
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">    Raises:
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">        ExceptionGroup: 包含所有驗證錯誤
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;Hook input validation&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="c1"># Check required fields</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="c1"># Check types (only if fields exist)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">],</span> <span class="nb">str</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                <span class="s2">&#34;tool_name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                <span class="s2">&#34;must be a string&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">],</span> <span class="nb">dict</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="s2">&#34;tool_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                <span class="s2">&#34;must be a dict&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="c1"># Check tool-specific validation</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">if</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;Write&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="s2">&#34;file_path&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input.file_path&#34;</span><span class="p">,</span> <span class="s2">&#34;is required for Write tool&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="s2">&#34;content&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input.content&#34;</span><span class="p">,</span> <span class="s2">&#34;is required for Write tool&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="c1"># Raise all errors at once</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="c1"># Using except* to handle ExceptionGroup (Python 3.11+)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="k">def</span> <span class="nf">handle_validation_errors_demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範如何用 except* 處理 ExceptionGroup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="mi">123</span><span class="p">,</span>  <span class="c1"># Wrong type</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="c1"># Missing tool_input</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="c1"># Handle all InputValidationError instances</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> validation errors:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  - </span><span class="si">{</span><span class="n">error</span><span class="o">.</span><span class="n">field</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">error</span><span class="o">.</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">HookError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="c1"># Handle other HookError instances</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> hook errors&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-4錯誤恢復策略">步驟 4：錯誤恢復策略</h4>
<p>設計可以根據錯誤類型決定恢復方式的機制：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="k">class</span> <span class="nc">RecoveryStrategy</span><span class="p">(</span><span class="n">ABC</span><span class="p">,</span> <span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    錯誤恢復策略抽象基礎類別
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    定義錯誤恢復的介面，讓不同類型的錯誤
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    可以有不同的恢復方式。
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;判斷是否可以恢復此錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;執行恢復邏輯，返回恢復後的結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="k">class</span> <span class="nc">DefaultValueRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">    預設值恢復策略
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">    當錯誤發生時，返回預設值。
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="p">(</span><span class="n">ConfigNotFoundError</span><span class="p">,</span> <span class="n">InputReadError</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="k">class</span> <span class="nc">RetryRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    重試恢復策略
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">    當錯誤是暫時性的，嘗試重試。
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="n">operation</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="nb">dict</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="n">recoverable_errors</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">type</span><span class="p">[</span><span class="ne">Exception</span><span class="p">],</span> <span class="o">...</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="ne">IOError</span><span class="p">,)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">operation</span> <span class="o">=</span> <span class="n">operation</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span> <span class="o">=</span> <span class="n">max_retries</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</span> <span class="o">=</span> <span class="n">recoverable_errors</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="n">last_error</span> <span class="o">=</span> <span class="n">error</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span> <span class="o">**</span> <span class="n">attempt</span><span class="p">))</span>  <span class="c1"># Exponential backoff</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">operation</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">            <span class="k">except</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="n">last_error</span> <span class="o">=</span> <span class="n">e</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="c1"># All retries failed, re-raise the last error</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">raise</span> <span class="n">last_error</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorRecoveryChain</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">    錯誤恢復鏈
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">    按順序嘗試多個恢復策略，
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">    直到找到可以處理的策略。
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">RecoveryStrategy</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="k">def</span> <span class="nf">add_strategy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">strategy</span><span class="p">:</span> <span class="n">RecoveryStrategy</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ErrorRecoveryChain&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="s2">&#34;&#34;&#34;新增恢復策略&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">strategy</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="k">def</span> <span class="nf">try_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="s2">        嘗試恢復錯誤
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="s2">            error: 要恢復的錯誤
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">            (是否成功恢復, 恢復結果或 None)
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="k">if</span> <span class="n">strategy</span><span class="o">.</span><span class="n">can_recover</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">strategy</span><span class="o">.</span><span class="n">recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input_with_recovery</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="s2">    讀取 Hook 輸入，帶有錯誤恢復機制
</span></span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="s2">    使用恢復鏈處理不同類型的錯誤。
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="c1"># Set up recovery chain</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{}}))</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="k">return</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="c1"># JSON parsing error - try to recover</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="c1"># Multiple validation errors</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="c1"># Check if all errors are recoverable</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="n">all_recoverable</span> <span class="o">=</span> <span class="nb">all</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="p">(</span><span class="n">InputValidationError</span><span class="p">,))</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="k">if</span> <span class="n">all_recoverable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{},</span> <span class="s2">&#34;validation_errors&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)}</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="k">raise</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">        <span class="c1"># Unknown error</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Hook Exception Hierarchy - Complete Example
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">Demonstrates how to design a clear exception hierarchy
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">and use ExceptionGroup for multiple error handling.
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="c1"># ===== Exception Hierarchy =====</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">class</span> <span class="nc">HookError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Base class for all Hook-related exceptions.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">context</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">            <span class="n">ctx</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">v</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">items</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">ctx</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfigError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Configuration-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">
</span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigNotFoundError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Config file not found.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">search_paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span> <span class="o">=</span> <span class="n">search_paths</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;search_paths&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigParseError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Failed to parse config file.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">line</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">detail</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">detail</span> <span class="o">=</span> <span class="n">detail</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Failed to parse config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; at line </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="k">if</span> <span class="n">detail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;: </span><span class="si">{</span><span class="n">detail</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="k">class</span> <span class="nc">HookInputError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Input-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">
</span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="k">class</span> <span class="nc">InputReadError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Failed to read input.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cause</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to read from </span><span class="si">{</span><span class="n">source</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;source&#34;</span><span class="p">:</span> <span class="n">source</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="k">if</span> <span class="n">cause</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">__cause__</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="k">class</span> <span class="nc">InputValidationError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Input validation failed.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">expected</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">actual</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected</span> <span class="o">=</span> <span class="n">expected</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual</span> <span class="o">=</span> <span class="n">actual</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Invalid input: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">if</span> <span class="n">actual</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;, got </span><span class="si">{</span><span class="n">actual</span><span class="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">:</span> <span class="n">expected</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="k">class</span> <span class="nc">HookExecutionError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Execution-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="k">class</span> <span class="nc">ToolNotFoundError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Tool not found.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">available_tools</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span> <span class="o">=</span> <span class="n">tool_name</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span> <span class="o">=</span> <span class="n">available_tools</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Tool &#39;</span><span class="si">{</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="n">tool_name</span><span class="p">,</span> <span class="s2">&#34;available&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="k">class</span> <span class="nc">PermissionDeniedError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Permission denied.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">action</span> <span class="o">=</span> <span class="n">action</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">resource</span> <span class="o">=</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Permission denied: cannot </span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="s2"> &#39;</span><span class="si">{</span><span class="n">resource</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">if</span> <span class="n">reason</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; (</span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="n">action</span><span class="p">,</span> <span class="s2">&#34;resource&#34;</span><span class="p">:</span> <span class="n">resource</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="c1"># ===== Error Context =====</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Rich context information for errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="n">timestamp</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="n">hook_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="n">tool_input</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="n">environment</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">            <span class="s2">&#34;timestamp&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="s2">&#34;hook_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hook_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="s2">&#34;environment&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">environment</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="c1"># ===== Validation Collector =====</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCollector</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Collects validation errors and raises ExceptionGroup.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;validation&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context_name</span> <span class="o">=</span> <span class="n">context_name</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="ne">Exception</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">def</span> <span class="nf">has_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="k">def</span> <span class="nf">raise_if_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">context_name</span><span class="si">}</span><span class="s2"> failed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> error(s)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">errors</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl"><span class="c1"># ===== Recovery Strategies =====</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="k">class</span> <span class="nc">RecoveryStrategy</span><span class="p">(</span><span class="n">ABC</span><span class="p">,</span> <span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Abstract base class for error recovery strategies.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">
</span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="k">class</span> <span class="nc">DefaultValueRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Returns a default value when error occurs.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="p">(</span><span class="n">ConfigNotFoundError</span><span class="p">,</span> <span class="n">InputReadError</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorRecoveryChain</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Chain of recovery strategies.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">
</span></span><span class="line"><span class="ln">192</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">RecoveryStrategy</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">
</span></span><span class="line"><span class="ln">195</span><span class="cl">    <span class="k">def</span> <span class="nf">add_strategy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">strategy</span><span class="p">:</span> <span class="n">RecoveryStrategy</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ErrorRecoveryChain&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">strategy</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="k">def</span> <span class="nf">try_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">            <span class="k">if</span> <span class="n">strategy</span><span class="o">.</span><span class="n">can_recover</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">strategy</span><span class="o">.</span><span class="n">recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl"><span class="c1"># ===== Main Functions =====</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">
</span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="s2">    Validate hook input, collecting all errors.
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="s2">    Uses ExceptionGroup to report all validation errors at once.
</span></span></span><span class="line"><span class="ln">216</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;Hook input validation&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="c1"># Required fields</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">
</span></span><span class="line"><span class="ln">229</span><span class="cl">    <span class="c1"># Type checks</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">],</span> <span class="nb">str</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">                <span class="s2">&#34;tool_name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">                <span class="s2">&#34;must be a string&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">
</span></span><span class="line"><span class="ln">240</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">],</span> <span class="nb">dict</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">                <span class="s2">&#34;tool_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">                <span class="s2">&#34;must be a dict&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">
</span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input_safe</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">255</span><span class="cl"><span class="s2">    Read hook input with error recovery.
</span></span></span><span class="line"><span class="ln">256</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">257</span><span class="cl"><span class="s2">    Enhanced version of the original read_hook_input()
</span></span></span><span class="line"><span class="ln">258</span><span class="cl"><span class="s2">    with proper exception handling.
</span></span></span><span class="line"><span class="ln">259</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{}}))</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="k">return</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">except</span> <span class="n">ExceptionGroup</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">        <span class="c1"># Re-raise validation errors as-is</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="k">raise</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">
</span></span><span class="line"><span class="ln">285</span><span class="cl"><span class="k">def</span> <span class="nf">create_error_response</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="n">HookError</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Create a standardized error response.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;deny&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">            <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="nb">type</span><span class="p">(</span><span class="n">error</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="s2">&#34;context&#34;</span><span class="p">:</span> <span class="n">error</span><span class="o">.</span><span class="n">context</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">
</span></span><span class="line"><span class="ln">297</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">
</span></span><span class="line"><span class="ln">299</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== Exception Hierarchy Demo ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="c1"># Demo 1: Basic exception</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. Basic exception:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;/path/1&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/2&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="k">except</span> <span class="n">HookConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">308</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Context: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">context</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl">    <span class="c1"># Demo 2: Exception chaining</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. Exception chaining:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">            <span class="k">raise</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">(</span><span class="s2">&#34;Unexpected EOF&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">        <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">            <span class="k">raise</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="k">except</span> <span class="n">InputReadError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caused by: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">__cause__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="c1"># Demo 3: ExceptionGroup</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">3. ExceptionGroup (multiple errors):&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="mi">123</span><span class="p">})</span>  <span class="c1"># Wrong type, missing tool_input</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught ExceptionGroup: </span><span class="si">{</span><span class="n">eg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">err</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Error </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">
</span></span><span class="line"><span class="ln">330</span><span class="cl">    <span class="c1"># Demo 4: except* syntax (Python 3.11+)</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">4. Using except* to handle specific error types:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">333</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({})</span>  <span class="c1"># Missing both fields</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> InputValidationError(s)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">336</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   - </span><span class="si">{</span><span class="n">err</span><span class="o">.</span><span class="n">field</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="o">.</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">
</span></span><span class="line"><span class="ln">339</span><span class="cl">    <span class="c1"># Demo 5: Error recovery</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">5. Error recovery:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;default&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}))</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="n">error</span> <span class="o">=</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="s2">&#34;missing_config&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Recovered: </span><span class="si">{</span><span class="n">recovered</span><span class="si">}</span><span class="s2">, Result: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== Demo Complete ===&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_exceptions</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">HookError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">ConfigNotFoundError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">InputValidationError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">create_error_response</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">process_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理 Hook 請求&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># Load configuration</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">config</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="c1"># Validate input</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">read_input</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># Process...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">except</span> <span class="n">ConfigNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># Specific handling for missing config</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Warning: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">, using defaults&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</span><span class="p">,</span> <span class="s2">&#34;warning&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">except</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="c1"># Input validation error</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">return</span> <span class="n">create_error_response</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">except</span> <span class="n">HookError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="c1"># Catch-all for other hook errors</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="n">create_error_response</span><span class="p">(</span><span class="n">e</span><span class="p">)</span></span></span></code></pre></div><h4 id="多重錯誤處理">多重錯誤處理</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_batch_inputs</span><span class="p">(</span><span class="n">inputs</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    驗證多個輸入，收集所有錯誤
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    即使某些輸入失敗，仍然處理其他輸入。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">all_errors</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">data</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">inputs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="n">validated</span> <span class="o">=</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span> <span class="s2">&#34;data&#34;</span><span class="p">:</span> <span class="n">validated</span><span class="p">,</span> <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="c1"># Collect errors from this input</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">                <span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                <span class="s2">&#34;errors&#34;</span><span class="p">:</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">except</span> <span class="n">HookError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span> <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># Report summary</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">if</span> <span class="n">all_errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Validation completed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> total errors&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># Using except* to handle different error types differently</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">def</span> <span class="nf">handle_mixed_errors</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範用 except* 分別處理不同類型的錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="c1"># This might raise ExceptionGroup with mixed errors</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">process_complex_operation</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="c1"># Handle validation errors - maybe log and continue</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">log_validation_error</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">PermissionDeniedError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="c1"># Handle permission errors - need to notify admin</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="n">notify_admin</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">HookExecutionError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="c1"># Handle execution errors - maybe retry</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">schedule_retry</span><span class="p">(</span><span class="n">err</span><span class="p">)</span></span></span></code></pre></div><h4 id="異常鏈exception-chaining">異常鏈（Exception Chaining）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">load_config_with_chain</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    載入配置，保留原始錯誤資訊
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用 `raise ... from ...` 保留異常鏈，
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    讓除錯時可以看到完整的錯誤來源。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">config_path</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;/config/</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># Wrap in domain-specific exception, preserve original</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="p">[</span><span class="n">config_path</span><span class="p">])</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">except</span> <span class="n">yaml</span><span class="o">.</span><span class="n">YAMLError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># Extract line number if available</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">line</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="s2">&#34;problem_mark&#34;</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">line_num</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">line</span> <span class="k">if</span> <span class="n">line</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigParseError</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">line</span><span class="o">=</span><span class="n">line_num</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">except</span> <span class="ne">PermissionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># Convert to domain exception</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">raise</span> <span class="n">PermissionDeniedError</span><span class="p">(</span><span class="s2">&#34;read&#34;</span><span class="p">,</span> <span class="n">config_path</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># When catching, you can access the full chain</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">def</span> <span class="nf">debug_config_error</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">config</span> <span class="o">=</span> <span class="n">load_config_with_chain</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">except</span> <span class="n">HookConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Original cause: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">__cause__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="c1"># Print full traceback including cause</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="kn">import</span> <span class="nn">traceback</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">traceback</span><span class="o">.</span><span class="n">print_exception</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">e</span><span class="p">,</span> <span class="n">e</span><span class="o">.</span><span class="n">__traceback__</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>標準異常</th>
          <th>自定義階層</th>
          <th>ExceptionGroup</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>學習成本</td>
          <td>低</td>
          <td>中</td>
          <td>中高</td>
      </tr>
      <tr>
          <td>錯誤辨識</td>
          <td>困難</td>
          <td>清晰</td>
          <td>清晰</td>
      </tr>
      <tr>
          <td>錯誤資訊</td>
          <td>有限</td>
          <td>豐富</td>
          <td>豐富</td>
      </tr>
      <tr>
          <td>多重錯誤</td>
          <td>只報一個</td>
          <td>只報一個</td>
          <td>全部報告</td>
      </tr>
      <tr>
          <td>恢復策略</td>
          <td>難實作</td>
          <td>易實作</td>
          <td>可分類處理</td>
      </tr>
      <tr>
          <td>Python 版本</td>
          <td>所有版本</td>
          <td>所有版本</td>
          <td>3.11+</td>
      </tr>
      <tr>
          <td>程式碼量</td>
          <td>最少</td>
          <td>中等</td>
          <td>較多</td>
      </tr>
  </tbody>
</table>
<h3 id="何時使用哪種方案">何時使用哪種方案？</h3>
<h4 id="使用標準異常如-hook_iopy-的做法">使用標準異常（如 <code>hook_io.py</code> 的做法）</h4>
<ul>
<li>簡單的腳本或工具</li>
<li>錯誤處理很簡單</li>
<li>不需要區分錯誤類型</li>
</ul>
<h4 id="使用自定義階層">使用自定義階層</h4>
<ul>
<li>函式庫或框架</li>
<li>需要區分不同錯誤類型</li>
<li>需要提供錯誤恢復建議</li>
</ul>
<h4 id="使用-exceptiongroup">使用 ExceptionGroup</h4>
<ul>
<li>批次處理需要收集所有錯誤</li>
<li>驗證邏輯有多個檢查點</li>
<li>需要讓使用者一次看到所有問題</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要區分不同錯誤類型的函式庫</li>
<li>批次處理需要收集所有錯誤</li>
<li>需要提供錯誤恢復建議</li>
<li>需要保留完整的錯誤來源（異常鏈）</li>
<li>Python 3.11+ 環境</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>簡單的腳本</li>
<li>錯誤類型很少（&lt; 3 種）</li>
<li>不需要精細的錯誤處理</li>
<li>需要支援舊版 Python（&lt; 3.11）使用 ExceptionGroup</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li><strong>設計異常階層</strong>：為一個配置載入模組設計異常階層，包含：
<ul>
<li>檔案不存在</li>
<li>格式錯誤</li>
<li>欄位缺失</li>
<li>型別錯誤</li>
</ul>
</li>
</ol>
<p>提示：先畫出階層圖，再實作類別。</p>
<h3 id="進階練習">進階練習</h3>
<ol>
<li><strong>實作 ExceptionGroup 驗證器</strong>：寫一個表單驗證器，可以：
<ul>
<li>收集所有欄位的驗證錯誤</li>
<li>用 ExceptionGroup 一次報告</li>
<li>支援 <code>except*</code> 分類處理</li>
</ul>
</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 目標 API</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">validate_form</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證表單，收集所有錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;form&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;username&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">))</span> <span class="o">&gt;=</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">FieldError</span><span class="p">(</span><span class="s2">&#34;username&#34;</span><span class="p">,</span> <span class="s2">&#34;must be at least 3 characters&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;@&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">FieldError</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;must contain @&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">data</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<ol>
<li><strong>實作帶有自動修復建議的異常</strong>：設計一個異常系統，可以：
<ul>
<li>根據錯誤類型自動生成修復建議</li>
<li>支援結構化的錯誤報告（JSON 格式）</li>
<li>提供「可能的修復」和「參考文件」連結</li>
</ul>
</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 目標 API</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">validate_config</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">except</span> <span class="n">ConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">format_message</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># [CONFIG_001] Invalid config &#39;agents.yaml&#39;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1">#   Field: known_agents</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">#   Error: expected list, got string</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1">#</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># Suggestions:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1">#   1. Change &#39;known_agents: &#34;basil&#34;&#39; to &#39;known_agents: [&#34;basil&#34;]&#39;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1">#   2. See: https://docs.example.com/config#known_agents</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://peps.python.org/pep-0654/">PEP 654 - Exception Groups and except*</a></li>
<li><a href="https://peps.python.org/pep-3134/">PEP 3134 - Exception Chaining</a></li>
<li><a href="https://docs.python.org/3/tutorial/errors.html">Python 異常最佳實踐</a></li>
<li><a href="https://realpython.com/python311-exception-groups/">Real Python - Exception Groups</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器</a></em></p>
]]></content:encoded></item><item><title>案例：泛型驗證器</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Generic 和 TypeVar 建立型別安全的通用驗證器。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.5.1 泛型進階&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 針對特定類型設計：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationIssue&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Single validation issue&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># &amp;#34;error&amp;#34; | &amp;#34;warning&amp;#34; | &amp;#34;info&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validation result for a single hook&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">is_compliant&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__post_init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Calculate is_compliant status&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_compliant&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">issue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">level&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">issue&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">issues&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook compliance validator - specific to Path type&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate a single hook file&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook file not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... more validation logic ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Resolve path to absolute path&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_absolute&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">p&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>針對具體需求設計&lt;/strong>：專為驗證 Hook 檔案設計，邏輯清晰&lt;/li>
&lt;li>&lt;strong>型別明確&lt;/strong>：輸入是 &lt;code>str&lt;/code>，輸出是 &lt;code>ValidationResult&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要驗證其他類型時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>需要複製大量相似程式碼&lt;/strong>：如果要驗證 API 回應、設定檔、表單輸入，需要寫多個類似的 Validator&lt;/li>
&lt;li>&lt;strong>驗證邏輯無法重用&lt;/strong>：「檢查是否為空」「檢查格式」這些通用邏輯無法跨 Validator 共享&lt;/li>
&lt;li>&lt;strong>型別檢查不夠通用&lt;/strong>：&lt;code>ValidationResult&lt;/code> 綁定了 &lt;code>hook_path: str&lt;/code>，無法用於其他場景&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>建立通用的驗證器介面&lt;/strong>：定義 &lt;code>Validator[T]&lt;/code> 協議&lt;/li>
&lt;li>&lt;strong>支援任意輸入類型&lt;/strong>：可以驗證 &lt;code>Path&lt;/code>、&lt;code>str&lt;/code>、&lt;code>dict&lt;/code>、自訂類別&lt;/li>
&lt;li>&lt;strong>保持型別安全&lt;/strong>：靜態型別檢查器能捕捉型別錯誤&lt;/li>
&lt;li>&lt;strong>支援驗證器組合&lt;/strong>：用 &lt;code>And&lt;/code>、&lt;code>Or&lt;/code>、&lt;code>Not&lt;/code> 組合基本驗證器&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1定義泛型-validator-協議">步驟 1：定義泛型 Validator 協議&lt;/h4>
&lt;p>首先，我們需要定義「什麼是驗證器」。使用 Protocol 和 TypeVar 來建立泛型介面：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Protocol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Generic&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># Define a type variable for the input type&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n">T&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># Type variable with contravariance for Protocol&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="n">T_contra&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T_contra&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contravariant&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Generic&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">]):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> Generic validation result
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> Type parameter T represents the validated value type.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> This allows type-safe access to the validated value.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">warnings&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">add_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;ValidationResult[T]&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Add an error and mark as invalid&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_valid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">add_warning&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;ValidationResult[T]&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Add a warning (does not affect validity)&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warnings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T_contra&lt;/span>&lt;span class="p">]):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2"> Generic validator protocol
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> Any class that implements validate(value: T) -&amp;gt; ValidationResult[T]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> is considered a Validator[T].
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> Using contravariant type variable because validators consume values.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> A Validator[Animal] can validate Dog (subtype), so it&amp;#39;s contravariant.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T_contra&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate the given value and return the result&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>關鍵設計決策&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Generic 和 TypeVar 建立型別安全的通用驗證器。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.5.1 泛型進階</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 針對特定類型設計：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Single validation issue&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Calculate is_compliant status&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook compliance validator - specific to Path type&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">hook_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </span><span class="si">{</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="c1"># ... more validation logic ...</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute path&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span> <span class="o">/</span> <span class="n">p</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>針對具體需求設計</strong>：專為驗證 Hook 檔案設計，邏輯清晰</li>
<li><strong>型別明確</strong>：輸入是 <code>str</code>，輸出是 <code>ValidationResult</code></li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要驗證其他類型時：</p>
<ul>
<li><strong>需要複製大量相似程式碼</strong>：如果要驗證 API 回應、設定檔、表單輸入，需要寫多個類似的 Validator</li>
<li><strong>驗證邏輯無法重用</strong>：「檢查是否為空」「檢查格式」這些通用邏輯無法跨 Validator 共享</li>
<li><strong>型別檢查不夠通用</strong>：<code>ValidationResult</code> 綁定了 <code>hook_path: str</code>，無法用於其他場景</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>建立通用的驗證器介面</strong>：定義 <code>Validator[T]</code> 協議</li>
<li><strong>支援任意輸入類型</strong>：可以驗證 <code>Path</code>、<code>str</code>、<code>dict</code>、自訂類別</li>
<li><strong>保持型別安全</strong>：靜態型別檢查器能捕捉型別錯誤</li>
<li><strong>支援驗證器組合</strong>：用 <code>And</code>、<code>Or</code>、<code>Not</code> 組合基本驗證器</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1定義泛型-validator-協議">步驟 1：定義泛型 Validator 協議</h4>
<p>首先，我們需要定義「什麼是驗證器」。使用 Protocol 和 TypeVar 來建立泛型介面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># Define a type variable for the input type</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># Type variable with contravariance for Protocol</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">T_contra</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_contra&#34;</span><span class="p">,</span> <span class="n">contravariant</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Generic validation result
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    Type parameter T represents the validated value type.
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    This allows type-safe access to the validated value.
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="n">T</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">is_valid</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">warnings</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidationResult[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add an error and mark as invalid&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">add_warning</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidationResult[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a warning (does not affect validity)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">(</span><span class="n">Protocol</span><span class="p">[</span><span class="n">T_contra</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">    Generic validator protocol
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">    Any class that implements validate(value: T) -&gt; ValidationResult[T]
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">    is considered a Validator[T].
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    Using contravariant type variable because validators consume values.
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">    A Validator[Animal] can validate Dog (subtype), so it&#39;s contravariant.
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_contra</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate the given value and return the result&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><p><strong>關鍵設計決策</strong>：</p>
<ul>
<li><code>T_contra</code> 使用逆變（contravariant），因為驗證器是「消費者」</li>
<li><code>ValidationResult[T]</code> 是泛型，讓結果可以攜帶原始值的型別資訊</li>
<li>Protocol 而非 ABC，支援結構型子型別（不需要顯式繼承）</li>
</ul>
<h4 id="步驟-2實作具體驗證器">步驟 2：實作具體驗證器</h4>
<p>接下來實作幾個常用的基礎驗證器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">NotEmptyValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string is not empty&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="s2">&#34;Value cannot be empty&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">PathExistsValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a path exists&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">must_be_file</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="n">must_be_dir</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="o">=</span> <span class="n">must_be_file</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="o">=</span> <span class="n">must_be_dir</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">Path</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path does not exist: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a file: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a directory: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">class</span> <span class="nc">PatternValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string matches a regex pattern&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">&#34;Value must match pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="k">class</span> <span class="nc">RangeValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a number is within a range&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> is below minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> is above maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="k">return</span> <span class="n">result</span></span></span></code></pre></div><h4 id="步驟-3驗證器組合andornot">步驟 3：驗證器組合（And、Or、Not）</h4>
<p>驗證器的威力來自於組合。實作三個組合器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Sequence</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">AndValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Combine multiple validators with AND logic
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    All validators must pass for the result to be valid.
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="n">validators</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">class</span> <span class="nc">OrValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    Combine multiple validators with OR logic
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">    At least one validator must pass for the result to be valid.
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="n">validators</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">all_errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="c1"># At least one passed, return success</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="c1"># All failed</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;None of the validators passed. Errors: </span><span class="si">{</span><span class="s1">&#39;; &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="k">class</span> <span class="nc">NotValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="s2">    Negate a validator
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">    The result is valid if the inner validator fails.
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="s2">&#34;Validation should have failed&#34;</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl">        <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="c1"># If inner failed, outer succeeds</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="k">return</span> <span class="n">result</span></span></span></code></pre></div><h4 id="步驟-4型別安全的建構器">步驟 4：型別安全的建構器</h4>
<p>為了讓組合更流暢，加入建構器模式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatorBuilder</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Fluent builder for composing validators
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Provides a chainable API for building complex validators.
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidatorBuilder[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a validator to the chain&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="nf">add_if</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidatorBuilder[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Conditionally add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build the final AND-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">return</span> <span class="n">AndValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">def</span> <span class="nf">build_or</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build with OR logic instead of AND&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">return</span> <span class="n">OrValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">validator_for</span><span class="p">(</span><span class="n">type_hint</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    Create a type-safe validator builder
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    Usage:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        validator = (
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">            validator_for(str)
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">            .add(NotEmptyValidator())
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">            .add(PatternValidator(r&#34;^[a-z]+$&#34;))
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">            .build()
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">        )
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">return</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]()</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Generic Validator System
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">A type-safe, composable validation framework using Generic and TypeVar.
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">Callable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">Generic</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">Protocol</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">Sequence</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">TypeVar</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">runtime_checkable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="c1"># ===== Type Variables =====</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="n">T_contra</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_contra&#34;</span><span class="p">,</span> <span class="n">contravariant</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="c1"># ===== Core Types =====</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    Generic validation result
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    Attributes:
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        value: The validated value (preserves type information)
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        is_valid: Whether validation passed
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        errors: List of error messages (cause validation failure)
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        warnings: List of warning messages (informational only)
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="n">T</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">is_valid</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="n">warnings</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add an error and mark as invalid&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="k">def</span> <span class="nf">add_warning</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a warning (does not affect validity)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">def</span> <span class="fm">__bool__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Allow using result in boolean context&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">(</span><span class="n">Protocol</span><span class="p">[</span><span class="n">T_contra</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">    Generic validator protocol
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">    Any class implementing validate(value) -&gt; ValidationResult
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">    satisfies this protocol.
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_contra</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate the given value&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="c1"># ===== Basic Validators =====</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="k">class</span> <span class="nc">NotEmptyValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string is not empty&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="s2">&#34;Value cannot be empty&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="k">class</span> <span class="nc">PathExistsValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a path exists&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="n">must_be_file</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">must_be_dir</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="o">=</span> <span class="n">must_be_file</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="o">=</span> <span class="n">must_be_dir</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">Path</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path does not exist: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a file: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a directory: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="k">class</span> <span class="nc">PatternValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate string matches a regex pattern&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">&#34;Must match pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">
</span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="k">class</span> <span class="nc">RangeValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate number is within range&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="k">class</span> <span class="nc">LengthValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate string length&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">length</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Length </span><span class="si">{</span><span class="n">length</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">length</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Length </span><span class="si">{</span><span class="n">length</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">
</span></span><span class="line"><span class="ln">161</span><span class="cl"><span class="k">class</span> <span class="nc">TypeValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate value is of expected type&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">expected_type</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">type_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected_type</span> <span class="o">=</span> <span class="n">expected_type</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">type_name</span> <span class="o">=</span> <span class="n">type_name</span> <span class="ow">or</span> <span class="n">expected_type</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">expected_type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># type: ignore</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># type: ignore</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Expected </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">type_name</span><span class="si">}</span><span class="s2">, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="c1"># ===== Composite Validators =====</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="k">class</span> <span class="nc">AndValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Combine validators with AND logic (all must pass)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">
</span></span><span class="line"><span class="ln">194</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl"><span class="k">class</span> <span class="nc">OrValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Combine validators with OR logic (at least one must pass)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">
</span></span><span class="line"><span class="ln">203</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="n">all_errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">            <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">                <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">
</span></span><span class="line"><span class="ln">214</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;No validator passed: </span><span class="si">{</span><span class="s1">&#39;; &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl"><span class="k">class</span> <span class="nc">NotValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Negate a validator (passes if inner validator fails)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="s2">&#34;Validation should have failed&#34;</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">
</span></span><span class="line"><span class="ln">228</span><span class="cl">        <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">
</span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="c1"># ===== Builder =====</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">
</span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatorBuilder</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Fluent builder for composing validators&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">
</span></span><span class="line"><span class="ln">241</span><span class="cl">    <span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">
</span></span><span class="line"><span class="ln">246</span><span class="cl">    <span class="k">def</span> <span class="nf">add_if</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">        <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Conditionally add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">        <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build AND-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;No validators added&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">        <span class="k">return</span> <span class="n">AndValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="k">def</span> <span class="nf">build_or</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build OR-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;No validators added&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">return</span> <span class="n">OrValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl"><span class="k">def</span> <span class="nf">validator_for</span><span class="p">(</span><span class="n">type_hint</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Create a type-safe validator builder&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">return</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]()</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">
</span></span><span class="line"><span class="ln">276</span><span class="cl"><span class="c1"># ===== List Validator =====</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</span><span class="cl"><span class="k">class</span> <span class="nc">ListValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate each element in a list&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">
</span></span><span class="line"><span class="ln">281</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="n">element_validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">element_validator</span> <span class="o">=</span> <span class="n">element_validator</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">
</span></span><span class="line"><span class="ln">291</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">list</span><span class="p">[</span><span class="n">T</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="c1"># Check list length</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List length </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List length </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">
</span></span><span class="line"><span class="ln">300</span><span class="cl">        <span class="c1"># Validate each element</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">element_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">            <span class="k">for</span> <span class="n">warning</span> <span class="ow">in</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">add_warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">warning</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">
</span></span><span class="line"><span class="ln">308</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">
</span></span><span class="line"><span class="ln">312</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== Generic Validator Demo ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">
</span></span><span class="line"><span class="ln">315</span><span class="cl">    <span class="c1"># Example 1: Basic validators</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. Basic Validators&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl">    <span class="n">not_empty</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  NotEmpty(&#39;&#39;): </span><span class="si">{</span><span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  NotEmpty(&#39;hello&#39;): </span><span class="si">{</span><span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s1">&#39;hello&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="c1"># Example 2: Path validator</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. Path Validator&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">
</span></span><span class="line"><span class="ln">327</span><span class="cl">    <span class="n">path_validator</span> <span class="o">=</span> <span class="n">PathExistsValidator</span><span class="p">(</span><span class="n">must_be_file</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">path_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/etc/hosts&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  /etc/hosts: valid=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">
</span></span><span class="line"><span class="ln">331</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">path_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/nonexistent&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  /nonexistent: valid=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="si">}</span><span class="s2">, errors=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">333</span><span class="cl">
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="c1"># Example 3: Composed validators</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">3. Composed Validators (AND)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">
</span></span><span class="line"><span class="ln">338</span><span class="cl">    <span class="n">username_validator</span> <span class="o">=</span> <span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">        <span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">        <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]*$&#34;</span><span class="p">,</span> <span class="s2">&#34;Must be lowercase alphanumeric&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="p">])</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="n">test_usernames</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;ab&#34;</span><span class="p">,</span> <span class="s2">&#34;valid_user&#34;</span><span class="p">,</span> <span class="s2">&#34;Invalid&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">25</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="k">for</span> <span class="n">username</span> <span class="ow">in</span> <span class="n">test_usernames</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  &#39;</span><span class="si">{</span><span class="n">username</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    - </span><span class="si">{</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="c1"># Example 4: Builder pattern</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">4. Builder Pattern&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">
</span></span><span class="line"><span class="ln">357</span><span class="cl">    <span class="n">email_validator</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">        <span class="n">validator_for</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">        <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">NotEmptyValidator</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">        <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">            <span class="sa">r</span><span class="s2">&#34;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">            <span class="s2">&#34;Invalid email format&#34;</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">        <span class="o">.</span><span class="n">build</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">
</span></span><span class="line"><span class="ln">367</span><span class="cl">    <span class="n">test_emails</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;invalid&#34;</span><span class="p">,</span> <span class="s2">&#34;test@example.com&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">    <span class="k">for</span> <span class="n">email</span> <span class="ow">in</span> <span class="n">test_emails</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">email</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  &#39;</span><span class="si">{</span><span class="n">email</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">
</span></span><span class="line"><span class="ln">373</span><span class="cl">    <span class="c1"># Example 5: List validator</span>
</span></span><span class="line"><span class="ln">374</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">5. List Validator&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">
</span></span><span class="line"><span class="ln">377</span><span class="cl">    <span class="n">tags_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">378</span><span class="cl">        <span class="n">element_validator</span><span class="o">=</span><span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">            <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">            <span class="n">LengthValidator</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">        <span class="p">]),</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">        <span class="n">max_length</span><span class="o">=</span><span class="mi">5</span>
</span></span><span class="line"><span class="ln">384</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">385</span><span class="cl">
</span></span><span class="line"><span class="ln">386</span><span class="cl">    <span class="n">test_tags</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">387</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">388</span><span class="cl">        <span class="p">[],</span>
</span></span><span class="line"><span class="ln">389</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;valid&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;also-valid&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">390</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">391</span><span class="cl">
</span></span><span class="line"><span class="ln">392</span><span class="cl">    <span class="k">for</span> <span class="n">tags</span> <span class="ow">in</span> <span class="n">test_tags</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">393</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">tags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">394</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">395</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">tags</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">396</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">397</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">398</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    - </span><span class="si">{</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">399</span><span class="cl">
</span></span><span class="line"><span class="ln">400</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== Demo Complete ===&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Create validators</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">not_empty</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">path_exists</span> <span class="o">=</span> <span class="n">PathExistsValidator</span><span class="p">(</span><span class="n">must_be_file</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># Validate string</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;hello&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;Value cannot be empty&#34;]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Validate path</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">path_exists</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/etc/hosts&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True (on Unix systems)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># Using ValidationResult in boolean context</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">if</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;test&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Validation passed!&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="組合驗證">組合驗證</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Username validator: non-empty, 3-20 chars, lowercase alphanumeric</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">username_validator</span> <span class="o">=</span> <span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]*$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># Test cases</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid_user&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;ab&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;Length 2 &lt; minimum 3&#34;]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># OR validation: accept either email or username format</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">login_validator</span> <span class="o">=</span> <span class="n">OrValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]{2,19}$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">])</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;user@example.com&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid_user&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>        <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;X&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>                  <span class="c1"># False</span></span></span></code></pre></div><h4 id="使用-builder-模式">使用 Builder 模式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Fluent API for building validators</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">password_validator</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">validator_for</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">NotEmptyValidator</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">128</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[A-Z].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain uppercase&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[a-z].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain lowercase&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[0-9].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain digit&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="o">.</span><span class="n">build</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">password_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;weakpass&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># [&#34;Must contain uppercase&#34;, &#34;Must contain digit&#34;]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">password_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;Strong1Password&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span></span></span></code></pre></div><h4 id="驗證列表元素">驗證列表元素</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Validate a list of tags</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">tag_validator</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">tags_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">element_validator</span><span class="o">=</span><span class="n">tag_validator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">max_length</span><span class="o">=</span><span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">,</span> <span class="s2">&#34;generic&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="s2">&#34;valid&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;also-valid&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;[1] Value cannot be empty&#34;]</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>具體類型</th>
          <th>泛型設計</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>重用性</td>
          <td>低：每個類型需要獨立實作</td>
          <td>高：一次實作，多處使用</td>
      </tr>
      <tr>
          <td>型別推導</td>
          <td>簡單：型別固定</td>
          <td>需要技巧：需正確標註 TypeVar</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>低：直覺易懂</td>
          <td>中：需理解 Generic、Protocol</td>
      </tr>
      <tr>
          <td>IDE 支援</td>
          <td>完整：型別明確</td>
          <td>需要正確標註才能獲得完整支援</td>
      </tr>
      <tr>
          <td>執行效能</td>
          <td>略佳：無泛型開銷</td>
          <td>略差：有 Protocol 檢查開銷</td>
      </tr>
      <tr>
          <td>錯誤訊息</td>
          <td>清晰：直接指出問題</td>
          <td>可能較模糊：泛型相關錯誤不易讀</td>
      </tr>
  </tbody>
</table>
<h3 id="何時選擇泛型設計">何時選擇泛型設計？</h3>
<p><strong>選擇泛型設計</strong>當：</p>
<ul>
<li>驗證邏輯會用於多種類型</li>
<li>需要組合多個驗證器</li>
<li>正在建立可重用的驗證函式庫</li>
<li>重視編譯時期的型別安全</li>
</ul>
<p><strong>選擇具體類型</strong>當：</p>
<ul>
<li>只驗證單一特定類型</li>
<li>驗證邏輯非常簡單</li>
<li>團隊對泛型不熟悉</li>
<li>效能是關鍵考量</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要驗證多種類型的函式庫</li>
<li>驗證邏輯需要組合重用</li>
<li>重視型別安全</li>
<li>API 設計需要表達「這個驗證器接受 T 類型」</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>只驗證單一類型</li>
<li>驗證邏輯很簡單（幾行 if-else 就能搞定）</li>
<li>團隊不熟悉泛型語法</li>
<li>程式碼不會被重用</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="1-實作-rangevalidatorint-和-lengthvalidatorstr">1. 實作 <code>RangeValidator[int]</code> 和 <code>LengthValidator[str]</code></h4>
<p>參考上面的 <code>RangeValidator</code> 實作，確保它可以正確驗證整數範圍。
測試案例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">age_validator</span> <span class="o">=</span> <span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">assert</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span></span></span></code></pre></div><h4 id="2-實作-emailvalidator">2. 實作 <code>EmailValidator</code></h4>
<p>建立一個 Email 驗證器，組合 <code>NotEmptyValidator</code> 和 <code>PatternValidator</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">email_validator</span> <span class="o">=</span> <span class="n">EmailValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">assert</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;user@example.com&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;invalid&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="1-實作-listvalidatort-驗證列表中的每個元素">1. 實作 <code>ListValidator[T]</code> 驗證列表中的每個元素</h4>
<p>建立一個泛型列表驗證器，可以：</p>
<ul>
<li>驗證列表長度</li>
<li>對每個元素執行子驗證器</li>
<li>收集所有錯誤，標註元素索引</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">int_list_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">element_validator</span><span class="o">=</span><span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">int_list_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># errors: [&#34;[2] Value -3 &lt; minimum 0&#34;]</span></span></span></code></pre></div><h4 id="2-實作-conditionalvalidatort">2. 實作 <code>ConditionalValidator[T]</code></h4>
<p>建立一個條件驗證器，只在條件成立時執行驗證：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Only validate age if it&#39;s provided (not None)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">optional_age_validator</span> <span class="o">=</span> <span class="n">ConditionalValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">condition</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">validator</span><span class="o">=</span><span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="1-實作-schemavalidator-驗證字典結構">1. 實作 <code>SchemaValidator</code> 驗證字典結構</h4>
<p>建立一個驗證器，可以驗證字典的結構和值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">user_schema</span> <span class="o">=</span> <span class="n">SchemaValidator</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">EmailValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">},</span> <span class="n">required_keys</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;email&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">user_schema</span><span class="o">.</span><span class="n">validate</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Alice&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;alice@example.com&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">assert</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">user_schema</span><span class="o">.</span><span class="n">validate</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="o">-</span><span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># errors: [&#34;name: Value cannot be empty&#34;, &#34;age: Value -5 &lt; minimum 0&#34;, &#34;Missing required key: email&#34;]</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>dict[str, Validator[Any]]</code> 作為 schema 類型</li>
<li>處理可選欄位和必填欄位</li>
<li>考慮巢狀 schema 的支援</li>
</ul>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/typing.html#typing.Generic">Python typing.Generic</a></li>
<li><a href="https://peps.python.org/pep-0484/">PEP 484 - Type Hints</a></li>
<li><a href="https://peps.python.org/pep-0544/">PEP 544 - Protocols: Structural subtyping</a></li>
<li><a href="https://mypy.readthedocs.io/en/stable/generics.html">mypy Generics</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></em>
<em>返回：<a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組 3.5：進階設計模式</a></em></p>
]]></content:encoded></item></channel></rss>