Source code for pyhs3.exceptions

"""
Exception classes for pyhs3.

Custom exception hierarchy for better error handling and debugging.
"""

from __future__ import annotations

from typing import Any

from pydantic import (
    ValidationError,
    ValidationInfo,
    WrapValidator,
)
from pydantic_core import ErrorDetails, InitErrorDetails, PydanticCustomError


[docs] class HS3Exception(Exception): """ Base exception class for all pyhs3-related errors. This serves as the root exception that all other pyhs3 exceptions inherit from, allowing users to catch all pyhs3-specific errors with a single except clause. """
[docs] class ExpressionParseError(HS3Exception): """ Exception raised when a mathematical expression cannot be parsed. This typically occurs when: - The expression contains invalid syntax - Unsupported mathematical operations are used - Variable names are malformed """
[docs] class ExpressionEvaluationError(HS3Exception): """ Exception raised when a parsed expression cannot be evaluated. This typically occurs when: - Required variables are missing from the evaluation context - The expression results in mathematical errors (division by zero, etc.) - PyTensor conversion fails """
class WorkspaceValidationError(HS3Exception): """ Raised when a workspace fails to validate. """ def custom_error_msg(custom_messages: dict[str, str]) -> Any: r""" Customize an error message for pydantic validation errors. See https://github.com/pydantic/pydantic/discussions/8468. Example: >>> from typing import Annotated >>> from pydantic import BaseModel >>> from pydantic.types import StringConstraints >>> NameString = Annotated[ ... str, ... StringConstraints(pattern=r"^[a-zA-Z0-9]*$"), ... custom_error_msg({"string_pattern_mismatch": "The field {field_name} can only contain letters and numbers."}), ... ] >>> class Model(BaseModel): ... name: NameString >>> Model(name="dog@123") # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... pydantic_core._pydantic_core.ValidationError: 1 validation error for Model name The field name can only contain letters and numbers. ... """ def _validator(v: Any, next_: Any, ctx: ValidationInfo) -> Any: try: return next_(v, ctx) except ValidationError as exc: new_errors: list[InitErrorDetails | ErrorDetails] = [] for error in exc.errors(): error["loc"] = error["loc"][1:] # to skip current location custom_message = custom_messages.get(error["type"]) if custom_message: err_ctx = error.get("ctx", {}).copy() # Add input and ValidationInfo data to context err_ctx["input"] = error["input"] if ctx.data: err_ctx.update(ctx.data) new_error = InitErrorDetails( type=PydanticCustomError( error["type"], custom_message, err_ctx ), loc=error["loc"], input=error["input"], ) new_errors.append(new_error) else: new_errors.append(error) raise ValidationError.from_exception_data( title=exc.title, line_errors=new_errors, # type: ignore[arg-type] ) from None return WrapValidator(_validator)