Package gavo :: Package user :: Module errhandle
[frames] | no frames]

Source Code for Module gavo.user.errhandle

  1  """ 
  2  Common error handling facilities for user interface components. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import re 
 12  import sys 
 13  import textwrap 
 14  import traceback 
 15   
 16  from gavo import base 
 17  from gavo import grammars 
 18  from gavo import rsc 
 19  from gavo import utils 
 20   
 21   
22 -class _Reformatter(object):
23 """A helper class for reformatMessage. 24 """ 25 verbatimRE = re.compile("\s") 26
27 - def __init__(self):
28 self.inBuffer, self.outBuffer = [], []
29
30 - def flush(self):
31 if self.inBuffer: 32 self.outBuffer.append(textwrap.fill("\n".join(self.inBuffer), 33 break_long_words=False)) 34 self.inBuffer = []
35
36 - def feed(self, line):
37 if self.verbatimRE.match(line): 38 self.flush() 39 self.outBuffer.append(line) 40 elif not line.strip(): 41 self.flush() 42 self.outBuffer.append("") 43 else: 44 self.inBuffer.append(line)
45
46 - def get(self):
47 self.flush() 48 return "\n".join(self.outBuffer)
49
50 - def feedLines(self, lines):
51 for l in lines: 52 self.feed(l)
53 54
55 -def reformatMessage(msg):
56 """reflows a message to about 80 characters width. 57 58 Lines starting with whitespace will not be wrapped. 59 """ 60 r = _Reformatter() 61 r.feedLines(msg.split("\n")) 62 return r.get()
63 64
65 -def outputError(message):
66 if isinstance(message, str): 67 # make sure we don't fail on non-ASCII in byte strings 68 message = message.decode("ascii", "replace") 69 sys.stderr.write(message.encode( 70 base.getConfig("ui", "outputEncoding"), "replace"))
71 72
73 -def formatRow(aDict):
74 """returns a string representation of aDict. 75 76 I'm trying to make this palatable for error message output. 77 """ 78 res = [] 79 for key, value in sorted(aDict.iteritems()): 80 res.append("%s -> %s"%(key, repr(value))) 81 return "\n ".join(res)
82 83
84 -def raiseAndCatch(opts=None, output=outputError):
85 """raises the current exception and tries write a good error message 86 for it. 87 88 opts can be an object with some attribute (read the source); this 89 usually comes from user.cli's main. 90 91 output must be a function accepting a single string, defaulting to 92 something just encoding the string for the output found and dumping 93 it to stderr. 94 95 The function returns a suggested return value for the whole program. 96 """ 97 # Messages are reformatted by reformatMessage (though it's probably ok 98 # to just call output(someString) to write to the user directly. 99 # 100 # To write messages, append strings to the messages list. An empty string 101 # would produce a paragraph. Append unicode or ASCII. 102 retval = 1 103 messages = [] 104 try: 105 raise 106 except SystemExit as msg: 107 retval = msg.code 108 except KeyboardInterrupt: 109 retval = 2 110 except grammars.ParseError as msg: 111 if msg.location: 112 messages.append("Parse error at %s: %s"%(msg.location, 113 utils.safe_str(msg))) 114 else: 115 messages.append("Parse error: %s"%utils.safe_str(msg)) 116 if msg.record: 117 messages.append("") 118 messages.append("Offending input was:\n") 119 messages.append(repr(msg.record)+"\n") 120 121 except base.SourceParseError as msg: 122 messages.append("While parsing source %s, near %s:\n"%( 123 msg.source, msg.location)) 124 messages.append((msg.msg+"\n").decode("iso-8859-1", "ignore")) 125 if msg.offending: 126 messages.append("Offending literal: %s\n"%repr(msg.offending)) 127 128 except base.BadCode as msg: 129 messages.append("Bad user %s: %s\n"%(msg.codeType, str(msg.origExc))) 130 if msg.pos: 131 messages.append("(At %s)"%msg.pos) 132 133 except rsc.DBTableError as msg: 134 messages.append("While building table %s: %s"%(msg.qName, 135 msg)) 136 137 except base.MetaError as msg: 138 messages.append("While working on metadata of '%s': %s"%( 139 str(msg.carrier), 140 str(msg.__class__.__name__))) 141 if getattr(msg, "pos", None): 142 messages.append("in element starting at %s:"%msg.pos) 143 if msg.key is not None: 144 messages.append("-- key %s --"%msg.key) 145 messages.append(utils.safe_str(msg)) 146 147 except (base.ValidationError, base.ReportableError, 148 base.LiteralParseError, base.StructureError, base.NotFoundError, 149 base.MetaValidationError) as msg: 150 151 if not getattr(msg, "posInMsg", False): 152 if getattr(msg, "pos", None): 153 messages.append("At or near %s:"%msg.pos) 154 elif getattr(msg, "inFile", None): 155 messages.append("In %s:"%msg.inFile) 156 157 if getattr(msg, "row", None): 158 row = dict((key, value) for key, value in msg.row.iteritems() 159 if key and not key.startswith("_") and not key.endswith("_")) 160 messages.append(" Row\n %s"%formatRow(row)) 161 messages.append(" ") 162 messages.append(str(msg).decode("iso-8859-1", "ignore")) 163 164 except base.DataError as msg: 165 messages.append("Error in input data:") 166 messages.append(utils.safe_str(msg)) 167 168 except Exception as msg: 169 if hasattr(msg, "excRow"): 170 messages.append("Snafu in %s, %s\n"%(msg.excRow, msg.excCol)) 171 messages.append("") 172 messages.append("Oops. Unhandled exception %s.\n"%msg.__class__.__name__) 173 messages.append("Exception payload: %s"%utils.safe_str(msg)) 174 base.ui.notifyError("Uncaught exception at toplevel") 175 176 if getattr(opts, "enablePDB", False): 177 raise 178 elif getattr(opts, "alwaysTracebacks", False): 179 traceback.print_exc() 180 if messages: 181 errTx = utils.safe_str("*** Error: "+"\n".join(messages)) 182 output(reformatMessage(errTx)+"\n") 183 if getattr(opts, "showHints", True) and getattr(msg, "hint", None): 184 output(reformatMessage("Hint: "+msg.hint)+"\n") 185 186 return retval
187 188
189 -def bailOut():
190 """A fake cli operation just raising exceptions. 191 192 This is mainly for testing and development. 193 """ 194 if len(sys.argv)<2: 195 raise ValueError("Too short") 196 arg = sys.argv[1] 197 if arg=="--help": 198 raise base.Error("Hands off this. For Developers only")
199