Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
exceptions.py491 linesDownload Raw Back to jsonschema
1"""2Validation errors, and some surrounding helpers.3"""4from __future__ import annotations5 6from collections import defaultdict, deque7from pprint import pformat8from textwrap import dedent, indent9from typing import TYPE_CHECKING, Any, ClassVar10import heapq11import re12import warnings13 14from attrs import define15from referencing.exceptions import Unresolvable as _Unresolvable16 17from jsonschema import _utils18 19if TYPE_CHECKING:20    from collections.abc import Iterable, Mapping, MutableMapping, Sequence21 22    from jsonschema import _types23 24WEAK_MATCHES: frozenset[str] = frozenset(["anyOf", "oneOf"])25STRONG_MATCHES: frozenset[str] = frozenset()26 27_JSON_PATH_COMPATIBLE_PROPERTY_PATTERN = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$")28 29_unset = _utils.Unset()30 31 32def _pretty(thing: Any, prefix: str):33    """34    Format something for an error message as prettily as we currently can.35    """36    return indent(pformat(thing, width=72, sort_dicts=False), prefix).lstrip()37 38 39def __getattr__(name):40    if name == "RefResolutionError":41        warnings.warn(42            _RefResolutionError._DEPRECATION_MESSAGE,43            DeprecationWarning,44            stacklevel=2,45        )46        return _RefResolutionError47    raise AttributeError(f"module {__name__} has no attribute {name}")48 49 50class _Error(Exception):51 52    _word_for_schema_in_error_message: ClassVar[str]53    _word_for_instance_in_error_message: ClassVar[str]54 55    def __init__(56        self,57        message: str,58        validator: str = _unset,  # type: ignore[assignment]59        path: Iterable[str | int] = (),60        cause: Exception | None = None,61        context=(),62        validator_value: Any = _unset,63        instance: Any = _unset,64        schema: Mapping[str, Any] | bool = _unset,  # type: ignore[assignment]65        schema_path: Iterable[str | int] = (),66        parent: _Error | None = None,67        type_checker: _types.TypeChecker = _unset,  # type: ignore[assignment]68    ) -> None:69        super().__init__(70            message,71            validator,72            path,73            cause,74            context,75            validator_value,76            instance,77            schema,78            schema_path,79            parent,80        )81        self.message = message82        self.path = self.relative_path = deque(path)83        self.schema_path = self.relative_schema_path = deque(schema_path)84        self.context = list(context)85        self.cause = self.__cause__ = cause86        self.validator = validator87        self.validator_value = validator_value88        self.instance = instance89        self.schema = schema90        self.parent = parent91        self._type_checker = type_checker92 93        for error in context:94            error.parent = self95 96    def __repr__(self) -> str:97        return f"<{self.__class__.__name__}: {self.message!r}>"98 99    def __str__(self) -> str:100        essential_for_verbose = (101            self.validator, self.validator_value, self.instance, self.schema,102        )103        if any(m is _unset for m in essential_for_verbose):104            return self.message105 106        schema_path = _utils.format_as_index(107            container=self._word_for_schema_in_error_message,108            indices=list(self.relative_schema_path)[:-1],109        )110        instance_path = _utils.format_as_index(111            container=self._word_for_instance_in_error_message,112            indices=self.relative_path,113        )114        prefix = 16 * " "115 116        return dedent(117            f"""\118            {self.message}119 120            Failed validating {self.validator!r} in {schema_path}:121                {_pretty(self.schema, prefix=prefix)}122 123            On {instance_path}:124                {_pretty(self.instance, prefix=prefix)}125            """.rstrip(),126        )127 128    @classmethod129    def create_from(cls, other: _Error):130        return cls(**other._contents())131 132    @property133    def absolute_path(self) -> Sequence[str | int]:134        parent = self.parent135        if parent is None:136            return self.relative_path137 138        path = deque(self.relative_path)139        path.extendleft(reversed(parent.absolute_path))140        return path141 142    @property143    def absolute_schema_path(self) -> Sequence[str | int]:144        parent = self.parent145        if parent is None:146            return self.relative_schema_path147 148        path = deque(self.relative_schema_path)149        path.extendleft(reversed(parent.absolute_schema_path))150        return path151 152    @property153    def json_path(self) -> str:154        path = "$"155        for elem in self.absolute_path:156            if isinstance(elem, int):157                path += "[" + str(elem) + "]"158            elif _JSON_PATH_COMPATIBLE_PROPERTY_PATTERN.match(elem):159                path += "." + elem160            else:161                escaped_elem = elem.replace("\\", "\\\\").replace("'", r"\'")162                path += "['" + escaped_elem + "']"163        return path164 165    def _set(166        self,167        type_checker: _types.TypeChecker | None = None,168        **kwargs: Any,169    ) -> None:170        if type_checker is not None and self._type_checker is _unset:171            self._type_checker = type_checker172 173        for k, v in kwargs.items():174            if getattr(self, k) is _unset:175                setattr(self, k, v)176 177    def _contents(self):178        attrs = (179            "message", "cause", "context", "validator", "validator_value",180            "path", "schema_path", "instance", "schema", "parent",181        )182        return {attr: getattr(self, attr) for attr in attrs}183 184    def _matches_type(self) -> bool:185        try:186            # We ignore this as we want to simply crash if this happens187            expected = self.schema["type"]  # type: ignore[index]188        except (KeyError, TypeError):189            return False190 191        if isinstance(expected, str):192            return self._type_checker.is_type(self.instance, expected)193 194        return any(195            self._type_checker.is_type(self.instance, expected_type)196            for expected_type in expected197        )198 199 200class ValidationError(_Error):201    """202    An instance was invalid under a provided schema.203    """204 205    _word_for_schema_in_error_message = "schema"206    _word_for_instance_in_error_message = "instance"207 208 209class SchemaError(_Error):210    """211    A schema was invalid under its corresponding metaschema.212    """213 214    _word_for_schema_in_error_message = "metaschema"215    _word_for_instance_in_error_message = "schema"216 217 218@define(slots=False)219class _RefResolutionError(Exception):  # noqa: PLW1641220    """221    A ref could not be resolved.222    """223 224    _DEPRECATION_MESSAGE = (225        "jsonschema.exceptions.RefResolutionError is deprecated as of version "226        "4.18.0. If you wish to catch potential reference resolution errors, "227        "directly catch referencing.exceptions.Unresolvable."228    )229 230    _cause: Exception231 232    def __eq__(self, other):233        if self.__class__ is not other.__class__:234            return NotImplemented  # pragma: no cover -- uncovered but deprecated  # noqa: E501235        return self._cause == other._cause236 237    def __str__(self) -> str:238        return str(self._cause)239 240 241class _WrappedReferencingError(_RefResolutionError, _Unresolvable):  # pragma: no cover -- partially uncovered but to be removed  # noqa: E501242    def __init__(self, cause: _Unresolvable):243        object.__setattr__(self, "_wrapped", cause)244 245    def __eq__(self, other):246        if other.__class__ is self.__class__:247            return self._wrapped == other._wrapped248        elif other.__class__ is self._wrapped.__class__:249            return self._wrapped == other250        return NotImplemented251 252    def __getattr__(self, attr):253        return getattr(self._wrapped, attr)254 255    def __hash__(self):256        return hash(self._wrapped)257 258    def __repr__(self):259        return f"<WrappedReferencingError {self._wrapped!r}>"260 261    def __str__(self):262        return f"{self._wrapped.__class__.__name__}: {self._wrapped}"263 264 265class UndefinedTypeCheck(Exception):266    """267    A type checker was asked to check a type it did not have registered.268    """269 270    def __init__(self, type: str) -> None:271        self.type = type272 273    def __str__(self) -> str:274        return f"Type {self.type!r} is unknown to this type checker"275 276 277class UnknownType(Exception):278    """279    A validator was asked to validate an instance against an unknown type.280    """281 282    def __init__(self, type, instance, schema):283        self.type = type284        self.instance = instance285        self.schema = schema286 287    def __str__(self):288        prefix = 16 * " "289 290        return dedent(291            f"""\292            Unknown type {self.type!r} for validator with schema:293                {_pretty(self.schema, prefix=prefix)}294 295            While checking instance:296                {_pretty(self.instance, prefix=prefix)}297            """.rstrip(),298        )299 300 301class FormatError(Exception):302    """303    Validating a format failed.304    """305 306    def __init__(self, message, cause=None):307        super().__init__(message, cause)308        self.message = message309        self.cause = self.__cause__ = cause310 311    def __str__(self):312        return self.message313 314 315class ErrorTree:316    """317    ErrorTrees make it easier to check which validations failed.318    """319 320    _instance = _unset321 322    def __init__(self, errors: Iterable[ValidationError] = ()):323        self.errors: MutableMapping[str, ValidationError] = {}324        self._contents: Mapping[str, ErrorTree] = defaultdict(self.__class__)325 326        for error in errors:327            container = self328            for element in error.path:329                container = container[element]330            container.errors[error.validator] = error331 332            container._instance = error.instance333 334    def __contains__(self, index: str | int):335        """336        Check whether ``instance[index]`` has any errors.337        """338        return index in self._contents339 340    def __getitem__(self, index):341        """342        Retrieve the child tree one level down at the given ``index``.343 344        If the index is not in the instance that this tree corresponds345        to and is not known by this tree, whatever error would be raised346        by ``instance.__getitem__`` will be propagated (usually this is347        some subclass of `LookupError`.348        """349        if self._instance is not _unset and index not in self:350            self._instance[index]351        return self._contents[index]352 353    def __setitem__(self, index: str | int, value: ErrorTree):354        """355        Add an error to the tree at the given ``index``.356 357        .. deprecated:: v4.20.0358 359            Setting items on an `ErrorTree` is deprecated without replacement.360            To populate a tree, provide all of its sub-errors when you361            construct the tree.362        """363        warnings.warn(364            "ErrorTree.__setitem__ is deprecated without replacement.",365            DeprecationWarning,366            stacklevel=2,367        )368        self._contents[index] = value  # type: ignore[index]369 370    def __iter__(self):371        """372        Iterate (non-recursively) over the indices in the instance with errors.373        """374        return iter(self._contents)375 376    def __len__(self):377        """378        Return the `total_errors`.379        """380        return self.total_errors381 382    def __repr__(self):383        total = len(self)384        errors = "error" if total == 1 else "errors"385        return f"<{self.__class__.__name__} ({total} total {errors})>"386 387    @property388    def total_errors(self):389        """390        The total number of errors in the entire tree, including children.391        """392        child_errors = sum(len(tree) for _, tree in self._contents.items())393        return len(self.errors) + child_errors394 395 396def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES):397    """398    Create a key function that can be used to sort errors by relevance.399 400    Arguments:401        weak (set):402            a collection of validation keywords to consider to be403            "weak".  If there are two errors at the same level of the404            instance and one is in the set of weak validation keywords,405            the other error will take priority. By default, :kw:`anyOf`406            and :kw:`oneOf` are considered weak keywords and will be407            superseded by other same-level validation errors.408 409        strong (set):410            a collection of validation keywords to consider to be411            "strong"412 413    """414 415    def relevance(error):416        validator = error.validator417        return (                        # prefer errors which are ...418            -len(error.path),           # 'deeper' and thereby more specific419            error.path,                 # earlier (for sibling errors)420            validator not in weak,      # for a non-low-priority keyword421            validator in strong,        # for a high priority keyword422            not error._matches_type(),  # at least match the instance's type423        )                               # otherwise we'll treat them the same424 425    return relevance426 427 428relevance = by_relevance()429"""430A key function (e.g. to use with `sorted`) which sorts errors by relevance.431 432Example:433 434.. code:: python435 436    sorted(validator.iter_errors(12), key=jsonschema.exceptions.relevance)437"""438 439 440def best_match(errors, key=relevance):441    """442    Try to find an error that appears to be the best match among given errors.443 444    In general, errors that are higher up in the instance (i.e. for which445    `ValidationError.path` is shorter) are considered better matches,446    since they indicate "more" is wrong with the instance.447 448    If the resulting match is either :kw:`oneOf` or :kw:`anyOf`, the449    *opposite* assumption is made -- i.e. the deepest error is picked,450    since these keywords only need to match once, and any other errors451    may not be relevant.452 453    Arguments:454        errors (collections.abc.Iterable):455 456            the errors to select from. Do not provide a mixture of457            errors from different validation attempts (i.e. from458            different instances or schemas), since it won't produce459            sensical output.460 461        key (collections.abc.Callable):462 463            the key to use when sorting errors. See `relevance` and464            transitively `by_relevance` for more details (the default is465            to sort with the defaults of that function). Changing the466            default is only useful if you want to change the function467            that rates errors but still want the error context descent468            done by this function.469 470    Returns:471        the best matching error, or ``None`` if the iterable was empty472 473    .. note::474 475        This function is a heuristic. Its return value may change for a given476        set of inputs from version to version if better heuristics are added.477 478    """479    best = max(errors, key=key, default=None)480    if best is None:481        return482 483    while best.context:484        # Calculate the minimum via nsmallest, because we don't recurse if485        # all nested errors have the same relevance (i.e. if min == max == all)486        smallest = heapq.nsmallest(2, best.context, key=key)487        if len(smallest) == 2 and key(smallest[0]) == key(smallest[1]):  # noqa: PLR2004488            return best489        best = smallest[0]490    return best491 
codekingpro/portable-devtools · Team Ai