简单数据验证库
项目描述
schema 是一个用于验证Python数据结构的库,例如从配置文件、表单、外部服务或命令行解析获得的数据,以及从JSON/YAML(或其他格式)转换为Python数据类型。
示例
以下是一个快速示例,了解如何使用 schema 验证包含个人信息条目的列表
from schema import Schema, And, Use, Optional, SchemaError
schema = Schema(
[
{
"name": And(str, len),
"age": And(Use(int), lambda n: 18 <= n <= 99),
Optional("gender"): And(
str,
Use(str.lower),
lambda s: s in ("squid", "kid"),
),
}
]
)
data = [
{"name": "Sue", "age": "28", "gender": "Squid"},
{"name": "Sam", "age": "42"},
{"name": "Sacha", "age": "20", "gender": "KID"},
]
validated = schema.validate(data)
assert validated == [
{"name": "Sue", "age": 28, "gender": "squid"},
{"name": "Sam", "age": 42},
{"name": "Sacha", "age": 20, "gender": "kid"},
]
如果数据有效,Schema.validate 将返回验证后的数据(可选地通过 Use 调用转换,见下文)。
如果数据无效,Schema将引发SchemaError异常。如果您只想检查数据是否有效,schema.is_valid(data)将返回True或False。
安装
使用pip或easy_install
pip install schema
或者,您只需将schema.py文件放入您的项目中——它是一个自包含的文件。
schema已在Python 2.6、2.7、3.2、3.3、3.4、3.5、3.6、3.7、3.8、3.9和PyPy上进行测试。
schema遵循语义版本控制。
Schema如何验证数据
类型
如果Schema(...)遇到一个类型(如int、str、object等),它将检查对应的数据项是否为该类型的实例,如果不是,将引发SchemaError。
>>> from schema import Schema
>>> Schema(int).validate(123)
123
>>> Schema(int).validate('123')
Traceback (most recent call last):
...
schema.SchemaUnexpectedTypeError: '123' should be instance of 'int'
>>> Schema(object).validate('hai')
'hai'
可调用对象
如果Schema(...)遇到一个可调用对象(函数、类或具有__call__方法的对象),它将调用该对象,如果其返回值评估为True,则继续验证,否则将引发SchemaError。
>>> import os
>>> Schema(os.path.exists).validate('./')
'./'
>>> Schema(os.path.exists).validate('./non-existent/')
Traceback (most recent call last):
...
schema.SchemaError: exists('./non-existent/') should evaluate to True
>>> Schema(lambda n: n > 0).validate(123)
123
>>> Schema(lambda n: n > 0).validate(-12)
Traceback (most recent call last):
...
schema.SchemaError: <lambda>(-12) should evaluate to True
“可验证对象”
如果Schema(...)遇到具有validate方法的对象,它将运行此方法以验证对应的数据,格式为data = obj.validate(data)。此方法可能引发SchemaError异常,这将告诉Schema该数据项无效,否则将继续验证。
“可验证对象”的一个例子是Regex,它尝试使用给定的正则表达式(本身为字符串、缓冲区或编译后的正则表达式SRE_Pattern)匹配字符串或缓冲区。
>>> from schema import Regex
>>> import re
>>> Regex(r'^foo').validate('foobar')
'foobar'
>>> Regex(r'^[A-Z]+$', flags=re.I).validate('those-dashes-dont-match')
Traceback (most recent call last):
...
schema.SchemaError: Regex('^[A-Z]+$', flags=re.IGNORECASE) does not match 'those-dashes-dont-match'
对于更通用的情况,您可以使用Use来创建这样的对象。Use在验证时帮助使用函数或类型转换值
>>> from schema import Use
>>> Schema(Use(int)).validate('123')
123
>>> Schema(Use(lambda f: open(f, 'a'))).validate('LICENSE-MIT')
<_io.TextIOWrapper name='LICENSE-MIT' mode='a' encoding='UTF-8'>
忽略细节,Use基本上
class Use(object):
def __init__(self, callable_):
self._callable = callable_
def validate(self, data):
try:
return self._callable(data)
except Exception as e:
raise SchemaError('%r raised %r' % (self._callable.__name__, e))
有时您需要转换和验证部分数据,但保持原始数据不变。Const有助于保护您的数据安全
>> from schema import Use, Const, And, Schema
>> from datetime import datetime
>> is_future = lambda date: datetime.now() > date
>> to_json = lambda v: {"timestamp": v}
>> Schema(And(Const(And(Use(datetime.fromtimestamp), is_future)), Use(to_json))).validate(1234567890)
{"timestamp": 1234567890}
现在您可以编写自己的具有验证功能的类和数据类型。
列表和类似容器
如果Schema(...)遇到list、tuple、set或frozenset的实例,它将验证对应数据容器中的所有数据与容器内列出的所有模式进行对比,并汇总所有错误
>>> Schema([1, 0]).validate([1, 1, 0, 1])
[1, 1, 0, 1]
>>> Schema((int, float)).validate((5, 7, 8, 'not int or float here'))
Traceback (most recent call last):
...
schema.SchemaError: Or(<class 'int'>, <class 'float'>) did not validate 'not int or float here'
'not int or float here' should be instance of 'int'
'not int or float here' should be instance of 'float'
字典
如果Schema(...)遇到dict的实例,它将验证数据键值对
>>> d = Schema(
... {"name": str, "age": lambda n: 18 <= n <= 99}
... ).validate(
... {"name": "Sue", "age": 28}
... )
>>> assert d == {'name': 'Sue', 'age': 28}
您也可以指定键为模式
>>> schema = Schema({
... str: int, # string keys should have integer values
... int: None, # int keys should be always None
... })
>>> data = schema.validate({
... "key1": 1,
... "key2": 2,
... 10: None,
... 20: None,
... })
>>> schema.validate({
... "key1": 1,
... 10: "not None here",
... })
Traceback (most recent call last):
...
schema.SchemaError: Key '10' error:
None does not match 'not None here'
如果您只想检查某些键值,而不关心其他键值,这将很有用
>>> schema = Schema({
... "<id>": int,
... "<file>": Use(open),
... str: object, # don't care about other str keys
... })
>>> data = schema.validate({
... "<id>": 10,
... "<file>": "README.rst",
... "--verbose": True,
... })
您可以如下标记一个键为可选的
>>> Schema({
... "name": str,
... Optional("occupation"): str,
... }).validate({"name": "Sam"})
{'name': 'Sam'}
可选键也可以携带一个默认值,在数据中没有匹配的键时使用
>>> Schema({
... Optional("color", default="blue"): str,
... str: str,
... }).validate({"texture": "furry"}) == {
... "color": "blue",
... "texture": "furry",
... }
True
默认值将直接使用,而不会通过值中指定的任何验证器。
默认值也可以是一个可调用对象
>>> from schema import Schema, Optional
>>> Schema({Optional('data', default=dict): {}}).validate({}) == {'data': {}}
True
此外,请注意:如果您指定了类型,schema不会验证空字典
>>> Schema({int:int}).is_valid({})
False
要这样做,您需要Schema(Or({int:int}, {}))。这与列表的情况不同,其中Schema([int]).is_valid([])将返回True。
模式包含类And和Or,它们有助于验证同一数据的多个模式
>>> from schema import And, Or
>>> Schema({'age': And(int, lambda n: 0 < n < 99)}).validate({'age': 7})
{'age': 7}
>>> Schema({'password': And(str, lambda s: len(s) > 6)}).validate({'password': 'hai'})
Traceback (most recent call last):
...
schema.SchemaError: Key 'password' error:
<lambda>('hai') should evaluate to True
>>> Schema(And(Or(int, float), lambda x: x > 0)).validate(3.1415)
3.1415
在字典中,您还可以以“一个或另一个”的方式组合两个键。为此,请使用Or类作为键
>>> from schema import Or, Schema
>>> schema = Schema({
... Or("key1", "key2", only_one=True): str
... })
>>> schema.validate({"key1": "test"}) # Ok
{'key1': 'test'}
>>> schema.validate({"key1": "test", "key2": "test"}) # SchemaError
Traceback (most recent call last):
...
schema.SchemaOnlyOneAllowedError: There are multiple keys present from the Or('key1', 'key2') condition
钩子
您可以定义钩子,这些是每当找到有效的键:值时执行的函数。Forbidden类就是这样的例子。
您可以通过以下方式将键标记为禁止
>>> from schema import Forbidden
>>> Schema({Forbidden('age'): object}).validate({'age': 50})
Traceback (most recent call last):
...
schema.SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50}
以下几点值得关注。首先,与禁止键配对的值确定它是否会遭到拒绝
>>> Schema({Forbidden('age'): str, 'age': int}).validate({'age': 50})
{'age': 50}
注意:如果我们没有在这里提供‘age’键,调用也会失败,但会引发SchemaWrongKeyError,而不是SchemaForbiddenKeyError。
其次,Forbidden比标准键具有更高的优先级,因此比Optional也更高。这意味着我们可以这样做
>>> Schema({Forbidden('age'): object, Optional(str): object}).validate({'age': 50})
Traceback (most recent call last):
...
schema.SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50}
您还可以定义自己的钩子。以下钩子会在遇到key时调用_my_function。
from schema import Hook
def _my_function(key, scope, error):
print(key, scope, error)
Hook("key", handler=_my_function)
以下是一个示例,其中添加了Deprecated类,以在遇到键时记录警告
from schema import Hook, Schema
class Deprecated(Hook):
def __init__(self, *args, **kwargs):
kwargs["handler"] = lambda key, *args: logging.warn(f"`{key}` is deprecated. " + (self._error or ""))
super(Deprecated, self).__init__(*args, **kwargs)
Schema({Deprecated("test", "custom error message."): object}, ignore_extra_keys=True).validate({"test": "value"})
...
WARNING: `test` is deprecated. custom error message.
额外键
Schema(...)参数ignore_extra_keys会导致验证忽略字典中的额外键,并且在验证后也不会返回它们。
>>> schema = Schema({'name': str}, ignore_extra_keys=True)
>>> schema.validate({'name': 'Sam', 'age': '42'})
{'name': 'Sam'}
如果您希望返回任何额外键,请使用object: object作为键/值对之一,这将匹配任何键和任何值。否则,额外键将引发SchemaError。
自定义验证
Schema.validate方法接受额外的关键字参数。这些关键字参数将传播到任何子验证对象(包括任何临时的Schema对象)的validate方法,或者为Optional键指定的默认值可调用对象。
可以将此功能与Schema类的继承一起使用来自定义验证。
以下是一个示例,其中在较大的模式中的子模式验证之后运行“验证后”钩子
class EventSchema(schema.Schema):
def validate(self, data, _is_event_schema=True):
data = super(EventSchema, self).validate(data, _is_event_schema=False)
if _is_event_schema and data.get("minimum", None) is None:
data["minimum"] = data["capacity"]
return data
events_schema = schema.Schema(
{
str: EventSchema({
"capacity": int,
schema.Optional("minimum"): int, # default to capacity
})
}
)
data = {'event1': {'capacity': 1}, 'event2': {'capacity': 2, 'minimum': 3}}
events = events_schema.validate(data)
assert events['event1']['minimum'] == 1 # == capacity
assert events['event2']['minimum'] == 3
请注意,额外的关键字参数_is_event_schema是必要的,以将自定义行为限制在EventSchema对象本身上,这样它就不会影响任何子模式的递归调用self.__class__.validate(例如,对Schema("capacity").validate("capacity")的调用)。
用户友好的错误报告
您可以将关键字参数error传递给任何验证类(如Schema、And、Or、Regex、Use)以报告此错误,而不是使用内置的错误。
>>> Schema(Use(int, error='Invalid year')).validate('XVII')
Traceback (most recent call last):
...
schema.SchemaError: Invalid year
您可以通过访问异常的exc.autos来查看发生的所有错误,对于自动生成的错误消息,以及通过传递带有error文本的exc.errors。
如果您想在不显示跟踪信息的情况下向用户显示消息,可以使用sys.exit(exc.code)退出。在这种情况下,错误消息具有优先级。
JSON API示例
以下是一个快速示例:验证github API中创建gist请求的验证。
>>> gist = '''{"description": "the description for this gist",
... "public": true,
... "files": {
... "file1.txt": {"content": "String file contents"},
... "other.txt": {"content": "Another file contents"}}}'''
>>> from schema import Schema, And, Use, Optional
>>> import json
>>> gist_schema = Schema(
... And(
... Use(json.loads), # first convert from JSON
... # use str since json returns unicode
... {
... Optional("description"): str,
... "public": bool,
... "files": {str: {"content": str}},
... },
... )
... )
>>> gist = gist_schema.validate(gist)
# gist:
{u'description': u'the description for this gist',
u'files': {u'file1.txt': {u'content': u'String file contents'},
u'other.txt': {u'content': u'Another file contents'}},
u'public': True}
使用schema和docopt
假设您正在使用以下使用模式使用docopt
用法:my_program.py [–count=N] <path> <files>…
并且您想验证<files>是可读的,<path>是存在的,并且--count是0到5之间的整数,或者是None。
假设 docopt 返回以下字典
>>> args = {
... "<files>": ["LICENSE-MIT", "setup.py"],
... "<path>": "../",
... "--count": "3",
... }
这是如何使用 schema 进行验证的
>>> from schema import Schema, And, Or, Use
>>> import os
>>> s = Schema({
... "<files>": [Use(open)],
... "<path>": os.path.exists,
... "--count": Or(None, And(Use(int), lambda n: 0 < n < 5)),
... })
>>> args = s.validate(args)
>>> args['<files>']
[<_io.TextIOWrapper name='LICENSE-MIT' ...>, <_io.TextIOWrapper name='setup.py' ...]
>>> args['<path>']
'../'
>>> args['--count']
3
如您所见,schema 成功验证了数据,打开了文件并将 '3' 转换为 int。
JSON schema
您还可以从字典 Schema 生成标准的 draft-07 JSON schema。这可以用于在代码编辑器中直接添加单词补全、验证和文档。输出模式也可以与兼容 JSON schema 的库一起使用。
JSON:生成
正常定义您的模式并对其调用 .json_schema()。输出是一个 Python 字典,您需要将其导出为 JSON。
>>> from schema import Optional, Schema
>>> import json
>>> s = Schema({
... "test": str,
... "nested": {Optional("other"): str},
... })
>>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json"))
# json_schema
{
"type":"object",
"properties": {
"test": {"type": "string"},
"nested": {
"type":"object",
"properties": {
"other": {"type": "string"}
},
"required": [],
"additionalProperties": false
}
},
"required":[
"test",
"nested"
],
"additionalProperties":false,
"$id":"https://example.com/my-schema.json",
"$schema":"https://json-schema.fullstack.org.cn/draft-07/schema#"
}
您可以使用 Literal 对象而不是字符串来为模式元素添加描述。主模式也可以有描述。
这些将显示在 IDE 中,以帮助用户编写配置。
>>> from schema import Literal, Schema
>>> import json
>>> s = Schema(
... {Literal("project_name", description="Names must be unique"): str},
... description="Project schema",
... )
>>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json"), indent=4)
# json_schema
{
"type": "object",
"properties": {
"project_name": {
"description": "Names must be unique",
"type": "string"
}
},
"required": [
"project_name"
],
"additionalProperties": false,
"$id": "https://example.com/my-schema.json",
"$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#",
"description": "Project schema"
}
JSON:支持的验证
生成的 JSON schema 不保证接受与库相同的对象,因为某些验证尚未实现或没有 JSON schema 等效。例如,Use 和 Hook 对象就是这样。
已实现
- 对象属性
使用字典字面量。字典键是 JSON schema 属性。
示例
Schema({"test": str})
变为
{'type': 'object', 'properties': {'test': {'type': 'string'}}, 'required': ['test'], 'additionalProperties': False}.
请注意,属性默认是必需的。要创建可选属性,请使用 Optional,如下所示
Schema({Optional("test"): str})
变为
{'type': 'object', 'properties': {'test': {'type': 'string'}}, 'required': [], 'additionalProperties': False}
- 当至少满足以下条件之一时,additionalProperties 设置为 true
ignore_extra_keys 为 True
至少有一个键是 str 或 object
例如
Schema({str: str}) 和 Schema({}, ignore_extra_keys=True)
都变为
{'type': 'object', 'properties': {}, 'required': [], 'additionalProperties': True}
和
Schema({})
变为
{'type': 'object', 'properties': {}, 'required': [], 'additionalProperties': False}
- 类型
直接使用 Python 类型名称。它将被转换为 JSON 名称
示例
Schema(float)
变为
{"type": "number"}
- 数组项
用 [] 包围一个模式。
示例
Schema([str]) 意味着一个字符串数组,变为
{'type': 'array', 'items': {'type': 'string'}}
- 枚举值
使用 Or。
示例
Schema(Or(1, 2, 3))
{"enum": [1, 2, 3]}
- 常量值
使用值本身。
示例
Schema("name")
{"const": "name"}
- 正则表达式
使用 Regex。
示例
Schema(Regex("^v\d+"))
{'type': 'string', 'pattern': '^v\\d+'}
- 注释(标题和描述)
您可以使用 Schema 对象的 init 方法中的 name 和 description 参数。
要将描述添加到键中,将字符串替换为 Literal 对象。
示例
Schema({Literal("test", description="描述"): str})
等同于
Schema({"test": str})
添加到生成的 JSON 模式中的描述。
- 使用 allOf 组合模式
使用 And
示例
Schema(And(str, "value"))
变为
{"allOf": [{"type": "string"}, {"const": "value"}]}
注意,这个示例在现实世界中并不实用,因为 const 已经暗示了类型。
- 使用 anyOf 组合模式
使用 Or
示例
Schema(Or(str, int))
变为
{"anyOf": [{"type": "string"}, {"type": "integer"}]}
未实现
以下 JSON 模式验证无法由此库生成。
JSON:最小化输出大小
显式重用
如果您的 JSON 模式很大且有很多重复,可以通过定义 Schema 对象作为引用来简化并减小其大小。这些引用将放置在主模式中的“definitions”部分。
>>> from schema import Optional, Schema
>>> import json
>>> s = Schema({
... "test": str,
... "nested": Schema({Optional("other"): str}, name="nested", as_reference=True)
... })
>>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json"), indent=4)
# json_schema
{
"type": "object",
"properties": {
"test": {
"type": "string"
},
"nested": {
"$ref": "#/definitions/nested"
}
},
"required": [
"test",
"nested"
],
"additionalProperties": false,
"$id": "https://example.com/my-schema.json",
"$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#",
"definitions": {
"nested": {
"type": "object",
"properties": {
"other": {
"type": "string"
}
},
"required": [],
"additionalProperties": false
}
}
}
当使用同一对象多次时,这非常有用
>>> from schema import Optional, Or, Schema
>>> import json
>>> language_configuration = Schema(
... {"autocomplete": bool, "stop_words": [str]},
... name="language",
... as_reference=True,
... )
>>> s = Schema({Or("ar", "cs", "de", "el", "eu", "en", "es", "fr"): language_configuration})
>>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json"), indent=4)
# json_schema
{
"type": "object",
"properties": {
"ar": {
"$ref": "#/definitions/language"
},
"cs": {
"$ref": "#/definitions/language"
},
"de": {
"$ref": "#/definitions/language"
},
"el": {
"$ref": "#/definitions/language"
},
"eu": {
"$ref": "#/definitions/language"
},
"en": {
"$ref": "#/definitions/language"
},
"es": {
"$ref": "#/definitions/language"
},
"fr": {
"$ref": "#/definitions/language"
}
},
"required": [],
"additionalProperties": false,
"$id": "https://example.com/my-schema.json",
"$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#",
"definitions": {
"language": {
"type": "object",
"properties": {
"autocomplete": {
"type": "boolean"
},
"stop_words": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"autocomplete",
"stop_words"
],
"additionalProperties": false
}
}
}
自动重用
如果您想最小化输出大小而不显式使用名称,可以使用库生成输出 JSON 模式部分的哈希并用作引用。
通过向 json_schema 方法提供参数 use_refs 启用此行为。
请注意,此方法与 IDE 和 JSON 模式库的兼容性较低。它生成的 JSON 模式对人类更难阅读。
>>> from schema import Optional, Or, Schema
>>> import json
>>> language_configuration = Schema({"autocomplete": bool, "stop_words": [str]})
>>> s = Schema({Or("ar", "cs", "de", "el", "eu", "en", "es", "fr"): language_configuration})
>>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json", use_refs=True), indent=4)
# json_schema
{
"type": "object",
"properties": {
"ar": {
"type": "object",
"properties": {
"autocomplete": {
"type": "boolean",
"$id": "#6456104181059880193"
},
"stop_words": {
"type": "array",
"items": {
"type": "string",
"$id": "#1856069563381977338"
}
}
},
"required": [
"autocomplete",
"stop_words"
],
"additionalProperties": false
},
"cs": {
"type": "object",
"properties": {
"autocomplete": {
"$ref": "#6456104181059880193"
},
"stop_words": {
"type": "array",
"items": {
"$ref": "#1856069563381977338"
},
"$id": "#-5377945144312515805"
}
},
"required": [
"autocomplete",
"stop_words"
],
"additionalProperties": false
},
"de": {
"type": "object",
"properties": {
"autocomplete": {
"$ref": "#6456104181059880193"
},
"stop_words": {
"$ref": "#-5377945144312515805"
}
},
"required": [
"autocomplete",
"stop_words"
],
"additionalProperties": false,
"$id": "#-8142886105174600858"
},
"el": {
"$ref": "#-8142886105174600858"
},
"eu": {
"$ref": "#-8142886105174600858"
},
"en": {
"$ref": "#-8142886105174600858"
},
"es": {
"$ref": "#-8142886105174600858"
},
"fr": {
"$ref": "#-8142886105174600858"
}
},
"required": [],
"additionalProperties": false,
"$id": "https://example.com/my-schema.json",
"$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#"
}
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。