Skip to content

Commit e4c0ec0

Browse files
authored
Merge pull request #3596 from michaelbynum/string_issue
Allow default and custom messages in derived classes from `PyomoException`
2 parents ca33341 + 0629934 commit e4c0ec0

File tree

3 files changed

+47
-48
lines changed

3 files changed

+47
-48
lines changed

pyomo/common/errors.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,19 @@ class ApplicationError(Exception):
117117
An exception used when an external application generates an error.
118118
"""
119119

120-
pass
121-
122120

123121
class PyomoException(Exception):
124122
"""
125123
Exception class for other Pyomo exceptions to inherit from,
126124
allowing Pyomo exceptions to be caught in a general way
127125
(e.g., in other applications that use Pyomo).
126+
Subclasses can define a class-level `default_message` attribute.
128127
"""
129128

130-
pass
129+
def __init__(self, *args):
130+
if not args and getattr(self, 'default_message', None):
131+
args = (self.default_message,)
132+
return super().__init__(*args)
131133

132134

133135
class DeferredImportError(ImportError):
@@ -137,8 +139,6 @@ class DeferredImportError(ImportError):
137139
138140
"""
139141

140-
pass
141-
142142

143143
class DeveloperError(PyomoException, NotImplementedError):
144144
"""
@@ -163,8 +163,6 @@ class InfeasibleConstraintException(PyomoException):
163163
the course of range reduction).
164164
"""
165165

166-
pass
167-
168166

169167
class IterationLimitError(PyomoException, RuntimeError):
170168
"""A subclass of :py:class:`RuntimeError`, raised by an iterative method
@@ -182,16 +180,12 @@ class IntervalException(PyomoException, ValueError):
182180
Exception class used for errors in interval arithmetic.
183181
"""
184182

185-
pass
186-
187183

188184
class InvalidValueError(PyomoException, ValueError):
189185
"""
190186
Exception class used for value errors in compiled model representations
191187
"""
192188

193-
pass
194-
195189

196190
class MouseTrap(PyomoException, NotImplementedError):
197191
"""
@@ -218,17 +212,13 @@ def __str__(self):
218212
class NondifferentiableError(PyomoException, ValueError):
219213
"""A Pyomo-specific ValueError raised for non-differentiable expressions"""
220214

221-
pass
222-
223215

224216
class TempfileContextError(PyomoException, IndexError):
225217
"""A Pyomo-specific IndexError raised when attempting to use the
226218
TempfileManager when it does not have a currently active context.
227219
228220
"""
229221

230-
pass
231-
232222

233223
class TemplateExpressionError(ValueError):
234224
"""Special ValueError raised by getitem for template arguments

pyomo/common/tests/test_errors.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
# ___________________________________________________________________________
1111

1212
import pyomo.common.unittest as unittest
13-
from pyomo.common.errors import format_exception
13+
from pyomo.common.errors import format_exception, PyomoException
1414

1515

1616
class LocalException(Exception):
1717
pass
1818

1919

20+
class CustomLocalException(PyomoException):
21+
default_message = 'Default message.'
22+
23+
2024
class TestFormatException(unittest.TestCase):
2125
def test_basic_message(self):
2226
self.assertEqual(format_exception("Hello world"), "Hello world")
@@ -137,3 +141,14 @@ def test_basic_message_formatted_epilog(self):
137141
" inevitably wrap onto another line.\n"
138142
"Hello world:\n This is an epilog:",
139143
)
144+
145+
146+
class TestPyomoException(unittest.TestCase):
147+
def test_default_message(self):
148+
exception = CustomLocalException()
149+
self.assertIn("Default message.", str(exception))
150+
151+
def test_custom_message_override(self):
152+
exception = CustomLocalException("Non-default message.")
153+
self.assertNotIn("Default message.", str(exception))
154+
self.assertIn("Non-default message.", str(exception))

pyomo/contrib/solver/common/util.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,47 @@
1616

1717

1818
class NoFeasibleSolutionError(PyomoException):
19-
def __init__(self):
20-
super().__init__(
21-
'A feasible solution was not found, so no solution can be loaded. '
22-
'Please set opt.config.load_solutions=False and check '
23-
'results.solution_status and '
24-
'results.incumbent_objective before loading a solution.'
25-
)
19+
default_message = (
20+
'A feasible solution was not found, so no solution can be loaded. '
21+
'Please set opt.config.load_solutions=False and check '
22+
'results.solution_status and '
23+
'results.incumbent_objective before loading a solution.'
24+
)
2625

2726

2827
class NoOptimalSolutionError(PyomoException):
29-
def __init__(self):
30-
super().__init__(
31-
'Solver did not find the optimal solution. Set '
32-
'opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.'
33-
)
28+
default_message = (
29+
'Solver did not find the optimal solution. Set '
30+
'opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.'
31+
)
3432

3533

3634
class NoSolutionError(PyomoException):
37-
def __init__(self):
38-
super().__init__(
39-
'Solution loader does not currently have a valid solution. Please '
40-
'check results.termination_condition and/or results.solution_status.'
41-
)
35+
default_message = (
36+
'Solution loader does not currently have a valid solution. Please '
37+
'check results.termination_condition and/or results.solution_status.'
38+
)
4239

4340

4441
class NoDualsError(PyomoException):
45-
def __init__(self):
46-
super().__init__(
47-
'Solver does not currently have valid duals. Please '
48-
'check results.termination_condition and/or results.solution_status.'
49-
)
42+
default_message = (
43+
'Solver does not currently have valid duals. Please '
44+
'check results.termination_condition and/or results.solution_status.'
45+
)
5046

5147

5248
class NoReducedCostsError(PyomoException):
53-
def __init__(self):
54-
super().__init__(
55-
'Solver does not currently have valid reduced costs. Please '
56-
'check results.termination_condition and/or results.solution_status.'
57-
)
49+
default_message = (
50+
'Solver does not currently have valid reduced costs. Please '
51+
'check results.termination_condition and/or results.solution_status.'
52+
)
5853

5954

6055
class IncompatibleModelError(PyomoException):
61-
def __init__(self):
62-
super().__init__(
63-
'Model is not compatible with the chosen solver. Please check '
64-
'the model and solver.'
65-
)
56+
default_message = (
57+
'Model is not compatible with the chosen solver. Please check '
58+
'the model and solver.'
59+
)
6660

6761

6862
def get_objective(block):

0 commit comments

Comments
 (0)