errors.py (4215B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import sys 6 from contextlib import contextmanager 7 8 9 class ErrorMessage(Exception): 10 """Exception type raised from errors.error() and errors.fatal()""" 11 12 13 class AccumulatedErrors(Exception): 14 """Exception type raised from errors.accumulate()""" 15 16 17 class ErrorCollector: 18 """ 19 Error handling/logging class. A global instance, errors, is provided for 20 convenience. 21 22 Warnings, errors and fatal errors may be logged by calls to the following 23 functions: 24 - errors.warn(message) 25 - errors.error(message) 26 - errors.fatal(message) 27 28 Warnings only send the message on the logging output, while errors and 29 fatal errors send the message and throw an ErrorMessage exception. The 30 exception, however, may be deferred. See further below. 31 32 Errors may be ignored by calling: 33 - errors.ignore_errors() 34 35 After calling that function, only fatal errors throw an exception. 36 37 The warnings, errors or fatal errors messages may be augmented with context 38 information when a context is provided. Context is defined by a pair 39 (filename, linenumber), and may be set with errors.context() used as a 40 41 context manager: 42 43 .. code-block:: python 44 45 with errors.context(filename, linenumber): 46 errors.warn(message) 47 48 Arbitrary nesting is supported, both for errors.context calls: 49 50 .. code-block:: python 51 52 with errors.context(filename1, linenumber1): 53 errors.warn(message) 54 with errors.context(filename2, linenumber2): 55 errors.warn(message) 56 57 as well as for function calls: 58 59 .. code-block:: python 60 61 def func(): 62 errors.warn(message) 63 with errors.context(filename, linenumber): 64 func() 65 66 Errors and fatal errors can have their exception thrown at a later time, 67 allowing for several different errors to be reported at once before 68 throwing. This is achieved with errors.accumulate() as a context manager: 69 70 .. code-block:: python 71 72 with errors.accumulate(): 73 if test1: 74 errors.error(message1) 75 if test2: 76 errors.error(message2) 77 78 In such cases, a single AccumulatedErrors exception is thrown, but doesn't 79 contain information about the exceptions. The logged messages do. 80 """ 81 82 out = sys.stderr 83 WARN = 1 84 ERROR = 2 85 FATAL = 3 86 _level = ERROR 87 _context = [] 88 _count = None 89 90 def ignore_errors(self, ignore=True): 91 if ignore: 92 self._level = self.FATAL 93 else: 94 self._level = self.ERROR 95 96 def _full_message(self, level, msg): 97 if level >= self._level: 98 level = "error" 99 else: 100 level = "warning" 101 if self._context: 102 file, line = self._context[-1] 103 return "%s: %s:%d: %s" % (level, file, line, msg) 104 return "%s: %s" % (level, msg) 105 106 def _handle(self, level, msg): 107 msg = self._full_message(level, msg) 108 if level >= self._level: 109 if self._count is None: 110 raise ErrorMessage(msg) 111 self._count += 1 112 print(msg, file=self.out) 113 114 def fatal(self, msg): 115 self._handle(self.FATAL, msg) 116 117 def error(self, msg): 118 self._handle(self.ERROR, msg) 119 120 def warn(self, msg): 121 self._handle(self.WARN, msg) 122 123 def get_context(self): 124 if self._context: 125 return self._context[-1] 126 127 @contextmanager 128 def context(self, file, line): 129 if file and line: 130 self._context.append((file, line)) 131 yield 132 if file and line: 133 self._context.pop() 134 135 @contextmanager 136 def accumulate(self): 137 assert self._count is None 138 self._count = 0 139 yield 140 count = self._count 141 self._count = None 142 if count: 143 raise AccumulatedErrors() 144 145 @property 146 def count(self): 147 # _count can be None. 148 return self._count if self._count else 0 149 150 151 errors = ErrorCollector()