fix(exchange,case_validator),refactor(),feat(model): 解决 Pydantic 模型初始化与变量占位符的类型冲突,优化变量替换逻辑,重构 CaseInfo 模型并引入延迟校验机制

- 引入 SmartInt 和 SmartDict 类型,支持 YAML 占位符与业务类型的自动转换。
- 优化 CaseInfo 互斥校验逻辑,确保 request 与 api_action 二选一。
- 统一使用 Pydantic V2 的 model_config 规范。
- 将变量替换时机提前至模型实例化之前,支持占位符在校验前完成真实值注入,
保证了 int/bool 等字段的类型转换正确性。
- 优化断言渲染时机,支持响应提取值关联。
This commit is contained in:
2026-03-11 10:29:16 +08:00
parent 69a96a0060
commit 293b5160fe
39 changed files with 1359 additions and 1031 deletions

View File

@@ -10,7 +10,6 @@
@desc: 读取和保存yaml文件
"""
import logging
from dataclasses import dataclass, asdict, field
from pathlib import Path
import yaml
@@ -18,20 +17,31 @@ logger = logging.getLogger(__name__)
class YamlFile(dict):
def __init__(self, path):
super().__init__() # 初始化父类 dict
self.path = Path(path)
self.load() # 链式初始化加载
def __init__(self, path=None, data=None):
super().__init__()
self.path = Path(path) if path else None
if data:
self.update(data)
elif self.path:
if self.path.is_dir():
raise IsADirectoryError(f"The path {self.path} is a directory, not a file.")
self.load()
def load(self):
if self.path.exists():
if not self.path:
logger.warning("No path specified for YamlFile, cannot load.")
return self
if self.path.exists() and self.path.is_file():
with open(self.path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f) or {} # 加载数据,空文件返回空字典
self.clear() # 清空当前实例
self.update(data) # 更新字典内容
loaded_data = yaml.safe_load(f) or {}
self.clear()
self.update(loaded_data)
else:
logger.warning(f"File {self.path} not found, initialized empty.")
return self # 链式调用
logger.warning(f"File not found at {self.path}, YamlFile initialized as empty.")
self.clear()
return self
def to_yaml(self) -> str:
return yaml.safe_dump(
@@ -40,29 +50,49 @@ class YamlFile(dict):
sort_keys=False
)
@classmethod
def by_yaml(cls, yaml_str):
data = yaml.safe_load(yaml_str) or {}
return cls({**data}) # 通过类方法创建实例
return cls(data=data)
def save(self):
if not self.path:
raise ValueError("Cannot save YamlFile instance without a specified path.")
# 确保父目录存在
self.path.parent.mkdir(parents=True, exist_ok=True)
with open(self.path, "w", encoding="utf-8") as f:
yaml.safe_dump(
dict(self), # 直接 dump 实例本身(已继承 dict
dict(self),
stream=f,
allow_unicode=True,
sort_keys=False
)
return self # 链式调用
return self
if __name__ == '__main__':
from commons.models import CaseInfo
from core.models import CaseInfo
from core.settings import TEST_CASE_DIR
yaml_path = r'E:\PyP\InterfaceAutoTest\TestCases\test_1_user.yaml'
yaml_file = YamlFile(yaml_path)
# yaml_file.load()
case_info = CaseInfo(**yaml_file)
yaml_file["title"] = "查询用户信息"
yaml_file.save()
# 1. 创建一个用于测试的临时yaml文件
dummy_path = TEST_CASE_DIR / "test_model_demo.yaml"
dummy_data = {
"title": "Get user info",
"request": {"method": "GET", "url": "/users/1"},
"validate": [{"equals": ["status_code", 200]}]
}
YamlFile(path=dummy_path, data=dummy_data).save()
print(f"--- 已创建临时测试文件: {dummy_path}")
# 2. 加载文件并使用Pydantic模型进行校验
yaml_case = YamlFile(dummy_path)
print("\n--- 已加载YAML内容 ---\n", yaml_case.to_yaml())
case_model = CaseInfo(**yaml_case)
print("\n--- Pydantic模型校验成功 ---")
print(case_model.model_dump_json(indent=2, by_alias=True))
# 3. 清理临时文件
dummy_path.unlink()
print(f"\n--- 已清理临时文件: {dummy_path}")