refactor: 简化 __getattr__ 和测试 helper (ce-simplify-code)

core/exceptions.py:
- __getattr__ 用循环 + getattr 替代三处显式列举异常名
- 异常名只在 _LLM_EXCEPTION_NAMES 中定义一次,新增异常只改元组

test_memory_models.py:
- 提取 _assert_uuid_default/_assert_utc_default/_assert_unique_ids helper
- 消除 TestEpisodeModel 与 TestExperienceModel 间 3 个方法的复制粘贴

行为保持不变:52 tests passed,ruff clean,import 兼容性验证通过。
This commit is contained in:
Chiguyong 2026-07-06 23:20:23 +08:00
parent e0a22e0447
commit a4d3f227a6
2 changed files with 29 additions and 34 deletions

View File

@ -146,15 +146,10 @@ _LLM_EXCEPTION_NAMES = ("LLMError", "LLMProviderError", "ModelNotFoundError")
def __getattr__(name: str): # PEP 562
if name in _LLM_EXCEPTION_NAMES:
from agentkit.llm.exceptions import ( # noqa: PLC0415
LLMError,
LLMProviderError,
ModelNotFoundError,
)
import agentkit.llm.exceptions as _llm_exc # noqa: PLC0415
globals()["LLMError"] = LLMError
globals()["LLMProviderError"] = LLMProviderError
globals()["ModelNotFoundError"] = ModelNotFoundError
for n in _LLM_EXCEPTION_NAMES:
globals()[n] = getattr(_llm_exc, n)
return globals()[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@ -23,6 +23,26 @@ from sqlalchemy.dialects.postgresql import JSONB
from agentkit.memory.models import EpisodeModel, ExperienceModel
def _assert_uuid_default(id_default) -> None:
"""SQLAlchemy wraps Python-side default callables to receive an execution
context; pass None for callables that don't inspect it."""
val = id_default(None)
assert isinstance(val, str)
uuid.UUID(val) # raises ValueError if not a valid uuid4 string
def _assert_utc_default(ts_default) -> None:
ts = ts_default(None)
assert isinstance(ts, datetime)
assert ts.tzinfo is not None
assert ts.utcoffset() == timezone.utc.utcoffset(None)
def _assert_unique_ids(id_default, n: int = 100) -> None:
ids = {id_default(None) for _ in range(n)}
assert len(ids) == n
class TestEpisodeModel:
def test_tablename(self):
assert EpisodeModel.__tablename__ == "episodic_memories"
@ -70,24 +90,13 @@ class TestEpisodeModel:
assert cols.quality_score.default.arg == 0.5
def test_callable_default_id_is_uuid_string(self):
# SQLAlchemy wraps Python-side default callables to receive an execution
# context; pass None for callables that don't inspect it.
id_default = EpisodeModel.__table__.c.id.default.arg
val = id_default(None)
assert isinstance(val, str)
uuid.UUID(val) # raises ValueError if not a valid uuid4 string
_assert_uuid_default(EpisodeModel.__table__.c.id.default.arg)
def test_callable_default_created_at_is_utc(self):
ts_default = EpisodeModel.__table__.c.created_at.default.arg
ts = ts_default(None)
assert isinstance(ts, datetime)
assert ts.tzinfo is not None
assert ts.utcoffset() == timezone.utc.utcoffset(None)
_assert_utc_default(EpisodeModel.__table__.c.created_at.default.arg)
def test_default_id_is_unique_across_calls(self):
id_default = EpisodeModel.__table__.c.id.default.arg
ids = {id_default(None) for _ in range(100)}
assert len(ids) == 100
_assert_unique_ids(EpisodeModel.__table__.c.id.default.arg)
def test_construct_with_explicit_values(self):
ts = datetime(2025, 1, 2, 3, 4, 5, tzinfo=timezone.utc)
@ -202,22 +211,13 @@ class TestExperienceModel:
assert cols.optimization_tips.default.arg(None) == []
def test_callable_default_id_is_uuid_string(self):
id_default = ExperienceModel.__table__.c.id.default.arg
val = id_default(None)
assert isinstance(val, str)
uuid.UUID(val)
_assert_uuid_default(ExperienceModel.__table__.c.id.default.arg)
def test_callable_default_created_at_is_utc(self):
ts_default = ExperienceModel.__table__.c.created_at.default.arg
ts = ts_default(None)
assert isinstance(ts, datetime)
assert ts.tzinfo is not None
assert ts.utcoffset() == timezone.utc.utcoffset(None)
_assert_utc_default(ExperienceModel.__table__.c.created_at.default.arg)
def test_default_id_is_unique_across_calls(self):
id_default = ExperienceModel.__table__.c.id.default.arg
ids = {id_default(None) for _ in range(100)}
assert len(ids) == 100
_assert_unique_ids(ExperienceModel.__table__.c.id.default.arg)
def test_construct_with_explicit_values(self):
ts = datetime(2025, 6, 7, 8, 9, 10, tzinfo=timezone.utc)