refactor(ai_client): apply Re-Raise Pattern 1 to 4 RETHROW sites (Phase 12)
Per styleguide §7.6 Pattern 1: 'catch + convert + raise as different type'
requires 'raise X from e' to preserve the original exception in the
traceback.
Sites updated:
Site 1 (L277 _load_credentials):
except FileNotFoundError as e:
raise FileNotFoundError(f'...') from e
Sites 2+3 (L878+L879 _default_send, nested in run_with_tool_loop):
if not res.ok:
raise res.errors[0].original from None
raise RuntimeError(...) from None
The exceptions come from a Result, not a local except; 'from None'
suppresses the implicit context.
Site 5 (L2061 _send inside _send_gemini_cli):
raise cast(Exception, send_result.errors[0].original) from None
Site 6 (L2742 _dashscope_call):
raise classify_dashscope_error(_dashscope_exception_from_response(resp)) from None
KNOWN LIMITATION: the audit script does not have a heuristic for
'raise X from e' / 'from None' (Pattern 1). The sites remain
INTERNAL_RETHROW in the audit. INTERNAL_RETHROW is 'suspicious but
not violation' (strict mode accepts). Adding a heuristic requires
Tier 1 approval per the conventions.
Audit: ai_client RETHROW 6 -> 5 (site 4 migrated separately; these
4 sites stay as INTERNAL_RETHROW by audit classification but follow
Pattern 1 by styleguide).
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""Phase 12 sites 1, 2+3, 5, 6: Pattern 1 (catch + raise from X) fixes.
|
||||
|
||||
Site 1 (_load_credentials):
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"...")
|
||||
Missing `from e`; per styleguide Pattern 1 requires `raise X from e`.
|
||||
|
||||
Sites 2+3 (_default_send):
|
||||
if not res.ok:
|
||||
if res.errors and res.errors[0].original:
|
||||
raise res.errors[0].original # site 2
|
||||
raise RuntimeError(res.errors[0].message ...) # site 3
|
||||
Missing `from None`; exception comes from a Result, not a local except.
|
||||
|
||||
Site 5 (_send inside _send_gemini_cli):
|
||||
if not send_result.ok:
|
||||
raise cast(Exception, send_result.errors[0].original)
|
||||
Missing `from None`.
|
||||
|
||||
Site 6 (_dashscope_call):
|
||||
if getattr(resp, "status_code", 200) != 200:
|
||||
raise classify_dashscope_error(...)
|
||||
Missing `from None`.
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
|
||||
def test_phase12_site1_load_credentials_has_from_e():
|
||||
import inspect
|
||||
import src.ai_client
|
||||
src_text = inspect.getsource(src.ai_client._load_credentials)
|
||||
assert "raise FileNotFoundError" in src_text
|
||||
# Per Pattern 1: catch + convert + raise must use 'from e'
|
||||
assert "from e" in src_text, \
|
||||
"_load_credentials raise must use 'from e' (Pattern 1)"
|
||||
|
||||
|
||||
def test_phase12_sites23_default_send_has_from_none():
|
||||
import inspect
|
||||
import src.ai_client
|
||||
# _default_send is a nested function inside run_with_tool_loop; get source from the parent
|
||||
src_text = inspect.getsource(src.ai_client.run_with_tool_loop)
|
||||
# The nested _default_send must have 'from None' on its raises
|
||||
assert "raise res.errors[0].original from None" in src_text, \
|
||||
"_default_send original-exception raise must use 'from None'"
|
||||
assert 'raise RuntimeError(res.errors[0].message if res.errors else "Unknown OpenAI error") from None' in src_text, \
|
||||
"_default_send RuntimeError raise must use 'from None'"
|
||||
|
||||
|
||||
def test_phase12_site5_send_cli_has_from_none():
|
||||
import inspect
|
||||
import src.ai_client
|
||||
src_text = inspect.getsource(src.ai_client._send_gemini_cli)
|
||||
assert "from None" in src_text, \
|
||||
"_send_gemini_cli inner _send raise must use 'from None'"
|
||||
|
||||
|
||||
def test_phase12_site6_dashscope_call_has_from_none():
|
||||
import inspect
|
||||
import src.ai_client
|
||||
src_text = inspect.getsource(src.ai_client._dashscope_call)
|
||||
assert "from None" in src_text, \
|
||||
"_dashscope_call raise must use 'from None'"
|
||||
Reference in New Issue
Block a user