spotlight,一个超酷的 Python 库!
接口没报 500,日志也干干净净,结果库里躺了一批脏数据。
手机号是空的,金额传了字符串,邮箱字段塞了个 abc,最离谱的是 age=-3 这种东西也进来了。 这种问题我一般不先翻数据库,也不先怀疑 ORM。先看入口参数校验。十有八九,入口太松。
Python 项目里写参数校验,很多人一开始都这么写:
defcreate_user(payload):
if"email"notin payload:
return {"email": "不能为空"}if"age"notin payload:
return {"age": "不能为空"}
ifnot isinstance(payload["age"], int):
return {"age": "必须是整数"}
if payload["age"] < 1:
return {"age": "不能小于 1"}
if len(payload.get("password", "")) < 8:
return {"password": "密码太短"}
# 后面才是真业务
return save_user(payload)
这代码第一眼看着没啥毛病,线上跑一阵就难受了。
字段一多,接口里全是 if。 业务没几行,校验写了半屏。 更麻烦的是,不同接口各写各的,今天密码最少 8 位,明天另一个地方写成 6 位,后面排查用户注册问题,能把人看烦。
这里可以看下 spotlight。
注意,不是 macOS 的 Spotlight,也不是推荐系统那个老项目。这里说的是 PyPI 上这个 spotlight,定位很直接:给 Python 做数据校验,写法受 Laravel 启发;PyPI 页面上当前能看到的版本是 3.4.0,发布时间是 2026 年 5 月 4 日,安装也就是一行 pip install spotlight。
装一下:
pip install spotlight
我更喜欢把规则单独拎出去,不塞在业务函数里。
from spotlight import Validatoruser_rules = {
"email": "required|email",
"age": "required|integer|min:1|max:120",
"nickname": "required|string|min:2|max:30",
"password": "required|string|min:8|max:64",
}
defcheck_user_payload(payload: dict) -> dict:
validator = Validator()
errors = validator.validate(payload, user_rules)
if errors:
return {
"ok": False,
"errors": errors,
}
return {
"ok": True,
"data": payload,
}
bad_user = {
"email": "test",
"age": -3,
"nickname": "A",
"password": "123",
}
print(check_user_payload(bad_user))
我喜欢这种地方。
规则直接贴在字段旁边,一眼能扫出来这个接口到底要什么。required|email、integer|min:1|max:120,这种写法不绕。 不用为了一个入参校验搞一堆类,也不用把接口写成表单验证框架。
PyPI 的示例里也提到,validate 返回的是错误字典;规则既可以写成 | 拼起来的字符串,也可以写成列表。
比如有些字段规则太长,我就不爱写成一坨字符串:
order_rules = {
"order_no": ["required", "string", "min:10", "max:32"],
"user_id": ["required", "integer"],
"amount": ["required", "numeric", "min:0.01"],
"pay_channel": ["required", "string"],
}
接口里就干净多了。
from spotlight import Validatorvalidator = Validator()
defsubmit_order(request_body: dict):
errors = validator.validate(request_body, order_rules)
if errors:
print("[order.check.failed]", errors)
return {
"code": 400,
"message": "订单参数不对",
"detail": errors,
}
order = {
"order_no": request_body["order_no"].strip(),
"user_id": request_body["user_id"],
"amount": request_body["amount"],
"pay_channel": request_body["pay_channel"],
}
return {
"code": 0,
"message": "ok",
"data": order,
}
这里我会顺手打日志。
别小看这一行:
print("[order.check.failed]", errors)
线上有时候不是代码坏了,是前端发错了,是第三方回调字段变了,是测试环境文档没同步。 没有这行日志,排查时只能抓请求体。 有这行日志,至少知道是哪个字段被挡住了。
不过 spotlight 这种库,我不会把它吹成万能。
它适合挡入口脏数据。 比如接口参数、配置文件、Excel 导入前的字段检查、脚本批量处理前的数据清洗。 这些地方最烦的不是复杂算法,而是脏数据一路往后传,最后在数据库、MQ、报表任务里炸。
比如批量导入用户,我一般会这么写:
from spotlight import Validatorrow_rules = {
"email": "required|email",
"dept_id": "required|integer",
"name": "required|string|min:2|max:20",
}
defscan_rows(rows):
validator = Validator()
bad_rows = []
for line_no, row in enumerate(rows, start=2):
errors = validator.validate(row, row_rules)
if errors:
bad_rows.append({
"line": line_no,
"row": row,
"errors": errors,
})
return bad_rows
rows = [
{"email": "[email protected]", "dept_id": 12, "name": "老周"},
{"email": "wrong-email", "dept_id": "研发部", "name": "x"},
]
print(scan_rows(rows))
Excel 导入这类活,最怕“先入库再说”。 等脏数据进库,后面清理成本比校验高多了。 我宁愿第一步就把第几行、哪个字段、什么错误打出来,让业务自己改表格。
spotlight 让我觉得舒服的点,不是它多复杂。恰恰相反,是它不复杂。
它没有逼你接受一整套框架。 你给它一个 dict,再给它一份 rules,它吐一份 errors。 这种库在小项目、脚本、后台接口里很好塞进去,不抢戏。
真要注意的地方也有。
规则别散着写。 不要这个文件一份 user_rules,另一个文件又复制一份。后面字段变了,一定漏改。 我一般会放到单独的 validators.py 里,接口只负责调用。
还有,校验挡的是格式,不是业务真相。
amount 大于 0,不代表账户余额够。user_id 是整数,不代表用户存在。email 格式对,不代表邮箱真能收到邮件。 这些别全塞到 spotlight 里,入口校验和业务校验分开,不然后面会写成一锅粥。
这个库最适合干一件事:把脏数据挡在门口。
门口干净了,后面的业务代码才像业务代码。 不然一个注册接口,从头到尾全是 if payload.get(...),写着写着就不像 Python 了。