changeset 281:faf7fc3cbacf trunk

Better error message when trying a `py:for` loop over an object that is not iterable. Closes #60.
author cmlenz
date Mon, 09 Oct 2006 19:25:41 +0000
parents ce848f7c41f1
children 24b3cbbc1b1b
files genshi/template.py genshi/tests/template.py
diffstat 2 files changed, 52 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/genshi/template.py
+++ b/genshi/template.py
@@ -73,6 +73,19 @@
         TemplateSyntaxError.__init__(self, message, filename, lineno)
 
 
+class TemplateRuntimeError(TemplateError):
+    """Exception raised when an the evualation of a Python expression in a
+    template causes an error."""
+
+    def __init__(self, message, filename='<string>', lineno=-1, offset=-1):
+        self.msg = message
+        message = '%s (%s, line %d)' % (self.msg, filename, lineno)
+        TemplateError.__init__(self, message)
+        self.filename = filename
+        self.lineno = lineno
+        self.offset = offset
+
+
 class TemplateNotFound(TemplateError):
     """Exception raised when a specific template file could not be found."""
 
@@ -435,12 +448,16 @@
         assign = self.assign
         scope = {}
         stream = list(stream)
-        for item in iter(iterable):
-            assign(scope, item)
-            ctxt.push(scope)
-            for event in _apply_directives(stream, ctxt, directives):
-                yield event
-            ctxt.pop()
+        try:
+            iterator = iter(iterable)
+            for item in iterator:
+                assign(scope, item)
+                ctxt.push(scope)
+                for event in _apply_directives(stream, ctxt, directives):
+                    yield event
+                ctxt.pop()
+        except TypeError, e:
+            raise TemplateRuntimeError(str(e), *stream[0][2])
 
     def __repr__(self):
         return '<%s>' % self.__class__.__name__
@@ -652,14 +669,14 @@
     def __call__(self, stream, ctxt, directives):
         matched, frame = ctxt._find('_choose.matched')
         if not frame:
-            raise TemplateSyntaxError('"when" directives can only be used '
-                                      'inside a "choose" directive',
-                                      *stream.next()[2])
+            raise TemplateRuntimeError('"when" directives can only be used '
+                                       'inside a "choose" directive',
+                                       *stream.next()[2])
         if matched:
             return []
         if not self.expr:
-            raise TemplateSyntaxError('"when" directive has no test condition',
-                                      *stream.next()[2])
+            raise TemplateRuntimeError('"when" directive has no test condition',
+                                       *stream.next()[2])
         value = self.expr.evaluate(ctxt)
         if '_choose.value' in frame:
             matched = (value == frame['_choose.value'])
@@ -681,9 +698,9 @@
     def __call__(self, stream, ctxt, directives):
         matched, frame = ctxt._find('_choose.matched')
         if not frame:
-            raise TemplateSyntaxError('an "otherwise" directive can only be '
-                                      'used inside a "choose" directive',
-                                      *stream.next()[2])
+            raise TemplateRuntimeError('an "otherwise" directive can only be '
+                                       'used inside a "choose" directive',
+                                       *stream.next()[2])
         if matched:
             return []
         frame['_choose.matched'] = True
--- a/genshi/tests/template.py
+++ b/genshi/tests/template.py
@@ -21,7 +21,8 @@
 from genshi import template
 from genshi.core import Markup, Stream
 from genshi.template import BadDirectiveError, MarkupTemplate, Template, \
-                            TemplateLoader, TemplateSyntaxError, TextTemplate
+                            TemplateLoader, TemplateRuntimeError, \
+                            TemplateSyntaxError, TextTemplate
 
 
 class AttrsDirectiveTestCase(unittest.TestCase):
@@ -173,7 +174,7 @@
         tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/">
           <div py:when="xy" />
         </doc>""")
-        self.assertRaises(TemplateSyntaxError, str, tmpl.generate())
+        self.assertRaises(TemplateRuntimeError, str, tmpl.generate())
 
     def test_otherwise_outside_choose(self):
         """
@@ -183,7 +184,7 @@
         tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/">
           <div py:otherwise="" />
         </doc>""")
-        self.assertRaises(TemplateSyntaxError, str, tmpl.generate())
+        self.assertRaises(TemplateRuntimeError, str, tmpl.generate())
 
     def test_when_without_test(self):
         """
@@ -195,7 +196,7 @@
             <py:when>foo</py:when>
           </div>
         </doc>""")
-        self.assertRaises(TemplateSyntaxError, str, tmpl.generate())
+        self.assertRaises(TemplateRuntimeError, str, tmpl.generate())
 
     def test_otherwise_without_test(self):
         """
@@ -440,6 +441,22 @@
             <p>1: key=b, value=2</p>
         </doc>""", str(tmpl.generate(items=enumerate(dict(a=1, b=2).items()))))
 
+    def test_not_iterable(self):
+        """
+        Verify that assignment to nested tuples works correctly.
+        """
+        tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/">
+          <py:for each="item in foo">
+            $item
+          </py:for>
+        </doc>""", filename='test.html')
+        try:
+            list(tmpl.generate(foo=12))
+        except TemplateRuntimeError, e:
+            self.assertEqual('test.html', e.filename)
+            if sys.version_info[:2] >= (2, 4):
+                self.assertEqual(2, e.lineno)
+
 
 class IfDirectiveTestCase(unittest.TestCase):
     """Tests for the `py:if` template directive."""
Copyright (C) 2012-2017 Edgewall Software