diff --git a/pyomo/common/errors.py b/pyomo/common/errors.py index 58139fbd5eb..617b325149b 100644 --- a/pyomo/common/errors.py +++ b/pyomo/common/errors.py @@ -117,17 +117,19 @@ class ApplicationError(Exception): An exception used when an external application generates an error. """ - pass - class PyomoException(Exception): """ Exception class for other Pyomo exceptions to inherit from, allowing Pyomo exceptions to be caught in a general way (e.g., in other applications that use Pyomo). + Subclasses can define a class-level `default_message` attribute. """ - pass + def __init__(self, *args): + if not args and getattr(self, 'default_message', None): + args = (self.default_message,) + return super().__init__(*args) class DeferredImportError(ImportError): @@ -137,8 +139,6 @@ class DeferredImportError(ImportError): """ - pass - class DeveloperError(PyomoException, NotImplementedError): """ @@ -163,8 +163,6 @@ class InfeasibleConstraintException(PyomoException): the course of range reduction). """ - pass - class IterationLimitError(PyomoException, RuntimeError): """A subclass of :py:class:`RuntimeError`, raised by an iterative method @@ -182,16 +180,12 @@ class IntervalException(PyomoException, ValueError): Exception class used for errors in interval arithmetic. """ - pass - class InvalidValueError(PyomoException, ValueError): """ Exception class used for value errors in compiled model representations """ - pass - class MouseTrap(PyomoException, NotImplementedError): """ @@ -218,8 +212,6 @@ def __str__(self): class NondifferentiableError(PyomoException, ValueError): """A Pyomo-specific ValueError raised for non-differentiable expressions""" - pass - class TempfileContextError(PyomoException, IndexError): """A Pyomo-specific IndexError raised when attempting to use the @@ -227,8 +219,6 @@ class TempfileContextError(PyomoException, IndexError): """ - pass - class TemplateExpressionError(ValueError): """Special ValueError raised by getitem for template arguments diff --git a/pyomo/common/tests/test_errors.py b/pyomo/common/tests/test_errors.py index 07ac2d85c05..6f04d597e15 100644 --- a/pyomo/common/tests/test_errors.py +++ b/pyomo/common/tests/test_errors.py @@ -10,13 +10,17 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -from pyomo.common.errors import format_exception +from pyomo.common.errors import format_exception, PyomoException class LocalException(Exception): pass +class CustomLocalException(PyomoException): + default_message = 'Default message.' + + class TestFormatException(unittest.TestCase): def test_basic_message(self): self.assertEqual(format_exception("Hello world"), "Hello world") @@ -137,3 +141,14 @@ def test_basic_message_formatted_epilog(self): " inevitably wrap onto another line.\n" "Hello world:\n This is an epilog:", ) + + +class TestPyomoException(unittest.TestCase): + def test_default_message(self): + exception = CustomLocalException() + self.assertIn("Default message.", str(exception)) + + def test_custom_message_override(self): + exception = CustomLocalException("Non-default message.") + self.assertNotIn("Default message.", str(exception)) + self.assertIn("Non-default message.", str(exception)) diff --git a/pyomo/contrib/solver/common/util.py b/pyomo/contrib/solver/common/util.py index 084b8114c66..92c5b3818e9 100644 --- a/pyomo/contrib/solver/common/util.py +++ b/pyomo/contrib/solver/common/util.py @@ -16,53 +16,47 @@ class NoFeasibleSolutionError(PyomoException): - def __init__(self): - super().__init__( - 'A feasible solution was not found, so no solution can be loaded. ' - 'Please set opt.config.load_solutions=False and check ' - 'results.solution_status and ' - 'results.incumbent_objective before loading a solution.' - ) + default_message = ( + 'A feasible solution was not found, so no solution can be loaded. ' + 'Please set opt.config.load_solutions=False and check ' + 'results.solution_status and ' + 'results.incumbent_objective before loading a solution.' + ) class NoOptimalSolutionError(PyomoException): - def __init__(self): - super().__init__( - 'Solver did not find the optimal solution. Set ' - 'opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' - ) + default_message = ( + 'Solver did not find the optimal solution. Set ' + 'opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' + ) class NoSolutionError(PyomoException): - def __init__(self): - super().__init__( - 'Solution loader does not currently have a valid solution. Please ' - 'check results.termination_condition and/or results.solution_status.' - ) + default_message = ( + 'Solution loader does not currently have a valid solution. Please ' + 'check results.termination_condition and/or results.solution_status.' + ) class NoDualsError(PyomoException): - def __init__(self): - super().__init__( - 'Solver does not currently have valid duals. Please ' - 'check results.termination_condition and/or results.solution_status.' - ) + default_message = ( + 'Solver does not currently have valid duals. Please ' + 'check results.termination_condition and/or results.solution_status.' + ) class NoReducedCostsError(PyomoException): - def __init__(self): - super().__init__( - 'Solver does not currently have valid reduced costs. Please ' - 'check results.termination_condition and/or results.solution_status.' - ) + default_message = ( + 'Solver does not currently have valid reduced costs. Please ' + 'check results.termination_condition and/or results.solution_status.' + ) class IncompatibleModelError(PyomoException): - def __init__(self): - super().__init__( - 'Model is not compatible with the chosen solver. Please check ' - 'the model and solver.' - ) + default_message = ( + 'Model is not compatible with the chosen solver. Please check ' + 'the model and solver.' + ) def get_objective(block):