[Checkins] SVN: z3c.pt/trunk/z3c/pt/ Added template source annotations on code stream object; when an exception is raised while rendering the template, the annotation is attached to the exception message.

Malthe Borch mborch at gmail.com
Sat Mar 22 13:28:08 EDT 2008


Log message for revision 84845:
  Added template source annotations on code stream object; when an exception is raised while rendering the template, the annotation is attached to the exception message.

Changed:
  U   z3c.pt/trunk/z3c/pt/README.txt
  U   z3c.pt/trunk/z3c/pt/clauses.py
  U   z3c.pt/trunk/z3c/pt/generation.py
  U   z3c.pt/trunk/z3c/pt/template.py
  U   z3c.pt/trunk/z3c/pt/translation.py
  U   z3c.pt/trunk/z3c/pt/translation.txt

-=-
Modified: z3c.pt/trunk/z3c/pt/README.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/README.txt	2008-03-22 15:28:44 UTC (rev 84844)
+++ z3c.pt/trunk/z3c/pt/README.txt	2008-03-22 17:28:07 UTC (rev 84845)
@@ -1,8 +1,10 @@
 z3c.pt
 ======
 
-This document demonstrates the high-level API of the package.
+This document demonstrates the high-level API of the package including
+error handling.
 
+
 Overview
 --------
 
@@ -28,6 +30,7 @@
 ``ViewTextTemplate``, ``ViewTextTemplateFile``
        See above.
 
+       
 Page template classes
 ---------------------
 
@@ -36,7 +39,7 @@
   >>> from z3c.pt import PageTemplate
   >>> template = PageTemplate("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal/python">
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
   ...   Hello World!
   ... </div>
   ... """)
@@ -59,6 +62,7 @@
 
 Keyword-parameters are passed on to the template namespace as-is.
 
+
 View template classes
 ---------------------
 
@@ -104,6 +108,7 @@
     <span>test</span>
   </div>
 
+  
 Text template classes
 ---------------------
 
@@ -126,3 +131,47 @@
   #region {
       background: #ccc;
   }
+
+
+Error handling
+--------------
+
+When an exception is raised which does not expose a bug in the TAL
+translation machinery, we expect the exception to contain the part of
+the template source that caused the exception.
+
+Exception while evaluating expression:
+
+  >>> from z3c.pt import PageTemplate
+  >>> PageTemplate("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span tal:content="range()" />
+  ... </div>""").render()
+  Traceback (most recent call last):
+    ...
+  TypeError: While rendering template, range expected at least 1 arguments, got 0 ("range()").
+
+Exception while evaluating definition:
+
+  >>> from z3c.pt import PageTemplate
+  >>> PageTemplate("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span tal:define="dummy range()" />
+  ... </div>""").render()
+  Traceback (most recent call last):
+    ...
+  TypeError: While rendering template, range expected at least 1 arguments, got 0 ("range()").
+
+Exception while evaluating interpolation:
+
+  >>> from z3c.pt import PageTemplate
+  >>> PageTemplate("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span>${range()}</span>
+  ... </div>""").render()
+  Traceback (most recent call last):
+    ...
+  TypeError: While rendering template, range expected at least 1 arguments, got 0 ("range()").

Modified: z3c.pt/trunk/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/z3c/pt/clauses.py	2008-03-22 15:28:44 UTC (rev 84844)
+++ z3c.pt/trunk/z3c/pt/clauses.py	2008-03-22 17:28:07 UTC (rev 84845)
@@ -112,6 +112,8 @@
         stream.outdent(len(self.parts)-1)
 
     def _assign(self, variable, value, stream):
+        stream.annotate(value)
+        
         if isinstance(value, types.value):
             stream.write("%s = %s" % (variable, value))
         elif isinstance(value, types.join):

Modified: z3c.pt/trunk/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/generation.py	2008-03-22 15:28:44 UTC (rev 84844)
+++ z3c.pt/trunk/z3c/pt/generation.py	2008-03-22 17:28:07 UTC (rev 84845)
@@ -7,6 +7,8 @@
 import expressions
 import utils
 
+import z3c.pt.generation
+
 wrapper = """\
 def render(%starget_language=None):
 \tglobal generation
@@ -38,16 +40,32 @@
 def initialize_traversal():
     return expressions.PathTranslation.traverse
 
+class Generator(object):
+    def __init__(self, params):
+        self.params = tuple(params)
+        self.stream = CodeIO(indentation=1, indentation_string="\t")
+
+        # initialize variable scope
+        self.stream.scope.append(set(params + ['_out']))
+
+    def __call__(self):
+        # prepare template arguments
+        args = ', '.join(self.params)
+        if args: args += ', '
+
+        code = self.stream.getvalue()
+
+        return wrapper % (args, code), {'generation': z3c.pt.generation}
+        
 class CodeIO(StringIO.StringIO):
-    """
-    A high-level I/O class to write Python code to a stream.
-    Indentation is managed using ``indent`` and ``outdent``.
+    """A I/O stream class that provides facilities to generate Python code.
 
-    Also:
-    
-    * Convenience methods for keeping track of temporary
-    variables (see ``save``, ``restore`` and ``getvariable``).
+    * Indentation is managed using ``indent`` and ``outdent``.
 
+    * Annotations can be assigned on a per-line basis using ``annotate``.
+
+    * Convenience methods for keeping track of temporary variables
+   
     * Methods to process clauses (see ``begin`` and ``end``).
     
     """
@@ -61,10 +79,12 @@
         self.indentation_string = indentation_string
         self.queue = u''
         self.scope = [set()]
+        self.annotations = {}
         
         self._variables = {}
         self.t_counter = 0
-
+        self.l_counter = 0
+        
     def save(self):
         self.t_counter += 1
         return "%s%d" % (self.t_prefix, self.t_counter)
@@ -84,29 +104,29 @@
             self.cook()
             self.indentation -= amount
 
+    def annotate(self, item):
+        self.annotations[self.l_counter] = item
+
     def out(self, string):
         self.queue += string
-            
+        
     def cook(self):
         if self.queue:
             queue = self.queue
             self.queue = ''
             self.write("_out.write('%s')" %
                        queue.replace('\n', '\\n').replace("'", "\\'"))
-        
+            
     def write(self, string):
         if isinstance(string, str):
             string = string.decode('utf-8')
+
+        self.l_counter += len(string.split('\n'))-1
         
         self.cook()
         StringIO.StringIO.write(
             self, self.indentation_string * self.indentation + string + '\n')
 
-        try:
-            self.getvalue()
-        except UnicodeDecodeError:
-            import pdb; pdb.set_trace()
-
     def getvalue(self):
         self.cook()
         return StringIO.StringIO.getvalue(self)

Modified: z3c.pt/trunk/z3c/pt/template.py
===================================================================
--- z3c.pt/trunk/z3c/pt/template.py	2008-03-22 15:28:44 UTC (rev 84844)
+++ z3c.pt/trunk/z3c/pt/template.py	2008-03-22 17:28:07 UTC (rev 84845)
@@ -1,7 +1,7 @@
 import os
 import sys
 import codegen
-
+     
 class BaseTemplate(object):
     registry = {}
     default_expression = 'python'
@@ -18,17 +18,21 @@
         return NotImplementedError("Must be implemented by subclass.")
 
     def cook(self, params):
-        source, _globals = self.translate(
+        generator = self.translate(
             self.body, params=params, default_expression=self.default_expression)
+        
+        source, _globals = generator()
+         
         suite = codegen.Suite(source)
         
         self.source = source
-
+        self.annotations = generator.stream.annotations
+        
         _globals.update(suite._globals)
         _locals = {}
 
         exec suite.code in _globals, _locals
-        
+
         return _locals['render']
 
     def render(self, **kwargs):
@@ -38,8 +42,26 @@
         if not template:
             self.registry[signature] = template = self.cook(kwargs.keys())
 
-        return template(**kwargs)
+        try:
+            return template(**kwargs)
+        except Exception, e:
+            etype, value, tb = sys.exc_info()
+            lineno = tb.tb_next.tb_lineno-1
+
+            annotations = self.annotations
+
+            while lineno >= 0:
+                if lineno in annotations:
+                    annotation = annotations.get(lineno)
+                    break
+
+                lineno -= 1
+            else:
+                annotation = None
             
+            raise e.__class__(
+                "While rendering template, %s (\"%s\")." % (str(e), str(annotation)))
+        
     def __call__(self, **kwargs):
         return self.render(**kwargs)
 

Modified: z3c.pt/trunk/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.py	2008-03-22 15:28:44 UTC (rev 84844)
+++ z3c.pt/trunk/z3c/pt/translation.py	2008-03-22 17:28:07 UTC (rev 84845)
@@ -378,26 +378,21 @@
     if None not in root.nsmap:
         raise ValueError, "Must set default namespace."
 
-    # set up code generation stream
-    stream = generation.CodeIO(indentation=1, indentation_string="\t")
-    stream.scope.append(set(params + ['_out']))
-
     # set default expression name
     key = '{http://xml.zope.org/namespaces/tal}default-expression'
     if key not in root.attrib:
         root.attrib[key] = default_expression
 
+    # set up code generation stream
+    generator = generation.Generator(params)
+    stream = generator.stream
+
     # visit root
     root.interpolate(stream)
     root.visit(stream)
 
-    # prepare template arguments
-    args = ', '.join(params)
-    if args: args += ', '
+    return generator
 
-    code = stream.getvalue()
-    return generation.wrapper % (args, code), {'generation': generation}
-
 def translate_text(body, *args, **kwargs):
     xml = parser.makeelement(
         '{http://www.w3.org/1999/xhtml}text',

Modified: z3c.pt/trunk/z3c/pt/translation.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.txt	2008-03-22 15:28:44 UTC (rev 84844)
+++ z3c.pt/trunk/z3c/pt/translation.txt	2008-03-22 17:28:07 UTC (rev 84845)
@@ -6,7 +6,8 @@
 A generic render-method is used for convenience.
 
   >>> def render(body, translator, **kwargs):
-  ...    source, _globals = translator(body)
+  ...    generator = translator(body)
+  ...    source, _globals = generator()
   ...    _locals = {}
   ...    _globals.update(kwargs)
   ...    exec source in _globals, _locals



More information about the Checkins mailing list