Source code for ClearMap.config.validators._schema

import re
from pathlib import Path
from typing import Any, Mapping, Dict, Sequence

from jsonschema import FormatChecker

from ClearMap.IO.assets_constants import CONTENT_TYPE_TO_PIPELINE

from .json_schema_utils import (load_yaml_schema, compile_validator, build_schema_registry_from_dir,
                                inject_image_content_types)
from ..config_handler import ALTERNATIVES_REG


__all__ = ['SectionValidators', 'validate_sections_with_jsonschema']

from ...Utils.exceptions import AggregatedValidationError

FORMAT_CHECKER = FormatChecker()


@FORMAT_CHECKER.checks('extension_string')
def _check_extension_string(v: Any) -> bool:
    if not isinstance(v, str):
        return False
    return re.fullmatch(r"\.[A-Za-z0-9][A-Za-z0-9._-]*", v) is not None


def _path_str(path: Sequence[Any]) -> str:
    if not path:
        return '(root)'
    parts = []
    for p in path:
        parts.append(f'[{p}]' if isinstance(p, int) else str(p))
    s = '.'.join(parts)
    return s.replace('.[', '[')


def get_image_content_types() -> list[str]:
    try:
        return list(CONTENT_TYPE_TO_PIPELINE.keys())
    except Exception:
        return []


[docs] class SectionValidators: """ Compiles & caches per-section JSON-Schema validators with shared typedefs and format checker. Private to the validators package; use via validate_sections_with_jsonschema or validate_all. """ def __init__(self, schemas_dir: Path): self.schemas_dir = Path(schemas_dir) def _mutate(path: Path, schema: Dict[str, Any]) -> None: # Only touch typedefs; everything else is passed through. if path.name == 'typedefs.schema.yaml': inject_image_content_types(schema, content_types=get_image_content_types()) # Keep a reference if we ever want to inspect typedefs. self.typedefs = schema self._registry = build_schema_registry_from_dir(self.schemas_dir, mutate=_mutate) self._compiled: Dict[str, Any] = {}
[docs] def validator_for(self, section: str): if section not in self._compiled: schema = load_yaml_schema(self.schemas_dir / f'{section}.schema.yaml') self._compiled[section] = compile_validator(schema, registry=self._registry, format_checker=FORMAT_CHECKER) return self._compiled[section]
[docs] def validate_sections_with_jsonschema(config: Mapping[str, Mapping[str, Any]], sv: SectionValidators) -> None: """ Validate each section of the config using JSON-Schema (Draft 2020-12). Uses the provided SectionValidators instance to load and cache schemas. Parameters ---------- config sv """ messages: list[str] = [] for section in ALTERNATIVES_REG.canonical_config_names: data = config.get(section) if data is None: continue v = sv.validator_for(section) # Not implemented yet but for future-proofing: # Optional custom messages via x-errorMessage in subschema for e in v.iter_errors(data): custom = None try: x = e.schema.get('x-errorMessage') if isinstance(x, dict): custom = x.get(e.validator) elif isinstance(x, str): custom = x except Exception: pass # TODO: check if that will blow up at some point msg = custom or e.message where = _path_str(e.absolute_path) messages.append(f'[{section}] {where}: {msg}') if messages: raise AggregatedValidationError(messages)