Как настроить локальные ссылки на файлы в документе 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#" }
  }
}

Мне нравится эта установка по двум причинам:

  1. Призыв уборщика 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)
    
  2. Тогда я пользуюсь удобным инструментом, который предоставляет библиотека, чтобы дать вам лучшее validator_for ваша схема (согласно вашей $schema ключ):

    Validator = validator_for(base)
    
  3. А затем просто соедините их, чтобы создать экземпляр 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 с помощью библиотеки, сделайте следующее:

  1. Создайте базовую схему:
base.schema.json
{
  "$id": "base.schema.json",
  "type": "object",
  "properties": {
    "prop": {
      "type": "string"
    }
  },
  "required": ["prop"]
}
  1. Создайте схему расширения
extend.schema.json
{
  "allOf": [
    {"$ref": "base.schema.json"},
    {
      "properties": {
        "extra": {
          "type": "boolean"
        }
      },
      "required": ["extra"]
    }
  ]
}
  1. Создайте свой JSON-файл, который вы хотите протестировать по схеме
data.json
{
  "prop": "This is the property",
  "extra": true
}
  1. Создайте свой 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)
Другие вопросы по тегам