- add authorized nine-source crawler connectors with security controls and packaged schemas - add the school internship contract to the vendor data standard center - add bidirectional campus data-to-report lineage with gap ownership and remediation Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
36 lines
1.3 KiB
Python
36 lines
1.3 KiB
Python
"""JSON Schema loading and validation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from functools import lru_cache
|
|
from importlib.resources import files
|
|
from itertools import islice
|
|
|
|
from jsonschema import Draft202012Validator, FormatChecker
|
|
|
|
from .errors import SchemaValidationFailed
|
|
|
|
|
|
@lru_cache(maxsize=8)
|
|
def validator_for(schema_name: str) -> Draft202012Validator:
|
|
if "/" in schema_name or "\\" in schema_name or not schema_name.endswith(".json"):
|
|
raise SchemaValidationFailed(f"Target schema is unavailable: {schema_name}")
|
|
resource = files("edu_crawler.schemas").joinpath(schema_name)
|
|
if not resource.is_file():
|
|
raise SchemaValidationFailed(f"Target schema is unavailable: {schema_name}")
|
|
schema = json.loads(resource.read_text(encoding="utf-8"))
|
|
Draft202012Validator.check_schema(schema)
|
|
return Draft202012Validator(schema, format_checker=FormatChecker())
|
|
|
|
|
|
def validate_record(schema_name: str, record: dict) -> None:
|
|
errors = list(islice(validator_for(schema_name).iter_errors(record), 5))
|
|
if not errors:
|
|
return
|
|
summaries = []
|
|
for error in errors:
|
|
path = ".".join(str(part) for part in error.absolute_path) or "$"
|
|
summaries.append(f"{path}: {error.validator} validation failed")
|
|
raise SchemaValidationFailed("; ".join(summaries))
|