#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Timestamp: "2025-12-21"
# File: /home/ywatanabe/proj/scitex-code/src/scitex/logging/_errors.py
"""Error classes for SciTeX.
All SciTeX exceptions are defined here for unified error handling.
Re-exported from scitex.logging for backwards compatibility.
Usage:
from scitex_logging import SciTeXError, SaveError, LoadError
# or (backwards compatible)
from scitex_logging import SciTeXError, SaveError, LoadError
"""
from typing import Optional, Union
# =============================================================================
# Base Errors
# =============================================================================
[docs]
class SciTeXError(Exception):
"""Base Exception class for all SciTeX errors."""
[docs]
def __init__(
self,
message: str,
context: Optional[dict] = None,
suggestion: Optional[str] = None,
):
"""Initialize SciTeX error with detailed information.
Parameters
----------
message : str
The error message
context : dict, optional
Additional context information (e.g., file paths, variable values)
suggestion : str, optional
Suggested fix or action
"""
self.message = message
self.context = context or {}
self.suggestion = suggestion
# Build the full error message
error_parts = [f"SciTeX Error: {message}"]
if context:
error_parts.append("\nContext:")
for key, value in context.items():
error_parts.append(f" {key}: {value}")
if suggestion:
error_parts.append(f"\nSuggestion: {suggestion}")
super().__init__("\n".join(error_parts))
# =============================================================================
# Configuration Errors
# =============================================================================
[docs]
class ConfigurationError(SciTeXError):
"""Raised when there are issues with SciTeX configuration."""
pass
[docs]
class ConfigFileNotFoundError(ConfigurationError):
"""Raised when a required configuration file is not found."""
[docs]
def __init__(self, filepath: str):
super().__init__(
f"Configuration file not found: {filepath}",
context={"filepath": filepath},
suggestion="Ensure the configuration file exists in ./config/ directory",
)
[docs]
class ConfigKeyError(ConfigurationError):
"""Raised when a required configuration key is missing."""
[docs]
def __init__(self, key: str, available_keys: Optional[list] = None):
context = {"missing_key": key}
if available_keys:
context["available_keys"] = available_keys
super().__init__(
f"Configuration key '{key}' not found",
context=context,
suggestion=f"Add '{key}' to your configuration file or check for typos",
)
# =============================================================================
# IO Errors
# =============================================================================
[docs]
class IOError(SciTeXError):
"""Base class for input/output related errors."""
pass
[docs]
class SaveError(IOError):
"""Raised when saving data fails."""
[docs]
def __init__(self, filepath: str, reason: str):
super().__init__(
f"Failed to save to {filepath}: {reason}",
context={"filepath": filepath, "reason": reason},
suggestion="Check file permissions and disk space",
)
[docs]
class LoadError(IOError):
"""Raised when loading data fails."""
[docs]
def __init__(self, filepath: str, reason: str):
super().__init__(
f"Failed to load from {filepath}: {reason}",
context={"filepath": filepath, "reason": reason},
suggestion="Verify the file exists and is not corrupted",
)
# =============================================================================
# Scholar Module Errors
# =============================================================================
[docs]
class ScholarError(SciTeXError):
"""Base class for scholar module errors."""
pass
[docs]
class SearchError(ScholarError):
"""Raised when paper search fails."""
[docs]
def __init__(self, query: str, source: str, reason: str):
super().__init__(
f"Search failed for query '{query}' on {source}",
context={"query": query, "source": source, "reason": reason},
suggestion="Check your internet connection and API keys",
)
[docs]
class EnrichmentError(ScholarError):
"""Raised when paper enrichment fails."""
[docs]
def __init__(self, paper_title: str, reason: str):
super().__init__(
f"Failed to enrich paper: {paper_title}",
context={"paper_title": paper_title, "reason": reason},
suggestion="Verify journal information is available",
)
[docs]
class PDFDownloadError(ScholarError):
"""Raised when PDF download fails."""
[docs]
def __init__(self, url: str, reason: str):
super().__init__(
f"Failed to download PDF from {url}",
context={"url": url, "reason": reason},
suggestion="Check if the paper is open access",
)
[docs]
class DOIResolutionError(ScholarError):
"""Raised when DOI resolution fails."""
[docs]
def __init__(self, doi: str, reason: str):
super().__init__(
f"Failed to resolve DOI: {doi}",
context={"doi": doi, "reason": reason},
suggestion="Verify the DOI is correct and try again",
)
[docs]
class BibTeXEnrichmentError(ScholarError):
"""Raised when BibTeX enrichment fails."""
[docs]
def __init__(self, bibtex_file: str, reason: str):
super().__init__(
f"Failed to enrich BibTeX file: {bibtex_file}",
context={"bibtex_file": bibtex_file, "reason": reason},
suggestion="Check the BibTeX format and ensure all entries are valid",
)
[docs]
class TranslatorError(ScholarError):
"""Raised when Zotero translator operations fail."""
[docs]
def __init__(self, translator_name: str, reason: str):
super().__init__(
f"Translator error in {translator_name}: {reason}",
context={"translator": translator_name, "reason": reason},
suggestion="Check translator compatibility and JavaScript environment",
)
[docs]
class AuthenticationError(ScholarError):
"""Raised when authentication fails."""
[docs]
def __init__(self, provider: str, reason: str = ""):
super().__init__(
f"Authentication failed for {provider}: {reason}",
context={"provider": provider, "reason": reason},
suggestion="Check your credentials and authentication settings",
)
# =============================================================================
# Plotting Errors
# =============================================================================
[docs]
class PlottingError(SciTeXError):
"""Base class for plotting-related errors."""
pass
[docs]
class AxisError(PlottingError):
"""Raised when there are issues with plot axes."""
[docs]
def __init__(self, message: str, axis_info: Optional[dict] = None):
super().__init__(
message,
context={"axis_info": axis_info} if axis_info else None,
suggestion="Check axis indices and subplot configuration",
)
# =============================================================================
# Data Processing Errors
# =============================================================================
[docs]
class DataError(SciTeXError):
"""Base class for data processing errors."""
pass
[docs]
class ShapeError(DataError):
"""Raised when data shapes are incompatible."""
[docs]
def __init__(self, expected_shape: tuple, actual_shape: tuple, operation: str):
super().__init__(
f"Shape mismatch in {operation}",
context={
"expected_shape": expected_shape,
"actual_shape": actual_shape,
"operation": operation,
},
suggestion="Reshape or transpose your data to match expected dimensions",
)
[docs]
class DTypeError(DataError):
"""Raised when data types are incompatible."""
[docs]
def __init__(self, expected_dtype: str, actual_dtype: str, operation: str):
super().__init__(
f"Data type mismatch in {operation}",
context={
"expected_dtype": expected_dtype,
"actual_dtype": actual_dtype,
"operation": operation,
},
suggestion=f"Convert data to {expected_dtype} using appropriate casting",
)
# =============================================================================
# Path Errors
# =============================================================================
[docs]
class PathError(SciTeXError):
"""Base class for path-related errors."""
pass
[docs]
class InvalidPathError(PathError):
"""Raised when a path is invalid or doesn't follow SciTeX conventions."""
[docs]
def __init__(self, path: str, reason: str):
super().__init__(
f"Invalid path: {path}",
context={"path": path, "reason": reason},
suggestion="Use relative paths starting with './' or '../'",
)
[docs]
class PathNotFoundError(PathError):
"""Raised when a required path doesn't exist."""
[docs]
def __init__(self, path: str):
super().__init__(
f"Path not found: {path}",
context={"path": path},
suggestion="Check if the path exists and is accessible",
)
# =============================================================================
# Template Errors
# =============================================================================
[docs]
class TemplateError(SciTeXError):
"""Base class for template-related errors."""
pass
[docs]
class TemplateViolationError(TemplateError):
"""Raised when SciTeX template is not followed."""
[docs]
def __init__(self, filepath: str, violation: str):
super().__init__(
f"Template violation in {filepath}: {violation}",
context={"filepath": filepath, "violation": violation},
suggestion="Follow the SciTeX template structure as defined in IMPORTANT-SCITEX-02-file-template.md",
)
# =============================================================================
# Neural Network Errors
# =============================================================================
[docs]
class NNError(SciTeXError):
"""Base class for neural network module errors."""
pass
[docs]
class ModelError(NNError):
"""Raised when there are issues with neural network models."""
[docs]
def __init__(self, model_name: str, reason: str):
super().__init__(
f"Model error in {model_name}: {reason}",
context={"model_name": model_name, "reason": reason},
)
# =============================================================================
# Statistics Errors
# =============================================================================
[docs]
class StatsError(SciTeXError):
"""Base class for statistics module errors."""
pass
[docs]
class TestError(StatsError):
"""Raised when statistical tests fail."""
[docs]
def __init__(self, test_name: str, reason: str):
super().__init__(
f"Statistical test '{test_name}' failed: {reason}",
context={"test_name": test_name, "reason": reason},
)
# =============================================================================
# Validation Helpers
# =============================================================================
[docs]
def check_path(path: str) -> None:
"""Validate a path according to SciTeX conventions."""
if not isinstance(path, str):
raise InvalidPathError(str(path), "Path must be a string")
if not (path.startswith("./") or path.startswith("../")):
raise InvalidPathError(
path, "Path must be relative and start with './' or '../'"
)
[docs]
def check_file_exists(filepath: str) -> None:
"""Check if a file exists."""
import os
if not os.path.exists(filepath):
raise PathNotFoundError(filepath)
[docs]
def check_shape_compatibility(shape1: tuple, shape2: tuple, operation: str) -> None:
"""Check if two shapes are compatible for an operation."""
if shape1 != shape2:
raise ShapeError(shape1, shape2, operation)
__all__ = [
# Base errors
"SciTeXError",
# Configuration
"ConfigurationError",
"ConfigFileNotFoundError",
"ConfigKeyError",
# IO
"IOError",
"FileFormatError",
"SaveError",
"LoadError",
# Scholar
"ScholarError",
"SearchError",
"EnrichmentError",
"PDFDownloadError",
"DOIResolutionError",
"PDFExtractionError",
"BibTeXEnrichmentError",
"TranslatorError",
"AuthenticationError",
# Plotting
"PlottingError",
"FigureNotFoundError",
"AxisError",
# Data
"DataError",
"ShapeError",
"DTypeError",
# Path
"PathError",
"InvalidPathError",
"PathNotFoundError",
# Template
"TemplateError",
"TemplateViolationError",
# Neural Network
"NNError",
"ModelError",
# Statistics
"StatsError",
"TestError",
# Validation helpers
"check_path",
"check_file_exists",
"check_shape_compatibility",
]
# EOF