让模型产出数据,而不是导入脚本
需要给测试环境准备一批员工数据,让模型直接写一个导入脚本,跑完显示「成功 4 条,失败 4 条」,没报错,就算完成了。过了几天有人发现,备注字段都被截断在第一个逗号处,还有一条记录的状态是一个不存在的值
P。
用模型准备数据时,常见的做法是让它连数据带导入代码一起生成。每次需要新数据,都得到一段新的、未经审查的导入代码。更稳妥的分工是:模型只产出数据,格式由 Schema 约束;导入由一个固定的、审查过的程序完成。数据每次都可能不同,导入程序只写一次,它的正确性可以被测试一次、复用多次。
本文用一份模拟模型生成的 CSV,对比一段常见的生成脚本与一个固定导入程序,看错误分别在哪里被发现。实验见文末。
一、生成脚本的四个常见缺陷
for line in text.splitlines():
cols = line.split(",")
try:
db.execute("insert into emp values(?,?,?,?,?)", (int(cols[0]), cols[1], cols[2], cols[3], cols[4]))
ok += 1
except Exception:
errors += 1逗号切分 + 逐行提交 + 吞异常:报告成功 4 行、失败 4 行,脚本没有报错退出
库里实际的行:1|Ada|active|"负责 A;2|Bo|active|"第一行;3|Cy|on_leave|;4|Di|P|把 P 写成了状态- 按逗号切分:CSV 的字段可以用引号包住,里面可以有逗号和换行。
"负责 A,B 两个模块"被切成两段,备注只剩"负责 A;带换行的字段被拆成两行,第二行无法解析。 - 不跳过表头:表头行进入了导入,靠
int()失败被吞掉,才没有出事。 - 逐行提交:前面的行已经写入,后面的行失败了,库里是一份「部分成功」的数据,而且没有办法区分哪些是这次导入的。
- 吞掉异常:失败只变成了一个计数,原因没有记录,退出码是 0。调用它的流水线会认为导入成功。
非法状态 P 则说明了另一件事:数据库约束只覆盖了一部分规则(这张表没有为状态加检查约束),能写进去不代表数据是对的。
二、标准解析器与编码
CSV 不是「逗号分隔的文本」,它有引号、转义和换行规则,应当用标准库的解析器:
标准 csv 解析:共 6 条记录;第 1 条 note='负责 A,B 两个模块',第 2 条 note='第一行\n第二行'编码也要显式指定。模型或表格软件导出的 UTF-8 文件常带 BOM:
按 utf-8 解码后用 DictReader:第一列的列名是 'id',取 row['id'] 得到 None
按 utf-8-sig 解码:列名 ['id', 'name', 'dept', 'status', 'note']BOM 被当成了第一列名字的一部分,row['id'] 取不到值,后面的代码会以为 ID 缺失。用 utf-8-sig 解码可以兼容有无 BOM 两种情况。
三、Schema 校验:在写入之前列出所有问题
固定程序在写入数据库之前先做校验,把问题连同行号一次列出来:
Schema 校验:3 条合格,3 个问题:第 5 行 status='P' 不在 ['active', 'left', 'on_leave'];第 6 行 id=3 重复;第 7 行 dept='sales' 不存在校验的内容来自领域规则:枚举的取值范围、引用的部门是否存在、ID 是否唯一、必填字段。这份 Schema 同时也是给模型的输入:告诉它允许哪些值,比在提示里用自然语言描述更精确,也更容易验证。
校验结果先给人看,再决定怎么处理:修正数据重新生成,还是只导入合格的部分。
四、单事务导入:要么全部成功,要么什么都没有
不过滤、整批单事务导入:回滚(IntegrityError: UNIQUE constraint failed: emp.id),库里 0 行
只导入合格记录(导入前展示问题清单):已提交,库里 3 行整批数据放在一个事务里,任何一行失败就整体回滚,库里不会留下一半的数据。失败的原因被完整地报告出来,而不是变成一个计数。对于很大的数据量,可以分批提交,但每一批都要记录进度,失败后能从断点继续,并且能识别和清理某一批导入的数据。
五、分工
| 谁 | 做什么 | 怎样验证 |
|---|---|---|
| 模型 | 按 Schema 生成数据 | 每次都校验,不信任 |
| Schema | 定义取值范围、引用、唯一、必填 | 和领域规则一起维护、有版本 |
| 导入程序 | 解析、校验、单事务写入、报告 | 写一次,有测试,复用 |
| 人 | 审阅问题清单,决定修正或部分导入 | — |
这和模型写的 SQL 离放它去查库还差几道检查是同一个思路:模型的产出是需要校验的输入,执行副作用的代码由人审查并固定下来。生成的数据进入测试时,还要注意数据归属和清理,见测试数据归谁。
六、检查清单
- 模型产出数据,导入由固定程序完成;不每次生成新的导入代码。
- 用标准库解析 CSV,显式指定编码(
utf-8-sig)。 - 写入前按 Schema 校验,问题带行号一次列出。
- 整批在一个事务里,失败整体回滚,原因完整报告。
- 不吞异常;失败时非零退出。
配套实验
- codesphere-labs/engineering/data-not-code-import:Python 3.14.8、SQLite:逗号切分与吞异常的假成功、BOM、引号与换行字段、Schema 校验、单事务导入(验证记录)
参考资料