"""Custom exceptions for yalab-procedures.
This module defines a hierarchy of exceptions for consistent error handling
across all procedures in the yalab-procedures package.
"""
from typing import Optional
[docs]
class YALabProcedureError(Exception):
"""Base exception for all yalab-procedures errors.
All custom exceptions in this package inherit from this class,
allowing callers to catch all yalab-procedures errors with a single
except clause if desired.
"""
pass
[docs]
class ProcedureExecutionError(YALabProcedureError):
"""Raised when a procedure fails during execution.
Parameters
----------
procedure_name : str
Name of the procedure that failed.
message : str
The error message.
original_error : Optional[Exception], optional
The underlying exception that caused the failure, if any, by default None.
"""
def __init__(
self,
procedure_name: str,
message: str,
original_error: Optional[Exception] = None,
):
self.procedure_name = procedure_name
self.original_error = original_error
super().__init__(f"{procedure_name} failed: {message}")
[docs]
class ProcedureConfigurationError(YALabProcedureError):
"""Raised when procedure configuration is invalid.
This includes missing required inputs, invalid input combinations,
or configuration that cannot be used together.
"""
pass
[docs]
class OutputCollectionError(YALabProcedureError):
"""Raised when expected outputs are not found after procedure execution.
This indicates the procedure may have failed silently or produced
unexpected output structure.
"""
pass
[docs]
class DockerExecutionError(ProcedureExecutionError):
"""Raised when a Docker-based procedure fails.
Parameters
----------
procedure_name : str
Name of the procedure that failed.
container : str
The Docker image/container that was running.
exit_code : int
The exit code returned by the Docker container.
stderr : str
Standard error output from the container.
"""
def __init__(
self,
procedure_name: str,
container: str,
exit_code: int,
stderr: str,
):
self.container = container
self.exit_code = exit_code
self.stderr = stderr
message = f"Docker container '{container}' exited with code {exit_code}"
if stderr:
# Truncate very long stderr for the message
stderr_preview = stderr[:500] + "..." if len(stderr) > 500 else stderr
message += f": {stderr_preview}"
super().__init__(procedure_name, message)
[docs]
class FreeSurferLicenseError(ProcedureConfigurationError):
"""Raised when FreeSurfer license file cannot be found.
This is a specific configuration error for procedures that require
FreeSurfer and cannot locate a valid license file.
"""
def __init__(self, message: Optional[str] = None):
if message is None:
message = (
"FreeSurfer license not found. Set FS_LICENSE or FREESURFER_HOME "
"environment variable, or provide fs_license_file parameter."
)
super().__init__(message)
[docs]
class BIDSValidationError(YALabProcedureError):
"""Raised when BIDS dataset validation fails.
This indicates the input data does not conform to BIDS specification
requirements for the procedure.
"""
pass
[docs]
class DependencyError(YALabProcedureError):
"""Raised when a required external dependency is not available.
This includes missing Docker, missing command-line tools, or
unavailable Python packages.
Parameters
----------
dependency : str
The name of the missing dependency.
message : Optional[str], optional
The error message, by default None.
"""
def __init__(self, dependency: str, message: Optional[str] = None):
self.dependency = dependency
if message is None:
message = f"Required dependency '{dependency}' is not available"
super().__init__(message)
# Alias for backwards compatibility
ProcedureError = YALabProcedureError