summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2014-12-28 20:44:43 +0200
committerLars Wirzenius <liw@liw.fi>2014-12-28 20:44:43 +0200
commit4b82cdec0cceb61abb6d1c933212e35b38f26e29 (patch)
treebd976feda0e2383f0054b0751eeff29f335bf99e
parent80a754e894de068a1d5c71cbd7607a9db0b6589d (diff)
downloadobnam-4b82cdec0cceb61abb6d1c933212e35b38f26e29.tar.gz
Add support for long error messages
-rw-r--r--obnamlib/structurederror.py30
-rw-r--r--obnamlib/structurederror_tests.py24
2 files changed, 50 insertions, 4 deletions
diff --git a/obnamlib/structurederror.py b/obnamlib/structurederror.py
index cebaecc9..81c4d5a4 100644
--- a/obnamlib/structurederror.py
+++ b/obnamlib/structurederror.py
@@ -17,6 +17,7 @@
import hashlib
+import textwrap
class StructuredError(Exception):
@@ -44,6 +45,13 @@ class StructuredError(Exception):
translations, though that is not currently implemented. The ID
will also make translated log files more greppable.
+ The msg attribute is a format string. It can be arbitrarily long.
+ The __str__ method returns the first line only, but the full
+ message can be retrieved using the formatted() method. The
+ convention is to have the first line be a short summary of the
+ problem, and have the full message provide additional, helpful
+ information to the user.
+
The format string uses syntax according to the str.format
specification (not the old % interpolation), in order to ease
eventual migration to Python 3.
@@ -96,17 +104,33 @@ class StructuredError(Exception):
return 'R{0}X'.format(hash.upper())
def _format_msg(self, template):
+ # In case template is a docstring, remove leading whitespace
+ # from lines.
+ lines = template.splitlines(True)
+ if len(lines) == 0:
+ dedented = ''
+ else:
+ dedented = (textwrap.dedent(lines[0]) +
+ textwrap.dedent(''.join(lines[1:])))
+
try:
- formatted_msg = template.format(**self.kwargs)
+ formatted_msg = dedented.format(**self.kwargs)
except KeyError as e:
# If there were any errors in the formatting of the message,
# report them here. We do NOT want replace the actual error
# message, because that would hide information from the user.
# We do want to know there was an error, though.
formatted_msg = '{0} (PROGRAMMING ERROR: {1} {2})'.format(
- template, repr(e), repr(self.kwargs))
+ dedented, repr(e), repr(self.kwargs))
return '{0}: {1}'.format(self.id, formatted_msg)
- def __str__(self):
+ def formatted(self):
+ '''Return the full formatted message.'''
return self._format_msg(self.msg)
+
+ def __str__(self):
+ full = self.formatted()
+ lines = full.splitlines()
+ assert lines
+ return lines[0]
diff --git a/obnamlib/structurederror_tests.py b/obnamlib/structurederror_tests.py
index ae43a7e6..ef49daca 100644
--- a/obnamlib/structurederror_tests.py
+++ b/obnamlib/structurederror_tests.py
@@ -23,7 +23,11 @@ import obnamlib
class FirstError(obnamlib.StructuredError):
- msg = 'first with parameter foo set to {foo}'
+ msg = '''first with parameter foo set to {foo}
+
+ This is some explanatory text.
+
+ '''
class SecondError(obnamlib.StructuredError):
@@ -31,6 +35,12 @@ class SecondError(obnamlib.StructuredError):
msg = 'second'
+class EmptyError(obnamlib.StructuredError):
+
+ msg = ''
+
+
+
class StructuredErrorTests(unittest.TestCase):
def test_ids_differ_between_classes(self):
@@ -49,3 +59,15 @@ class StructuredErrorTests(unittest.TestCase):
def test_returns_error_string_even_with_lacking_keywords(self):
first = FirstError()
self.assertTrue(first.id in str(first))
+
+ def test_str_returns_first_line_only(self):
+ first = FirstError()
+ self.assertNotIn('\n', str(first))
+
+ def test_formatted_returns_full_message(self):
+ first = FirstError()
+ self.assertIn('\n', first.formatted())
+
+ def test_handles_empty_message_string(self):
+ empty = EmptyError()
+ self.assertTrue(str(empty).rstrip().endswith(':'))