Source code for linkml.validator

"""
The ``linkml.validator`` package contains the LinkML validation framework.
"""

import os
from pathlib import Path
from typing import Any

from linkml.validator.loaders import default_loader_for_file
from linkml.validator.plugins import JsonschemaValidationPlugin
from linkml.validator.report import ValidationReport
from linkml.validator.validator import Validator
from linkml_runtime.linkml_model import SchemaDefinition
from linkml_runtime.loaders import yaml_loader


def _get_default_validator(
    schema: str | dict | Path | SchemaDefinition,
    *,
    strict: bool = False,
    closed: bool = True,
    config: str | Path | dict | None = None,
) -> Validator:
    """Build the default :class:`Validator` for a schema.

    :param schema: The schema to validate against.
    :param strict: Stop after the first validation failure when ``True``.
    :param closed: When ``True`` (default), the bundled
        :class:`JsonschemaValidationPlugin` rejects undeclared slots
        (closed-world validation). Set to ``False`` to allow extra
        properties. Ignored if ``config`` provides an explicit ``plugins``
        section.
    :param config: Optional project-level validation config - either a path
        to a ``linkml-validate``-style YAML file or a pre-parsed mapping.
        When the mapping contains a ``plugins:`` key, those plugins are
        used in place of the default and ``closed`` is ignored.
    """
    try:
        if isinstance(schema, Path):
            schema = str(schema)
        if isinstance(schema, dict):
            schema = SchemaDefinition(**schema)
        elif isinstance(schema, str):
            schema = yaml_loader.load(schema, target_class=SchemaDefinition)

        if not isinstance(schema, SchemaDefinition):
            raise ValueError(f"Schema could not be loaded from {schema}")
    except ValueError as e:
        raise ValueError(f"Invalid schema: {schema}") from e

    validation_plugins = _resolve_default_plugins(closed=closed, config=config)

    return Validator(schema, validation_plugins=validation_plugins, strict=strict)


def _resolve_default_plugins(
    *,
    closed: bool = True,
    config: str | Path | dict | None = None,
) -> list:
    """Resolve the list of plugins for :func:`_get_default_validator`.

    Honors an optional project-level ``linkml-validate``-style config that
    may override the default ``JsonschemaValidationPlugin(closed=...)``.
    """
    if config is not None:
        # Lazy import to avoid a circular dependency with "linkml.validator.cli".
        import yaml

        from linkml.validator.cli import Config as _CliConfig
        from linkml.validator.cli import _resolve_plugins

        if isinstance(config, str | Path):
            with open(config) as cfg_file:
                config_data = yaml.safe_load(cfg_file) or {}
        else:
            config_data = dict(config)
        # ``Config.schema`` is optional; drop ``data_sources`` if present
        # since it has no meaning here.
        config_data.pop("data_sources", None)
        cfg = _CliConfig(**config_data)
        if cfg.plugins:
            return _resolve_plugins(cfg.plugins)

    return [JsonschemaValidationPlugin(closed=closed)]


[docs] def validate( instance: Any, schema: str | dict | SchemaDefinition, target_class: str | None = None, *, strict: bool = False, ) -> ValidationReport: """Validate a data instance against a schema This function provides a simple interface to do basic validation performed by a JSON Schema validator on a single instance. To have more control over the type of validation performed, see the :class:`Validator` class. :param instance: The instance to validate :param schema: The schema used to validate the instance. If a string is it will be interpreted as a path, URL, or other loadable location. If it is a dict it should be compatible with ``SchemaDefinition``, otherwise it should be a ``SchemaDefinition`` instance. :param target_class: Name of the class within the schema to validate against. If ``None``, the class will be inferred from the schema by looking for a class with ``tree_root: true``. Defaults to ``None``. :param strict: If ``True``, validation will stop after the first validation error is found, Otherwise all validation problems will be reported. Defaults to ``False``. :raises ValidationError: If requested to raise and validation errors are found. :return: A validation report :rtype: ValidationReport """ validator = _get_default_validator(schema, strict=strict) return validator.validate(instance, target_class)
[docs] def validate_file( file: str | bytes | os.PathLike, schema: str | dict | SchemaDefinition, target_class: str | None = None, *, strict: bool = False, ) -> ValidationReport: """Validate instances loaded from a file against a schema This function provides a simple interface to do basic validation performed by a JSON Schema validator on instances loaded from a file. Loading is done according to the file's extension. Accepted file extensions are: ``.csv``, ``.tsv``, ``.yaml``, ``.yml``, and ``.json``. Individual rows of CSV and TSV files are treated as instances to validate. Each document within a YAML file is treated as an individual instance to validate. If the top-level of a JSON file is an array, each element of the array is treated as an instance to validate. Otherwise, if the top-level is an object it is treated as a single instance to validate. To have more control over the type of validation performed, see the :class:`Validator` class. :param file: Path-like object of the file to be read :param schema: The schema used to validate the instance. If a string is it will be interpreted as a path, URL, or other loadable location. If it is a dict it should be compatible with ``SchemaDefinition``, otherwise it should be a ``SchemaDefinition`` instance. :param target_class: Name of the class within the schema to validate against. If ``None``, the class will be inferred from the schema by looking for a class with ``tree_root: true``. Defaults to ``None``. :param strict: If ``True``, validation will stop after the first validation error is found, Otherwise all validation problems will be reported. Defaults to ``False``. :return: A validation report :rtype: ValidationReport """ schema_path = schema if isinstance(schema, str | Path) else None loader = default_loader_for_file(file, schema_path=schema_path, target_class=target_class) validator = _get_default_validator(schema, strict=strict) return validator.validate_source(loader, target_class)
__all__ = ["Validator", "validate", "validate_file"]