[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