Как настроить локальные ссылки на файлы в документе python-jsonschema?
У меня есть набор документов, соответствующих https://json-schema.org/. Некоторые документы содержат ссылки на другие документы (через $ref
атрибуты). Я не хочу размещать эти документы так, чтобы они были доступны по HTTP URI. Таким образом, все ссылки являются относительными. Все документы живут в локальной структуре папок.
Как я могу сделать python-jsonschema
понять, как правильно использовать мою локальную файловую систему для загрузки ссылочных документов?
Например, если у меня есть документ с именем файла defs.json
содержащий некоторые определения. И я пытаюсь загрузить другой документ, который ссылается на него, например:
{
"allOf": [
{"$ref":"defs.json#/definitions/basic_event"},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["page_load"]
}
},
"required": ["action"]
}
]
}
Я получаю ошибку RefResolutionError: <urlopen error [Errno 2] No such file or directory: '/defs.json'>
Это может быть важно, что я на linux box.
(Я пишу это как вопросы и ответы, потому что мне было трудно понять это и наблюдать, как у других тоже возникают проблемы.)
6 ответов
Мне было труднее всего понять, как разрешить набор схем, $ref
друг друга (я новичок в схемах JSON). Оказывается, главное - создатьRefResolver
с store
это dict
который отображает URL в схему. Основываясь на ответе @devin-p:
import json
from jsonschema import RefResolver, Draft7Validator
base = """
{
"$id": "base.schema.json",
"type": "object",
"properties": {
"prop": {
"type": "string"
}
},
"required": ["prop"]
}
"""
extend = """
{
"$id": "extend.schema.json",
"allOf": [
{"$ref": "base.schema.json#"},
{
"properties": {
"extra": {
"type": "boolean"
}
},
"required": ["extra"]
}
]
}
"""
extend_extend = """
{
"$id": "extend_extend.schema.json",
"allOf": [
{"$ref": "extend.schema.json#"},
{
"properties": {
"extra2": {
"type": "boolean"
}
},
"required": ["extra2"]
}
]
}
"""
data = """
{
"prop": "This is the property string",
"extra": true,
"extra2": false
}
"""
schema = json.loads(base)
extendedSchema = json.loads(extend)
extendedExtendSchema = json.loads(extend_extend)
schema_store = {
schema['$id'] : schema,
extendedSchema['$id'] : extendedSchema,
extendedExtendSchema['$id'] : extendedExtendSchema,
}
resolver = RefResolver.from_schema(schema, store=schema_store)
validator = Draft7Validator(extendedExtendSchema, resolver=resolver)
jsonData = json.loads(data)
validator.validate(jsonData)
Вышеупомянутое было построено с jsonschema==3.2.0
.
Вы должны построить кастом jsonschema.RefResolver
для каждой схемы, которая использует относительную ссылку и гарантирует, что ваш распознаватель знает, где в файловой системе живет данная схема.
Такие как...
import os
import json
from jsonschema import Draft4Validator, RefResolver # We prefer Draft7, but jsonschema 3.0 is still in alpha as of this writing
abs_path_to_schema = '/path/to/schema-doc-foobar.json'
with open(abs_path_to_schema, 'r') as fp:
schema = json.load(fp)
resolver = RefResolver(
# The key part is here where we build a custom RefResolver
# and tell it where *this* schema lives in the filesystem
# Note that `file:` is for unix systems
schema_path='file:{}'.format(abs_path_to_schema),
schema=schema
)
Draft4Validator.check_schema(schema) # Unnecessary but a good idea
validator = Draft4Validator(schema, resolver=resolver, format_checker=None)
# Then you can...
data_to_validate = `{...}`
validator.validate(data_to_validate)
РЕДАКТИРОВАТЬ
Исправлена неправильная ссылка (
$ref
) кbase
схема. Обновлен пример, чтобы использовать один из самых Docs: https://json-schema.org/understanding-json-schema/structuring.html
Это просто еще одна версия ответа @Daniel, которая была правильной для меня. По сути, я решил определить
$schema
в базовой схеме. Которая затем освобождает другие схемы и делает четкий вызов при создании экземпляра преобразователя.
- Дело в том, что
RefResolver.from_schema()
получает (1) некоторую схему, а также (2) хранилище схем, было не очень ясно для меня, были ли здесь релевантны порядок и какая "некоторая" схема. Итак, структура, которую вы видите ниже.
Имею следующее:
base.schema.json
:
{
"$schema": "http://json-schema.org/draft-07/schema#"
}
definitions.schema.json
:
{
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
address.schema.json
:
{
"type": "object",
"properties": {
"billing_address": { "$ref": "definitions.schema.json#" },
"shipping_address": { "$ref": "definitions.schema.json#" }
}
}
Мне нравится эта установка по двум причинам:
Призыв уборщика
RefResolver.from_schema()
:base = json.loads(open('base.schema.json').read()) definitions = json.loads(open('definitions.schema.json').read()) schema = json.loads(open('address.schema.json').read()) schema_store = { base.get('$id','base.schema.json') : base, definitions.get('$id','definitions.schema.json') : definitions, schema.get('$id','address.schema.json') : schema, } resolver = RefResolver.from_schema(base, store=schema_store)
Тогда я пользуюсь удобным инструментом, который предоставляет библиотека, чтобы дать вам лучшее
validator_for
ваша схема (согласно вашей$schema
ключ):Validator = validator_for(base)
А затем просто соедините их, чтобы создать экземпляр
validator
:validator = Validator(schema, resolver=resolver)
Наконец, вы
validate
ваши данные:
data = { "shipping_address": { "street_address": "1600 Pennsylvania Avenue NW", "city": "Washington", "state": "DC" }, "billing_address": { "street_address": "1st Street SE", "city": "Washington", "state": 32 } }
- Этот выйдет из строя, так как
"state": 32
:
>>> validator.validate(data)
ValidationError: 32 is not of type 'string'
Failed validating 'type' in schema['properties']['billing_address']['properties']['state']:
{'type': 'string'}
On instance['billing_address']['state']:
32
Измените это на
"DC"
, и будет проверять.
Следуя ответу @chris-w, я хотел сделать то же самое с jsonschema 3.2.0
но его ответ не совсем охватил это. Надеюсь, этот ответ поможет тем, кто все еще обращается к этому вопросу за помощью, но использует более новую версию пакета.
Чтобы расширить схему JSON с помощью библиотеки, сделайте следующее:
- Создайте базовую схему:
base.schema.json
{
"$id": "base.schema.json",
"type": "object",
"properties": {
"prop": {
"type": "string"
}
},
"required": ["prop"]
}
- Создайте схему расширения
extend.schema.json
{
"allOf": [
{"$ref": "base.schema.json"},
{
"properties": {
"extra": {
"type": "boolean"
}
},
"required": ["extra"]
}
]
}
- Создайте свой JSON-файл, который вы хотите протестировать по схеме
data.json
{
"prop": "This is the property",
"extra": true
}
- Создайте свой RefResolver и Validator для базовой схемы и используйте его для проверки данных.
#Set up schema, resolver, and validator on the base schema
baseSchema = json.loads(baseSchemaJSON) # Create a schema dictionary from the base JSON file
relativeSchema = json.loads(relativeJSON) # Create a schema dictionary from the relative JSON file
resolver = RefResolver.from_schema(baseSchema) # Creates your resolver, uses the "$id" element
validator = Draft7Validator(relativeSchema, resolver=resolver) # Create a validator against the extended schema (but resolving to the base schema!)
# Check validation!
data = json.loads(dataJSON) # Create a dictionary from the data JSON file
validator.validate(data)
Возможно, вам придется внести некоторые изменения в приведенные выше записи, например не использовать Draft7Validator. Это должно работать для одноуровневых ссылок (дочерние элементы, расширяющие базу), вам нужно будет быть осторожным со своими схемами и тем, как вы настраиваетеRefResolver
а также Validator
объекты.
PS Вот отрывок, который упражняется в вышеизложенном. Попробуйте изменитьdata
строка для удаления одного из обязательных атрибутов:
import json
from jsonschema import RefResolver, Draft7Validator
base = """
{
"$id": "base.schema.json",
"type": "object",
"properties": {
"prop": {
"type": "string"
}
},
"required": ["prop"]
}
"""
extend = """
{
"allOf": [
{"$ref": "base.schema.json"},
{
"properties": {
"extra": {
"type": "boolean"
}
},
"required": ["extra"]
}
]
}
"""
data = """
{
"prop": "This is the property string",
"extra": true
}
"""
schema = json.loads(base)
extendedSchema = json.loads(extend)
resolver = RefResolver.from_schema(schema)
validator = Draft7Validator(extendedSchema, resolver=resolver)
jsonData = json.loads(data)
validator.validate(jsonData)
Мой подход заключается в предварительной загрузке всех фрагментов схемы в кеш RefResolver. Я создал суть, которая это иллюстрирует: https://gist.github.com/mrtj/d59812a981da17fbaa67b7de98ac3d4b
Это то, что я использовал для динамической генерации
schema_store
из всех схем в данном каталоге
base.schema.json
{
"$id": "base.schema.json",
"type": "object",
"properties": {
"prop": {
"type": "string"
}
},
"required": ["prop"]
}
extend.schema.json
{
"$id": "extend.schema.json",
"allOf": [
{"$ref": "base.schema.json"},
{
"properties": {
"extra": {
"type": "boolean"
}
},
"required": ["extra"]
}
]
}
instance.json
{
"prop": "This is the property string",
"extra": true
}
validator.py
import json
from pathlib import Path
from jsonschema import Draft7Validator, RefResolver
from jsonschema.exceptions import RefResolutionError
schemas = (json.load(open(source)) for source in Path("schema/dir").iterdir())
schema_store = {schema["$id"]: schema for schema in schemas}
schema = json.load(open("schema/dir/name.schema.json"))
instance = json.load(open("instance/dir/instance.json"))
resolver = RefResolver.from_schema(schema, store=schema_store)
validator = Draft7Validator(schema, resolver=resolver)
try:
errors = sorted(validator.iter_errors(instance), key=lambda e: e.path)
except RefResolutionError as e:
print(e)