"""Módulo que contiene excepciones personalizadas para PySAG.
Este módulo define excepciones específicas para manejar errores en algoritmos genéticos.
"""
from typing import Any, Dict, List, Optional, Type, TypeVar, Union, get_origin
# Tipo genérico para los valores de los errores
T = TypeVar("T")
[documentos]
class PySAGError(Exception):
"""Clase base para todas las excepciones de PySAG.
Args:
message: Mensaje descriptivo del error.
details: Información adicional sobre el error.
"""
[documentos]
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
"""Inicializa la excepción con un mensaje y detalles opcionales."""
self.message = message
self.details = details or {}
super().__init__(self.message)
[documentos]
def __str__(self) -> str:
"""Representación en cadena de la excepción."""
if self.details:
return f"{self.message} | Detalles: {self.details}"
return self.message
[documentos]
class GeneticAlgorithmError(PySAGError):
"""Excepción base para errores en el algoritmo genético."""
pass
[documentos]
class InitializationError(GeneticAlgorithmError):
"""Se produce cuando hay un error en la inicialización de la población."""
pass
[documentos]
class SelectionError(GeneticAlgorithmError):
"""Se produce cuando hay un error en la selección de individuos."""
pass
[documentos]
class CrossoverError(GeneticAlgorithmError):
"""Se produce cuando hay un error en el operador de cruce."""
pass
[documentos]
class MutationError(GeneticAlgorithmError):
"""Se produce cuando hay un error en el operador de mutación."""
pass
[documentos]
class FitnessEvaluationError(GeneticAlgorithmError):
"""Se produce cuando hay un error en la evaluación de la función de aptitud."""
pass
[documentos]
class ParameterError(PySAGError):
"""Se produce cuando hay un error en los parámetros de entrada."""
pass
[documentos]
class ValidationError(PySAGError):
"""Se produce cuando falla la validación de un valor o parámetro.
Args:
param_name: Nombre del parámetro que falló la validación.
param_value: Valor que falló la validación.
expected: Descripción del valor esperado.
""" # D205, D400 corregidos aquí
[documentos]
def __init__(
self, param_name: str, param_value: Any, expected: str, **kwargs: Any
) -> None:
"""
Inicializa la excepción.
Args:
param_name: Nombre del parámetro que falló la validación.
param_value: Valor que falló la validación.
expected: Descripción del valor esperado.
"""
message = (
f"Validación fallida para el parámetro '{param_name}'. "
f"Se recibió: {param_value}. Se esperaba: {expected}"
)
details = {
"param_name": param_name,
"param_value": str(param_value),
"expected": expected,
**kwargs,
}
super().__init__(message, details)
[documentos]
class RangeError(ValidationError):
"""Se produce cuando un valor está fuera del rango permitido."""
[documentos]
def __init__(
self,
param_name: str,
param_value: Union[int, float],
min_val: Optional[Union[int, float]] = None,
max_val: Optional[Union[int, float]] = None,
inclusive: bool = True,
details: Optional[str] = None,
) -> None:
"""
Inicializa la excepción.
Args:
param_name: Nombre del parámetro que falló la validación.
param_value: Valor que falló la validación.
min_val: Valor mínimo permitido (inclusive).
max_val: Valor máximo permitido (inclusive).
inclusive: Si True, el rango es inclusivo.
details: Información adicional sobre el error.
"""
range_desc_parts = []
if min_val is not None:
range_desc_parts.append(f"{'>=' if inclusive else '>'} {min_val}")
if max_val is not None:
range_desc_parts.append(f"{'<=' if inclusive else '<'} {max_val}")
range_desc = (
" y ".join(range_desc_parts) if range_desc_parts else "un valor válido"
)
expected = f"valor en el rango: {range_desc}"
if details:
expected += f" ({details})"
super().__init__(
param_name=param_name,
param_value=param_value,
expected=expected,
min_val=min_val,
max_val=max_val,
inclusive=inclusive,
)
def _format_expected_type(expected_type_val: Any) -> str:
"""Formatea un tipo esperado a una cadena legible."""
origin = get_origin(expected_type_val)
if origin: # Es un tipo genérico como List[int], NDArray[float]
return (
str(expected_type_val).replace("typing.", "").replace("numpy.typing.", "")
)
elif hasattr(expected_type_val, "__name__"):
return expected_type_val.__name__
else:
return str(expected_type_val)
[documentos]
class TypeValidationError(ValidationError):
"""Se produce cuando un valor tiene un tipo incorrecto."""
[documentos]
def __init__(
self,
param_name: str,
param_value: Any,
expected_type: Union[Type, List[Any], Any],
) -> None:
"""
Inicializa la excepción.
Args:
param_name: Nombre del parámetro que falló la validación.
param_value: Valor que falló la validación.
expected_type: Tipo esperado o lista de tipos esperados.
"""
actual_type_name = type(param_value).__name__
if isinstance(expected_type, list):
expected_str = "uno de los tipos:\n" + ", ".join(
[_format_expected_type(t) for t in expected_type]
)
else:
expected_str = f"tipo {_format_expected_type(expected_type)}"
super().__init__(
param_name=param_name,
param_value=f"(tipo: {actual_type_name})",
expected=expected_str,
actual_type=actual_type_name,
)
[documentos]
def validate_parameter(
value: T,
name: str,
expected_type: Union[Type, List[Any], Any],
min_val: Optional[Union[int, float]] = None,
max_val: Optional[Union[int, float]] = None,
inclusive: bool = True,
) -> T:
"""
Función de validación de parámetros.
Args:
value: Valor a validar.
name: Nombre del parámetro.
expected_type: Tipo esperado o lista de tipos esperados.
min_val: Valor mínimo permitido (inclusive).
max_val: Valor máximo permitido (inclusive).
inclusive: Si True, el rango es inclusivo.
Returns:
El valor validado.
Raises:
TypeValidationError: Si el valor no cumple con el tipo esperado.
RangeError: Si el valor está fuera del rango permitido.
"""
raw_expected_types = (
[expected_type] if not isinstance(expected_type, list) else expected_type
)
instance_check_types = []
for et in raw_expected_types:
origin = get_origin(et)
if origin is not None:
instance_check_types.append(origin)
else:
instance_check_types.append(et)
if not instance_check_types or not any(
isinstance(value, t) for t in instance_check_types if t is not Any
): # type: ignore
# Si t es Any, isinstance(value, Any) no es lo que queremos.
# Any debería permitir cualquier tipo,
# así que si Any está en los tipos esperados, pasa.
# Esta condición se vuelve más compleja si 'Any'
# es uno de los múltiples tipos esperados.
# Por simplicidad, si Any es un expected_type,
# se asume que la validación de tipo pasa.
is_any_expected = any(et is Any for et in raw_expected_types) # type: ignore
if not is_any_expected:
raise TypeValidationError(name, value, expected_type)
# Validación de rango para números
if isinstance(value, (int, float)) and (min_val is not None or max_val is not None):
if min_val is not None:
if inclusive and value < min_val:
raise RangeError(
name, value, min_val=min_val, max_val=max_val, inclusive=inclusive
)
if not inclusive and value <= min_val:
raise RangeError(
name, value, min_val=min_val, max_val=max_val, inclusive=inclusive
)
if max_val is not None:
if inclusive and value > max_val:
raise RangeError(
name, value, min_val=min_val, max_val=max_val, inclusive=inclusive
)
if not inclusive and value >= max_val:
raise RangeError(
name, value, min_val=min_val, max_val=max_val, inclusive=inclusive
)
return value