Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🛡️ Valkit · Python 运行时数据校验工具库

🌐 语言 / Language:简体中文 | English繁體中文

Python Dependencies License Tests

零依赖、Pythonic、可链式调用的运行时 Schema 校验工具库:把不可信的 JSON、表单、环境变量与消息载荷,一次性校验、归一化成你想要的数据。


🎉 项目介绍

它是什么

Valkit 是一个纯 Python 标准库实现的运行时数据校验工具库(runtime validation toolkit)。你用一套流式(fluent)API 声明数据形状,Valkit 负责:

  • ✅ 在运行时拦截非法输入(请求体、配置文件、消息队列消息、外部 API 响应……)
  • 🔧 归一化(normalize):去空白、大小写转换、字符串转数字/布尔、默认值填充
  • 📍 一次性返回带精确路径的结构化错误清单,而不是只报第一个错
  • 📤 一键导出 JSON Schema 2020-12,方便与前端 / 其他语言团队共享契约
  • 🔌 附带环境变量解析器与命令行工具,覆盖后端开发最常见的三个入口

解决什么痛点

痛点 手写 if/else 重型建模库 Valkit
嵌套结构报错定位 自己拼路径 错误结构偏重 内置 users[0].name 式路径
部署体积 无依赖但难维护 依赖链长、冷启动慢 零运行时依赖,纯标准库
表单/环境变量字符串转类型 到处 int(...) try/except 需额外胶水代码 coerce=True 一键宽松转换
一次收集全部错误 自己实现 支持 默认收集全部问题
复用与派生 Schema 重复代码 继承体系复杂 链式不可变,pick/omit/partial 秒派生

✨ 自研差异化亮点

  1. 零运行时依赖:整个库只依赖 Python 标准库,pip install 后没有任何传递依赖,适合 Serverless / 边缘 / 内网环境。
  2. 不可变链式 Schema:每个链式方法都返回新对象,基础 Schema 可安全复用、派生,不会被下游调用悄悄改坏。
  3. 双模式错误处理parse() 直接抛异常;safe_parse() 返回带 ok/value/error 的结果对象,业务代码各取所需。
  4. 宽松输入强类型输出coerce 模式支持 "42"→42"true"→True2.0→2 等真实世界脏数据转换。
  5. 内置环境变量加载器v.env(prefix="APP_") 声明式读取配置,缺失、越界、格式错误一次性聚合抛出。
  6. JSON Schema 双向打通to_json_schema() 把 Python Schema 导出为标准 draft 2020-12 文档。
  7. 自带 CLIvalkit validate schema.py:User payload.json 可直接在 CI 流水线里校验 JSON 文件。

💡 灵感来源:开发者体验参考了 TypeScript 生态的 zod「声明即校验、类型可推导」的产品思路;但 Valkit 没有复制其任何源码,所有架构、API 细节与实现均为面向 Python 语言习惯独立自研,并额外补齐了环境变量解析、CLI、JSON Schema 导出等差异化能力。


✨ 核心特性

  • 🧩 完整的基础类型str / int / float / number / bool / date / datetime / literal / enum / none / any / unknown / never
  • 📦 丰富的容器类型object / array / tuple / record / union / discriminated_union / intersection
  • 📏 细粒度约束:长度、区间、正则、前后缀、multiple_of、唯一性、枚举、精确字面量
  • 📮 内置格式校验(零依赖)email / url / uuid / ipv4 / ipv6 / iso_date / iso_datetime / semver / slug
  • 🧼 归一化管线trim / lowercase / uppercase / transform / pipe
  • 🎯 自定义规则refine(fn, message, code),抛 ValueError 也会被转成结构化问题
  • 🧱 对象派生pick / omit / partial / required / extend / merge,以及 strip / strict / passthrough 三种未知键策略
  • 🧪 判别联合(Discriminated Union):消息 / 事件场景按字面量标签快速分发,报错更精准
  • 🧾 结构化错误:每个问题都带 path / code / message,可直接序列化为 JSON
  • 🔄 宽松强制转换parse(data, coerce=True) 处理表单、query string、环境变量等字符串输入
  • 📄 JSON Schema 导出:draft 2020-12,含 required / additionalProperties / discriminator
  • 🌍 环境变量解析:带前缀、默认值、必填聚合、类型强转
  • 💻 命令行工具:CI 友好,退出码 0=通过 / 1=校验失败 / 2=用法错误
  • ♻️ 完全跨平台:纯 Python,Windows / macOS / Linux 行为一致;支持 Python 3.8 ~ 3.13

🚀 快速开始

环境要求

  • 🐍 Python 3.8 或更高版本
  • 📦 运行时依赖:(开发/测试需要 pytest,属于可选依赖)

安装

从源码构建 wheel 后安装(推荐,离线可用):

# 克隆后本地构建
git clone https://github.com/gitstq/valkit.git
cd valkit
python3 -m pip install build
python3 -m build
python3 -m pip install dist/valkit-1.0.0-py3-none-any.whl

或以可编辑模式参与开发:

python3 -m pip install -e ".[dev]"

30 秒示例

from valkit import v, ValidationError

Signup = v.object({
    "username": v.str().min_len(3).max_len(20).slug(),
    "email": v.str().email(),
    "age": v.int().gte(18).lte(120).optional(),
    "role": v.enum(["admin", "member"]).default("member"),
})

# parse():失败抛 ValidationError,成功返回归一化后的数据
user = Signup.parse({
    "username": "Ada-Lovelace",
    "email": "ada@example.com",
    "age": "36",          # 表单里的字符串
}, coerce=True)
print(user)
# {'username': 'Ada-Lovelace', 'email': 'ada@example.com', 'age': 36, 'role': 'member'}

# safe_parse():不抛异常,自行分支
result = Signup.safe_parse({"username": "x", "email": "bad"})
if not result:
    for issue in result.error.issues:
        print(issue.path_str, issue.code, issue.message)
    # username min_length string must be at least 3 characters, got 1
    # email format invalid email format

运行仓库内示例:

python examples/quickstart.py
python examples/env_demo.py

📖 详细使用指南

1️⃣ 基础类型与约束

from valkit import v

name   = v.str().trim().min_len(1).max_len(50)
score  = v.int().gte(0).lte(100)
ratio  = v.number().finite()                       # 拒绝 NaN / inf
flag   = v.bool()
day    = v.date()                                  # 接受 date 或 "YYYY-MM-DD"
moment = v.datetime()                              # 接受 datetime 或 ISO 8601 字符串
level  = v.enum(["low", "high"])                   # 也支持 Python Enum 类
kind   = v.literal("csv")                          # 精确字面量(区分 1 与 True)
ip     = v.str().ipv4()
ver    = v.str().semver()

字符串约束min_len / max_len / length / nonempty / regex / starts_with / ends_with / includes / trim / lowercase / uppercase,以及格式方法 email / url / uuid / ipv4 / ipv6 / semver / slug / format(name)

数字约束gt / gte / lt / lte / positive / nonnegative / negative / multiple_of / finite

2️⃣ 对象:未知键策略与 Schema 派生

Base = v.object({"id": v.int(), "name": v.str(), "note": v.str().optional()})

Base.parse({"id": 1, "name": "x", "extra": 9})
# 默认 strip:{'id': 1, 'name': 'x'} —— extra 被丢弃

Base.strict()      # 未知键直接报错(additionalProperties=False)
Base.passthrough() # 保留未知键

Base.pick(["id"])             # -> 只含 id
Base.omit(["note"])           # -> 去掉 note
Base.partial()                # -> 所有字段变可选
Base.partial(["name"])        # -> 仅 name 变可选
Base.extend({"age": v.int()}) # -> 追加字段
Base.merge(other_object)      # -> 合并两个对象 Schema

3️⃣ 数组 / 元组 / 记录

v.array(v.int()).min_len(1).max_len(10).nonempty().unique()
v.tuple([v.str(), v.int(), v.bool()])           # 定长、按位置校验
v.record(v.str().slug(), v.number().gte(0))     # 键、值分别约束

4️⃣ 联合类型与判别联合

# 普通联合:依次尝试,命中任一即可
id_schema = v.union([v.int(), v.str().uuid()])
id_schema = v.int() | v.str()                   # 等价写法

# 判别联合:按 type 字段分发,事件/消息建模首选
Event = v.discriminated_union("type", [
    v.object({"type": v.literal("created"), "name": v.str()}),
    v.object({"type": v.literal("deleted"), "id": v.int()}),
])

5️⃣ optional / nullable / default 的区别(重要)

修饰 允许键缺失 允许值为 None 说明
.optional() 对应 TypeScript 的 ?:
.nullable() 值可以显式为 None
.nullish() 两者都允许
.default(x) ✅(自动) 缺失时填入 xx 为函数时惰性调用
Settings = v.object({
    "host": v.str().default("127.0.0.1"),
    "port": v.int().default(lambda: 8080),  # 工厂函数,每次 parse 独立生成
    "note": v.str().nullish(),
})
Settings.parse({})  # {'host': '127.0.0.1', 'port': 8080}

6️⃣ refine / transform / pipe

even = v.int().refine(lambda x: x % 2 == 0, message="must be even", code="even")

cleaned = (
    v.str()
    .transform(str.strip)
    .transform(str.lower)
    .pipe(v.str().min_len(2))   # 归一化后再交给下一个 Schema
)
cleaned.parse("  HELLO ")  # 'hello'

7️⃣ 结构化错误模型

result = schema.safe_parse(bad_data)
for issue in result.error.issues:
    issue.path      # ('users', 0, 'name') 元组路径
    issue.path_str  # 'users[0].name'
    issue.code      # 'min_length' / 'format' / 'required' ...
    issue.message   # 人类可读信息
result.error.to_dicts()   # 直接 json.dumps 输出给前端 / 日志

8️⃣ 导出 JSON Schema 2020-12

from valkit import to_json_schema
import json

print(json.dumps(to_json_schema(Signup, title="Signup"), indent=2, ensure_ascii=False))

9️⃣ 环境变量解析

env = v.env(prefix="APP_")          # 默认读取 os.environ
env.str("HOST", default="0.0.0.0")
env.int("PORT", default=8080, ge=1, le=65535)
env.bool("DEBUG", default=False)
env.enum("LOG_LEVEL", ["debug", "info", "error"], default="info")
env.url("PUBLIC_API", required=True)
config = env.load()                 # 缺失必填 / 类型错误会一次性聚合抛出

🔟 命令行工具(CI 友好)

# 校验 JSON 文件(--coerce 开启宽松转换)
valkit validate examples/cli_schemas.py:Signup examples/signup_ok.json --coerce

# 导出 JSON Schema
valkit jsonschema examples/cli_schemas.py:Signup -o signup.schema.json

# 不安装也可用模块方式运行
python -m valkit validate ./schemas.py:User ./payload.json

退出码:0 校验通过;1 数据校验失败(问题以 JSON 打到 stderr);2 文件/参数/Schema 加载错误。

🧑‍💻 典型使用场景

  • Web 框架入参校验:在 Flask / FastAPI / Django 的视图边界调用 schema.parse(request.get_json()),非法请求统一返回结构化错误。
  • 配置与密钥管理:用 v.env() 取代散落各处的 os.getenv + int(),启动即校验,杜绝线上配置事故。
  • 消息队列 / Webhook 事件:用 discriminated_union 建模不同 type 的事件载荷。
  • 外部 API 响应防御:第三方数据结构不可信,先过 Schema 再进业务逻辑。
  • CI 数据契约检查:用 CLI 在流水线中校验示例 JSON、生成 JSON Schema 产物。

🖼️ 演示截图 / GIF 占位:终端演示录屏请放置于 docs/demo/ 目录(如 docs/demo/quickstart.svg),文档此处预留引用位置:![demo](docs/demo/quickstart.svg)


💡 设计思路与迭代规划

设计理念

  1. 库不是框架:不绑定任何 Web / ORM / 序列化框架,任何 Python 程序都能在边界处随手使用。
  2. 默认收集全部错误:真实用户宁可一次改完所有问题,也不想反复提交、每次只看到一个错。
  3. 归一化与校验同管线trim、大小写、强转、默认值、自定义 transform 按声明顺序执行,输出即可直接入库。
  4. Schema 不可变:链式方法返回副本,基础 Schema 可以在模块顶层定义、到处复用、安全派生。
  5. 零依赖是特性不是约束:格式校验(邮箱/URL/IP/UUID/日期/SemVer)全部基于标准库实现,可在内网与离线环境自由分发。

技术选型理由

  • 纯标准库 + src 布局:零传递依赖、类型清晰、打包简单(py3-none-any 通用 wheel)。
  • PEP 621 pyproject.toml:现代 Python 打包标准,setuptools 后端保证最大兼容性。
  • pytest 参数化测试:70 条用例覆盖类型、容器、管线、环境变量、JSON Schema 导出与 CLI。
  • PEP 561 py.typed:内置类型标记,方便接入 mypy / pyright 的工程。

🗺️ 迭代路线图

  • v1.1:错误信息 i18n(内置中英文模板,可自定义文案表)
  • v1.1:更多格式(base64 / json-web-token / country-code / phone)与异步 refine
  • v1.2:从 JSON Schema / OpenAPI Schema 反向生成 Valkit Schema
  • v1.2:性能基准套件与循环引用安全的递归 Schema
  • v1.3:发布到 PyPI,提供 pip install valkit 一键安装
  • v1.3:mypy / pyright 类型推导插件(parse 返回精确类型)

🏘️ 社区贡献方向

欢迎认领路线图条目,也欢迎补充格式校验器、边界测试用例、更多语言的文档翻译(日语、韩语、西班牙语等)。


📦 打包与部署指南

Valkit 属于工具库 / 组件库,以源码与 wheel 形式分发,无需可执行 Release 产物

本地一键构建(跨平台)

# Linux / macOS
bash scripts/build.sh

# Windows (cmd / PowerShell)
scripts\build.bat

脚本会依次执行:① 全量 pytest → ② 清理旧产物 → ③ 生成 dist/valkit-x.y.z.tar.gz(sdist)与 dist/valkit-x.y.z-py3-none-any.whl(wheel)。

兼容环境

范围
操作系统 Windows / macOS / Linux(纯 Python,无原生扩展)
Python 3.8 / 3.9 / 3.10 / 3.11 / 3.12 / 3.13
运行时依赖
开发依赖 pytest>=7build(仅构建时)
产物格式 sdist .tar.gz + universal wheel .whl

在自有项目中集成

python3 -m pip install /path/to/valkit-1.0.0-py3-none-any.whl

或直接把 src/valkit 目录复制进项目(零依赖,单包自洽)。CI 中可使用 CLI 对样例数据做契约校验,详见上文「命令行工具」。


🤝 贡献指南

我们欢迎 Issue、PR 与文档翻译!提交前请先阅读完整的 CONTRIBUTING.md。简要规范:

  1. 🔀 Fork 后新建 feat/xxxfix/xxx 分支;
  2. 🧪 新功能必须附带测试,保证 pytest 全绿;
  3. 🧱 不得引入运行时第三方依赖(开发依赖除外);
  4. 📝 遵循 Angular 提交规范:
    • feat: 新增功能
    • fix: 修复问题
    • docs: 文档更新
    • refactor: 代码重构
    • test: 测试相关
    • chore: 构建/工程配置
  5. 💬 Issue 请附最小复现、Python 版本与期望/实际行为。

❓ 常见问题

Q:和 Pydantic 有什么区别? A:Pydantic 是优秀的数据建模框架,功能全面但依赖链与包体更大;Valkit 定位是零依赖、边界处随手用的轻量校验器,不做 ORM、不绑定模型类,适合 Serverless、插件、SDK 与内网受限环境。

Q:optional() 为什么不接受显式 None A:这是刻意区分「键缺失」与「值为空」两种语义;想两者都接受请用 .nullish()

Q:coerce 会不会悄悄吞掉脏数据? A:不会。强转只覆盖明确安全的场景(数字字符串、常见布尔词、整数值浮点),转换失败仍然报类型错误。

Q:可以校验 dataclass / TypedDict 吗? A:Valkit 只校验数据本身,不关心容器类型;把对象转成 dict 后交给 Object Schema 即可。


📄 开源协议

本项目基于 MIT License 开源,允许自由使用、修改、分发与商用,保留版权声明即可。

About

🛡️ Zero-dependency Pythonic runtime schema validation toolkit — fluent API, structured errors, coercion, JSON Schema export, env loader & CLI. 零依赖 Python 运行时校验库

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages