Python数据验证库
项目描述
仅限贡献
这是什么意思? 我没有时间自己修复问题。唯一添加修复或新功能的方式是人们提交PR。
当前状态: Voluptuous在功能上基本稳定。有一段时间没有添加新功能的需求了,但是有一些应该修复的错误。
为什么? 我个人不再使用 Voluptuous(事实上,我不再经常编写 Python 代码)。与其让项目陷入人们提交问题并疑惑为什么它们没有得到处理的僵局,我相信这个通知将更清楚地设定期望。
Voluptuous 是一个 Python 数据验证库
尽管名字如此,Voluptuous 也是一个 Python 数据验证库。它主要用于验证以 JSON、YAML 等形式进入 Python 的数据。
它有三个目标
- 简单性。
- 支持复杂的数据结构。
- 提供有用的错误信息。
联系方式
Voluptuous 现在有一个邮件列表!发送邮件至 voluptuous@librelist.com 订阅。后续会有说明。
要提交错误,请在 GitHub 上创建一个 新问题,并提供一个简短示例,说明如何复制该问题。
文档
文档在此处提供 这里。
对文档的贡献
文档使用 Sphinx
构建。您可以通过以下方式安装它:
pip install -r requirements.txt
要从头开始构建 sphinx-apidoc
,您需要将 PYTHONPATH 设置为 voluptuous/voluptuous
仓库。
文档在此处提供 这里。
变更日志
请参阅 CHANGELOG.md。
为什么选择 Voluptuous 而不是其他验证库?
验证器是简单的可调用对象:不需要继承任何东西,只需使用一个函数即可。
错误是简单的异常:验证器只需 raise Invalid(msg)
并期望用户获得有用的消息。
模式是基本的 Python 数据结构:如果您的数据应该是整数键到字符串的字典?{int: str}
做您期望的事情。整数、浮点数或字符串的列表?[int, float, str]
。
从一开始就是为了验证不仅仅是表单而设计的:嵌套数据结构与其他类型以相同的方式处理。需要字典列表吗?[{}]
。
一致性:模式中的类型被检查为类型。值被比较为值。可调用对象被调用以进行验证。简单。
给我一个例子
Twitter 的 用户搜索 API 接受查询 URL,如下所示:
$ curl 'https://api.twitter.com/1.1/users/search.json?q=python&per_page=20&page=1'
为了验证这一点,我们可能会使用以下模式:
>>> from voluptuous import Schema
>>> schema = Schema({
... 'q': str,
... 'per_page': int,
... 'page': int,
... })
该模式简洁地概述了 API 所需的数据,并且可以很好地工作。但它有几个问题。首先,它没有完全表达 API 的约束。根据 API,per_page
应该限制在最多 20,默认为 5。为了更准确地描述 API 的语义,我们的模式需要更彻底地定义。
>>> from voluptuous import Required, All, Length, Range
>>> schema = Schema({
... Required('q'): All(str, Length(min=1)),
... Required('per_page', default=5): All(int, Range(min=1, max=20)),
... 'page': All(int, Range(min=0)),
... })
该模式完全强制执行 Twitter 文档中定义的接口,并在完整性方面略有扩展。
"q" 是必需的
>>> from voluptuous import MultipleInvalid, Invalid
>>> try:
... schema({})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data['q']"
True
...必须是一个字符串
>>> try:
... schema({'q': 123})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "expected str for dictionary value @ data['q']"
True
...并且必须至少有一个字符的长度
>>> try:
... schema({'q': ''})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
True
>>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
True
"per_page" 是一个不超过 20 的正整数
>>> try:
... schema({'q': '#topic', 'per_page': 900})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
True
>>> try:
... schema({'q': '#topic', 'per_page': -10})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
True
"page" 是一个大于等于 0 的整数
>>> try:
... schema({'q': '#topic', 'per_page': 'one'})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc)
"expected int for dictionary value @ data['per_page']"
>>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
True
定义模式
模式是由字典、列表、标量和一个 验证器 组成的嵌套数据结构。输入模式中的每个节点都与输入数据中相应节点的模式匹配。
字面量
模式中的字面量使用正常相等性检查进行匹配
>>> schema = Schema(1)
>>> schema(1)
1
>>> schema = Schema('a string')
>>> schema('a string')
'a string'
类型
模式中的类型通过检查相应的值是否是该类型的实例进行匹配
>>> schema = Schema(int)
>>> schema(1)
1
>>> try:
... schema('one')
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "expected int"
True
URL
模式中的 URL 通过使用 urlparse
库进行匹配
>>> from voluptuous import Url
>>> schema = Schema(Url())
>>> schema('http://w3.org')
'http://w3.org'
>>> try:
... schema('one')
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "expected a URL"
True
列表
模式中的列表被视为一组有效值。模式列表中的每个元素都与输入数据中的每个值进行比较
>>> schema = Schema([1, 'a', 'string'])
>>> schema([1])
[1]
>>> schema([1, 1, 1])
[1, 1, 1]
>>> schema(['a', 1, 'string', 1, 'string'])
['a', 1, 'string', 1, 'string']
然而,空列表([]
)被当作原样处理。如果您想指定一个可以包含任何内容的列表,请将其指定为list
>>> schema = Schema([])
>>> try:
... schema([1])
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "not a valid value @ data[1]"
True
>>> schema([])
[]
>>> schema = Schema(list)
>>> schema([])
[]
>>> schema([1, 2])
[1, 2]
集合和冻结集合
集合和冻结集合被视为一组有效值。模式集合中的每个元素都与输入数据中的每个值进行比较
>>> schema = Schema({42})
>>> schema({42}) == {42}
True
>>> try:
... schema({43})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "invalid value in set"
True
>>> schema = Schema({int})
>>> schema({1, 2, 3}) == {1, 2, 3}
True
>>> schema = Schema({int, str})
>>> schema({1, 2, 'abc'}) == {1, 2, 'abc'}
True
>>> schema = Schema(frozenset([int]))
>>> try:
... schema({3})
... raise AssertionError('Invalid not raised')
... except Invalid as e:
... exc = e
>>> str(exc) == 'expected a frozenset'
True
然而,空集合(set()
)被当作原样处理。如果您想指定一个可以包含任何内容的集合,请将其指定为set
>>> schema = Schema(set())
>>> try:
... schema({1})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "invalid value in set"
True
>>> schema(set()) == set()
True
>>> schema = Schema(set)
>>> schema({1, 2}) == {1, 2}
True
验证函数
验证器是简单的可调用对象,当遇到无效数据时会引发一个Invalid
异常。确定有效性的标准完全取决于实现;它可以检查一个值是否是有效的用户名使用pwd.getpwnam()
,它可以检查一个值是否是特定类型,等等。
最简单的验证器类型是当其参数无效时引发ValueError的Python函数。方便的是,许多内置Python函数都具有此属性。以下是一个日期验证器的示例
>>> from datetime import datetime
>>> def Date(fmt='%Y-%m-%d'):
... return lambda v: datetime.strptime(v, fmt)
>>> schema = Schema(Date())
>>> schema('2013-03-03')
datetime.datetime(2013, 3, 3, 0, 0)
>>> try:
... schema('2013-03')
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "not a valid value"
True
除了简单地确定一个值是否有效外,验证器还可以将值转换为有效形式。这可以通过Coerce(type)
函数实现,该函数返回一个函数,该函数将它的参数强制转换为指定的类型
def Coerce(type, msg=None):
"""Coerce a value to a type.
If the type constructor throws a ValueError, the value will be marked as
Invalid.
"""
def f(v):
try:
return type(v)
except ValueError:
raise Invalid(msg or ('expected %s' % type.__name__))
return f
此示例还展示了常见的惯用法,其中可以提供一个可选的易读消息。这可以极大地提高最终错误消息的有用性。
字典
模式字典中的每个键值对都会与对应的数据字典中的每个键值对进行验证
>>> schema = Schema({1: 'one', 2: 'two'})
>>> schema({1: 'one'})
{1: 'one'}
额外的字典键
默认情况下,数据中的任何额外键(不在模式中)都会引发异常
>>> schema = Schema({2: 3})
>>> try:
... schema({1: 2, 2: 3})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "extra keys not allowed @ data[1]"
True
此行为可以按每个模式进行更改。要允许额外的键,请使用Schema(..., extra=ALLOW_EXTRA)
>>> from voluptuous import ALLOW_EXTRA
>>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
>>> schema({1: 2, 2: 3})
{1: 2, 2: 3}
要删除额外的键,请使用Schema(..., extra=REMOVE_EXTRA)
>>> from voluptuous import REMOVE_EXTRA
>>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
>>> schema({1: 2, 2: 3})
{2: 3}
也可以通过使用通配符标记令牌extra
作为键来按字典进行覆盖
>>> from voluptuous import Extra
>>> schema = Schema({1: {Extra: object}})
>>> schema({1: {'foo': 'bar'}})
{1: {'foo': 'bar'}}
必需的字典键
默认情况下,模式中的键不必须是数据中的必需项
>>> schema = Schema({1: 2, 3: 4})
>>> schema({3: 4})
{3: 4}
与extra键的行为类似,此行为也可以按每个模式和每个键进行覆盖,使用标记令牌Required(key)
>>> schema = Schema({1: 2, 3: 4}, required=True)
>>> try:
... schema({3: 4})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data[1]"
True
使用标记令牌Optional(key)
可以单独标记键为可选
>>> schema = Schema({Required(1): 2, 3: 4})
>>> try:
... schema({3: 4})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data[1]"
True
>>> schema({1: 2})
{1: 2}
可选的字典键
如果模式具有required=True
,则可以使用标记令牌Optional(key)
单独标记键为可选
>>> from voluptuous import Optional
>>> schema = Schema({1: 2, Optional(3): 4}, required=True)
>>> try:
... schema({})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "required key not provided @ data[1]"
True
>>> schema({1: 2})
{1: 2}
>>> try:
... schema({1: 2, 4: 5})
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "extra keys not allowed @ data[4]"
True
>>> schema({1: 2, 3: 4})
{1: 2, 3: 4}
递归/嵌套模式
您可以使用voluptuous.Self
来定义一个嵌套模式
>>> from voluptuous import Schema, Self
>>> recursive = Schema({"more": Self, "value": int})
>>> recursive({"more": {"value": 42}, "value": 41}) == {'more': {'value': 42}, 'value': 41}
True
扩展现有的Schema
通常有一个基本的Schema
,可以扩展以添加更多要求。在这种情况下,您可以使用Schema.extend
来创建一个新的Schema
>>> from voluptuous import Schema
>>> person = Schema({'name': str})
>>> person_with_age = person.extend({'age': int})
>>> sorted(list(person_with_age.schema.keys()))
['age', 'name']
原始的Schema
保持不变。
对象
模式字典中的每个键值对都会与对应对象中的每个属性值对进行验证
>>> from voluptuous import Object
>>> class Structure(object):
... def __init__(self, q=None):
... self.q = q
... def __repr__(self):
... return '<Structure(q={0.q!r})>'.format(self)
...
>>> schema = Schema(Object({'q': 'one'}, cls=Structure))
>>> schema(Structure(q='one'))
<Structure(q='one')>
允许None值
要允许值是None,请使用Any
>>> from voluptuous import Any
>>> schema = Schema(Any(None, int))
>>> schema(None)
>>> schema(5)
5
错误报告
如果将无效数据传递给验证器,验证器必须抛出一个Invalid
异常。所有其他异常都视为验证器中的错误,不会被捕获。
每个Invalid
异常都有一个相关的path
属性,表示数据结构中当前正在验证的值的路径,以及一个包含原始异常消息的error_message
属性。这在您想要捕获Invalid
异常并向用户提供一些反馈时特别有用,例如在HTTP API的上下文中。
>>> def validate_email(email):
... """Validate email."""
... if not "@" in email:
... raise Invalid("This email is invalid.")
... return email
>>> schema = Schema({"email": validate_email})
>>> exc = None
>>> try:
... schema({"email": "whatever"})
... except MultipleInvalid as e:
... exc = e
>>> str(exc)
"This email is invalid. for dictionary value @ data['email']"
>>> exc.path
['email']
>>> exc.msg
'This email is invalid.'
>>> exc.error_message
'This email is invalid.'
path
属性在错误报告过程中使用,但在匹配过程中也使用,以确定是否应向用户报告错误,或者是否应尝试下一个匹配。这是通过比较检查路径的深度与错误发生路径的深度来确定的。如果错误比一个级别更深,则将其报告。
结果是,匹配是深度优先且快速失败的。
为了说明这一点,以下是一个示例模式
>>> schema = Schema([[2, 3], 6])
顶层列表中的每个值都将按深度优先顺序匹配。给定输入数据 [[6]]
,内部列表将匹配模式的第一元素,但字面量 6
将不会匹配该列表中的任何元素。此错误将立即报告给用户。不会尝试回溯。
>>> try:
... schema([[6]])
... raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
... exc = e
>>> str(exc) == "not a valid value @ data[0][0]"
True
如果我们传递数据 [6]
,则 6
不是一个列表类型,因此不会递归到模式的第一元素。匹配将继续进行到模式中的第二个元素,并成功。
>>> schema([6])
[6]
多字段验证
涉及多个字段的验证规则可以实施为自定义验证器。建议使用 All()
进行两遍验证 - 第一遍检查数据的基本结构,然后仅在之后,第二遍应用你的跨字段验证器
def passwords_must_match(passwords):
if passwords['password'] != passwords['password_again']:
raise Invalid('passwords must match')
return passwords
schema = Schema(All(
# First "pass" for field types
{'password': str, 'password_again': str},
# Follow up the first "pass" with your multi-field rules
passwords_must_match
))
# valid
schema({'password': '123', 'password_again': '123'})
# raises MultipleInvalid: passwords must match
schema({'password': '123', 'password_again': 'and now for something completely different'})
使用这种结构,你的多字段验证器将使用第一“遍”的预验证数据运行,因此不需要对其输入进行自己的类型检查。
另一方面,如果第一“遍”的验证失败,则不会运行跨字段验证器
# raises Invalid because password_again is not a string
# passwords_must_match() will not run because first-pass validation already failed
schema({'password': '123', 'password_again': 1337})
运行测试
Voluptuous 使用 pytest
$ pip install pytest
$ pytest
同时包含覆盖率报告
$ pip install pytest pytest-cov coverage>=3.0
$ pytest --cov=voluptuous voluptuous/tests/
其他库和灵感
Voluptuous 受到 Validino 的极大启发,以及到一定程度上的 jsonvalidator 和 json_schema。
pytest-voluptuous 是一个 pytest 插件,它帮助在 assert
中使用 voluptuous 验证器。
我非常偏好这些库推广的轻量级风格,而不是像 FormEncode 这样的库的复杂性。
项目详细信息
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。
源分发
构建分发
voluptuous-0.15.2.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6ffcab32c4d3230b4d2af3a577c87e1908a714a11f6f95570456b1849b0279aa |
|
MD5 | d963b2b4f3f2c6489e85f32605418964 |
|
BLAKE2b-256 | 91afa54ce0fb6f1d867e0b9f0efe5f082a691f51ccf705188fca67a3ecefd7f4 |
voluptuous-0.15.2-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 016348bc7788a9af9520b1764ebd4de0df41fe2138ebe9e06fa036bf86a65566 |
|
MD5 | 73e9484c7bb9375d8e92ab25ad8e7a8f |
|
BLAKE2b-256 | dba88f9cc6749331186e6a513bfe3745454f81d25f6e34c6024f88f80c71ed28 |