[Checkins] SVN: Sandbox/malthe/chameleon.core/ Added error handler which adds debugging information to exceptions.

Malthe Borch mborch at gmail.com
Wed Nov 5 10:12:53 EST 2008


Log message for revision 92798:
  Added error handler which adds debugging information to exceptions.

Changed:
  U   Sandbox/malthe/chameleon.core/CHANGES.txt
  U   Sandbox/malthe/chameleon.core/src/chameleon/core/generation.py
  U   Sandbox/malthe/chameleon.core/src/chameleon/core/template.py
  U   Sandbox/malthe/chameleon.core/src/chameleon/core/template.txt
  U   Sandbox/malthe/chameleon.core/src/chameleon/core/utils.py

-=-
Modified: Sandbox/malthe/chameleon.core/CHANGES.txt
===================================================================
--- Sandbox/malthe/chameleon.core/CHANGES.txt	2008-11-05 15:06:25 UTC (rev 92797)
+++ Sandbox/malthe/chameleon.core/CHANGES.txt	2008-11-05 15:12:47 UTC (rev 92798)
@@ -4,6 +4,9 @@
 HEAD
 ~~~~
 
+- Added error handler which adds debugging information to the
+  exception object's string output (in debug-mode only). [malthe]
+
 1.0b4 (released 28/10/2008)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

Modified: Sandbox/malthe/chameleon.core/src/chameleon/core/generation.py
===================================================================
--- Sandbox/malthe/chameleon.core/src/chameleon/core/generation.py	2008-11-05 15:06:25 UTC (rev 92797)
+++ Sandbox/malthe/chameleon.core/src/chameleon/core/generation.py	2008-11-05 15:12:47 UTC (rev 92798)
@@ -69,6 +69,8 @@
     t_prefix = '_tmp'
     v_prefix = '_tmpv'
 
+    annotation = None
+    
     def __init__(self, symbols=None, encoding=None,
                  indentation=0, indentation_string="\t"):
         BufferIO.__init__(self)
@@ -110,7 +112,7 @@
             self.indentation -= amount
 
     def annotate(self, item):
-        self.annotations[self.l_counter] = item
+        self.annotation = self.annotations[self.l_counter] = item
 
     def out(self, string):
         if isinstance(string, unicode) and self.encoding:
@@ -130,11 +132,19 @@
             string = string.encode(self.encoding)
 
         self.l_counter += len(string.split('\n'))-1
-
         self.cook()
-        BufferIO.write(self,
-            self.indentation_string * self.indentation + string + '\n')
+        
+        indent = self.indentation_string * self.indentation
 
+        # if a source code annotation is set, write it as a comment
+        # prior to the source code line
+        if self.annotation:
+            BufferIO.write(
+                self, "%s# %s\n" % (indent, self.annotation))
+            self.annotation = None
+            
+        BufferIO.write(self, indent + string + '\n')
+
     def getvalue(self):
         self.cook()
         return BufferIO.getvalue(self)

Modified: Sandbox/malthe/chameleon.core/src/chameleon/core/template.py
===================================================================
--- Sandbox/malthe/chameleon.core/src/chameleon/core/template.py	2008-11-05 15:06:25 UTC (rev 92797)
+++ Sandbox/malthe/chameleon.core/src/chameleon/core/template.py	2008-11-05 15:12:47 UTC (rev 92798)
@@ -1,4 +1,7 @@
 import os
+import sys
+import utils
+import pprint
 import config
 import doctypes
 import filecache
@@ -60,8 +63,41 @@
 
     def render(self, **kwargs):
         template = self.cook_check(parameters=kwargs)
-        return template.render(**kwargs)
+        if config.DEBUG_MODE is False:
+            return template.render(**kwargs)
+        
+        try:
+            return template.render(**kwargs)
+        except:
+            tb = sys.exc_info()[-1]
+            lineno = tb.tb_next.tb_next.tb_lineno-1
+            del tb
 
+            # locate source code annotation (these are available from
+            # the template source as comments)
+            source = template.source.split('\n')
+            for i in reversed(range(lineno)):
+                if source[i].lstrip().startswith('#'):
+                    annotation = source[i].split('#', 1)[-1].lstrip()
+                    break
+            else:
+                annotation = ""
+                
+            formatted_arguments = pprint.pformat(kwargs).split('\n')
+            
+            # indent consecutive arguments for readability
+            for index, string in enumerate(formatted_arguments[1:]):
+                formatted_arguments[index+1] = " "*15 + string
+            
+            utils.reraise(
+                sys.exc_info(),
+                ("Caught exception rendering template."
+                 "\n\n"
+                 " - Expression: %s\n"
+                 " - Instance:   %s\n"
+                 " - Arguments:  %s\n"
+                 ) % (annotation, repr(self), "\n".join(formatted_arguments)))
+                      
     def render_macro(self, macro, global_scope=False, parameters={}):
         template = self.cook_check(
             parameters=parameters, macro=macro, global_scope=global_scope)

Modified: Sandbox/malthe/chameleon.core/src/chameleon/core/template.txt
===================================================================
--- Sandbox/malthe/chameleon.core/src/chameleon/core/template.txt	2008-11-05 15:06:25 UTC (rev 92797)
+++ Sandbox/malthe/chameleon.core/src/chameleon/core/template.txt	2008-11-05 15:12:47 UTC (rev 92798)
@@ -66,3 +66,61 @@
     <span>Hello, world!</span>
     </div>
   </div>
+
+Error handling
+--------------
+
+When an exception is raised during rendering, the exception is
+augmented with information useful for debugging.
+
+Note that the compiler must run in debug-mode to get this
+functionality.
+
+  >>> from chameleon.core import config
+  >>> config.DEBUG_MODE = True
+
+To test the error handler, we'll try to call a symbol `dummy`; for
+technical reasons, we must wrap this in an encoded pickle---this is
+due to our test setup.
+  
+  >>> from chameleon.core.types import value
+  >>> from base64 import encodestring
+  >>> from cPickle import dumps
+
+  >>> template = Template("""\
+  ... <div meta:replace="%s" />""" % encodestring(dumps(value('dummy()'))),
+  ... mock_parser)
+
+First, a simple example where a ``NameError`` exception is raised in a
+function which we'll call from inside the template.
+  
+  >>> def dummy():
+  ...     print i
+
+  >>> print template(dummy=dummy)
+  Traceback (most recent call last):
+    ...
+    print i
+    ...
+  RuntimeError: Caught exception rendering template.
+    ...
+    dummy()
+    ...
+  NameError: ...
+
+Note that the exception is a copy of the original exception, with a
+customized ``__str__`` method.
+
+  >>> class CustomException(Exception):
+  ...     def __init__(self, msg):
+  ...         self.msg = msg
+  
+  >>> def dummy():
+  ...     raise CustomException("This is a custom error message.")
+
+  >>> try:
+  ...    print template(dummy=dummy)
+  ... except CustomException, exc:
+  ...    print exc.msg
+  This is a custom error message.
+

Modified: Sandbox/malthe/chameleon.core/src/chameleon/core/utils.py
===================================================================
--- Sandbox/malthe/chameleon.core/src/chameleon/core/utils.py	2008-11-05 15:06:25 UTC (rev 92797)
+++ Sandbox/malthe/chameleon.core/src/chameleon/core/utils.py	2008-11-05 15:12:47 UTC (rev 92798)
@@ -253,3 +253,23 @@
 
 def py_attr(name):
     return "{%s}%s" % (config.PY_NS, name)
+
+def reraise(exc_info=(None, None, None), error_msg=""):
+    """Re-raise the latest exception given by ``exc_info`` tuple (see
+    ``sys.exc_info``) with an additional ``error_msg`` text."""
+
+    cls, exc, tb = exc_info
+
+    __dict__ = exc.__dict__
+    error_string = str(exc)
+    
+    if issubclass(cls, Exception):
+        class RuntimeError(cls):
+            def __str__(self):
+                return "%s\n%s: %s" % (
+                    error_msg, cls.__name__, error_string)
+
+        exc = RuntimeError.__new__(RuntimeError)
+        exc.__dict__.update(__dict__)            
+            
+    raise cls, exc, tb



More information about the Checkins mailing list