ProvenPin Install

jsonschema Draft 2020-12 validation with a referencing registry: working versions (tested 2026-10-15)

Validate JSON fixtures against Draft 2020-12 schemas that cross-reference each other via a referencing Registry, including dynamic anchors, unevaluatedProperties and format checking.

passed6 passed · 0 failed2026-10-15 18:09 UTC
Status
passed
Last passed
2026-10-15 18:09 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions3
jsonschema@4.26.0referencing@0.37.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38783276983

The code

Exactly the files that passed. Your agent can pull them with get_recipe("jsonschema-referencing-draft2020") (install), or see them on GitHub.

requirements.txt
jsonschema==4.26.0
referencing==0.37.0
pytest==9.1.1
schemas.py
from jsonschema import Draft202012Validator, FormatChecker
from referencing import Registry
from referencing.jsonschema import DRAFT202012

ADDRESS = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://example.com/schemas/address",
    "type": "object",
    "properties": {
        "street": {"type": "string", "minLength": 1},
        "postcode": {"type": "string", "pattern": r"^[0-9]{5}$"},
    },
    "required": ["street"],
    "unevaluatedProperties": False,
}

PERSON = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://example.com/schemas/person",
    "type": "object",
    "properties": {
        "name": {"type": "string", "minLength": 1},
        "born": {"type": "string", "format": "date"},
        "address": {"$ref": "https://example.com/schemas/address"},
    },
    "required": ["name", "address"],
    "unevaluatedProperties": False,
}

TREE = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://example.com/schemas/tree",
    "$dynamicAnchor": "node",
    "type": "object",
    "properties": {
        "value": {"type": "integer"},
        "children": {"type": "array", "items": {"$dynamicRef": "#node"}},
    },
    "required": ["value"],
    "unevaluatedProperties": False,
}

def build_registry() -> Registry:
    return Registry().with_resources(
        [(s["$id"], DRAFT202012.create_resource(s)) for s in (ADDRESS, PERSON, TREE)]
    )

def errors(schema, instance, formats=False):
    checker = FormatChecker() if formats else None
    validator = Draft202012Validator(schema, registry=build_registry(), format_checker=checker)
    return sorted(validator.iter_errors(instance), key=lambda e: list(e.absolute_path))
tests/test_schemas.py
import pytest
from referencing import Resource
from referencing.exceptions import CannotDetermineSpecification
from referencing.jsonschema import DRAFT202012

from schemas import PERSON, TREE, errors

VALID = {
    "name": "Ada",
    "born": "1815-12-10",
    "address": {"street": "12 Baker St", "postcode": "12345"},
}

def test_valid_document_passes():
    assert errors(PERSON, VALID) == []

def test_ref_constraints_apply_across_resources():
    bad = {**VALID, "address": {"street": "12 Baker St", "postcode": "nope"}}
    (err,) = errors(PERSON, bad)
    assert list(err.absolute_path) == ["address", "postcode"]
    assert err.validator == "pattern"

def test_unevaluated_properties_sees_through_ref():
    person_level = {**VALID, "nickname": "Ada"}
    assert errors(PERSON, person_level)[0].validator == "unevaluatedProperties"
    address_level = {**VALID, "address": {**VALID["address"], "country": "UK"}}
    (err,) = errors(PERSON, address_level)
    assert err.validator == "unevaluatedProperties"

def test_format_is_annotation_only_unless_checker_passed():
    bad = {**VALID, "born": "not-a-date"}
    assert errors(PERSON, bad) == []
    (err,) = errors(PERSON, bad, formats=True)
    assert err.validator == "format" and err.instance == "not-a-date"

def test_dynamic_ref_recurses_into_children():
    doc = {"value": 1, "children": [{"value": 2}, {"value": "x"}]}
    (err,) = errors(TREE, doc)
    assert list(err.absolute_path) == ["children", 1, "value"]

def test_from_contents_needs_schema_or_default():
    with pytest.raises(CannotDetermineSpecification):
        Resource.from_contents({"type": "object"})
    resource = Resource.from_contents({"type": "object"}, default_specification=DRAFT202012)
    assert resource.contents == {"type": "object"}
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.