summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Jerdonek <chris.jerdonek@gmail.com>2012-05-03 06:38:04 -0700
committerChris Jerdonek <chris.jerdonek@gmail.com>2012-05-03 06:38:04 -0700
commit5a94c93b9ad263bf8497576ca4a5b8b0ca4cd581 (patch)
tree6bae0fb12088b2c2d70267a6eeaccb50108dddce
parent27f2ae76cff07d8d7fc8dec06bc9698e3f0c0e51 (diff)
downloadpystache-5a94c93b9ad263bf8497576ca4a5b8b0ca4cd581.tar.gz
Addressed issue #115: "Match spec expectation for partials not found"
-rw-r--r--HISTORY.rst1
m---------ext/spec0
-rw-r--r--pystache/commands/render.py3
-rw-r--r--pystache/common.py12
-rw-r--r--pystache/locator.py6
-rw-r--r--pystache/parsed.py3
-rw-r--r--pystache/parser.py15
-rw-r--r--pystache/renderengine.py3
-rw-r--r--pystache/renderer.py6
-rw-r--r--pystache/tests/common.py14
-rw-r--r--pystache/tests/test_locator.py8
-rw-r--r--pystache/tests/test_renderer.py29
-rw-r--r--pystache/tests/test_specloader.py6
13 files changed, 79 insertions, 27 deletions
diff --git a/HISTORY.rst b/HISTORY.rst
index a281594..7f9c3a2 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -5,6 +5,7 @@ History
-----------
* Added support for dot notation and version 1.1.2 of the spec (issue #99). [rbp]
+* Missing partials now render as empty string per latest version of spec (issue #115).
* Bugfix: falsey values now coerced to strings using str().
* Bugfix: lambda return values for sections no longer pushed onto context stack (issue #113).
* Bugfix: lists of lambdas for sections were not rendered (issue #114).
diff --git a/ext/spec b/ext/spec
-Subproject bf6288ed6bd0ce8ccea6f1dac070b3d779132c3
+Subproject 9b1bc7ad19247e9671304af02078f2ce3013266
diff --git a/pystache/commands/render.py b/pystache/commands/render.py
index 23b19f8..1a9c309 100644
--- a/pystache/commands/render.py
+++ b/pystache/commands/render.py
@@ -35,6 +35,7 @@ import sys
#
# ValueError: Attempted relative import in non-package
#
+from pystache.common import TemplateNotFoundError
from pystache.renderer import Renderer
@@ -78,7 +79,7 @@ def main(sys_argv=sys.argv):
try:
template = renderer.load_template(template)
- except IOError:
+ except TemplateNotFoundError:
pass
try:
diff --git a/pystache/common.py b/pystache/common.py
index 00f8a77..c1fd7a1 100644
--- a/pystache/common.py
+++ b/pystache/common.py
@@ -1,7 +1,7 @@
# coding: utf-8
"""
-Exposes common functions.
+Exposes functionality needed throughout the project.
"""
@@ -24,3 +24,13 @@ def read(path):
return f.read()
finally:
f.close()
+
+
+class PystacheError(Exception):
+ """Base class for Pystache exceptions."""
+ pass
+
+
+class TemplateNotFoundError(PystacheError):
+ """An exception raised when a template is not found."""
+ pass
diff --git a/pystache/locator.py b/pystache/locator.py
index a1f06db..2189cf2 100644
--- a/pystache/locator.py
+++ b/pystache/locator.py
@@ -9,6 +9,7 @@ import os
import re
import sys
+from pystache.common import TemplateNotFoundError
from pystache import defaults
@@ -117,9 +118,8 @@ class Locator(object):
path = self._find_path(search_dirs, file_name)
if path is None:
- # TODO: we should probably raise an exception of our own type.
- raise IOError('Template file %s not found in directories: %s' %
- (repr(file_name), repr(search_dirs)))
+ raise TemplateNotFoundError('File %s not found in dirs: %s' %
+ (repr(file_name), repr(search_dirs)))
return path
diff --git a/pystache/parsed.py b/pystache/parsed.py
index 552af55..a37565b 100644
--- a/pystache/parsed.py
+++ b/pystache/parsed.py
@@ -32,6 +32,9 @@ class ParsedTemplate(object):
"""
self._parse_tree = parse_tree
+ def __repr__(self):
+ return "[%s]" % (", ".join([repr(part) for part in self._parse_tree]))
+
def render(self, context):
"""
Returns: a string of type unicode.
diff --git a/pystache/parser.py b/pystache/parser.py
index 2b97405..4e05f3b 100644
--- a/pystache/parser.py
+++ b/pystache/parser.py
@@ -9,12 +9,13 @@ This module is only meant for internal use by the renderengine module.
import re
+from pystache.common import TemplateNotFoundError
from pystache.parsed import ParsedTemplate
-DEFAULT_DELIMITERS = ('{{', '}}')
-END_OF_LINE_CHARACTERS = ['\r', '\n']
-NON_BLANK_RE = re.compile(r'^(.)', re.M)
+DEFAULT_DELIMITERS = (u'{{', u'}}')
+END_OF_LINE_CHARACTERS = [u'\r', u'\n']
+NON_BLANK_RE = re.compile(ur'^(.)', re.M)
def _compile_template_re(delimiters=None):
@@ -215,10 +216,14 @@ class Parser(object):
elif tag_type == '>':
- template = engine.load_partial(tag_key)
+ try:
+ # TODO: make engine.load() and test it separately.
+ template = engine.load_partial(tag_key)
+ except TemplateNotFoundError:
+ template = u''
# Indent before rendering.
- template = re.sub(NON_BLANK_RE, leading_whitespace + r'\1', template)
+ template = re.sub(NON_BLANK_RE, leading_whitespace + ur'\1', template)
func = engine._make_get_partial(template)
diff --git a/pystache/renderengine.py b/pystache/renderengine.py
index 6aca336..46e1647 100644
--- a/pystache/renderengine.py
+++ b/pystache/renderengine.py
@@ -35,7 +35,8 @@ class RenderEngine(object):
load_partial: the function to call when loading a partial. The
function should accept a string template name and return a
- template string of type unicode (not a subclass).
+ template string of type unicode (not a subclass). If the
+ template is not found, it should raise a TemplateNotFoundError.
literal: the function used to convert unescaped variable tag
values to unicode, e.g. the value corresponding to a tag
diff --git a/pystache/renderer.py b/pystache/renderer.py
index 26f271f..a3d4c57 100644
--- a/pystache/renderer.py
+++ b/pystache/renderer.py
@@ -8,6 +8,7 @@ This module provides a Renderer class to render templates.
import sys
from pystache import defaults
+from pystache.common import TemplateNotFoundError
from pystache.context import ContextStack
from pystache.loader import Loader
from pystache.renderengine import RenderEngine
@@ -239,9 +240,8 @@ class Renderer(object):
template = partials.get(name)
if template is None:
- # TODO: make a TemplateNotFoundException type that provides
- # the original partials as an attribute.
- raise Exception("Partial not found with name: %s" % repr(name))
+ raise TemplateNotFoundError("Name %s not found in partials: %s" %
+ (repr(name), type(partials)))
# RenderEngine requires that the return value be unicode.
return self._to_unicode_hard(template)
diff --git a/pystache/tests/common.py b/pystache/tests/common.py
index 4c8f46c..24b24dc 100644
--- a/pystache/tests/common.py
+++ b/pystache/tests/common.py
@@ -168,6 +168,20 @@ class AssertIsMixin:
self.assertTrue(first is second, msg="%s is not %s" % (repr(first), repr(second)))
+class AssertExceptionMixin:
+
+ """A unittest.TestCase mixin adding assertException()."""
+
+ # unittest.assertRaisesRegexp() is not available until Python 2.7:
+ # http://docs.python.org/library/unittest.html#unittest.TestCase.assertRaisesRegexp
+ def assertException(self, exception_type, msg, callable, *args, **kwds):
+ try:
+ callable(*args, **kwds)
+ raise Exception("Expected exception: %s: %s" % (exception_type, repr(msg)))
+ except exception_type, err:
+ self.assertEqual(str(err), msg)
+
+
class SetupDefaults(object):
"""
diff --git a/pystache/tests/test_locator.py b/pystache/tests/test_locator.py
index 3a8b229..f17a289 100644
--- a/pystache/tests/test_locator.py
+++ b/pystache/tests/test_locator.py
@@ -11,14 +11,15 @@ import sys
import unittest
# TODO: remove this alias.
+from pystache.common import TemplateNotFoundError
from pystache.loader import Loader as Reader
from pystache.locator import Locator
-from pystache.tests.common import DATA_DIR, EXAMPLES_DIR
+from pystache.tests.common import DATA_DIR, EXAMPLES_DIR, AssertExceptionMixin
from pystache.tests.data.views import SayHello
-class LocatorTests(unittest.TestCase):
+class LocatorTests(unittest.TestCase, AssertExceptionMixin):
def _locator(self):
return Locator(search_dirs=DATA_DIR)
@@ -110,7 +111,8 @@ class LocatorTests(unittest.TestCase):
def test_find_name__non_existent_template_fails(self):
locator = Locator()
- self.assertRaises(IOError, locator.find_name, search_dirs=[], template_name='doesnt_exist')
+ self.assertException(TemplateNotFoundError, "File 'doesnt_exist.mustache' not found in dirs: []",
+ locator.find_name, search_dirs=[], template_name='doesnt_exist')
def test_find_object(self):
locator = Locator()
diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py
index 64a4325..f04c799 100644
--- a/pystache/tests/test_renderer.py
+++ b/pystache/tests/test_renderer.py
@@ -13,9 +13,10 @@ import unittest
from examples.simple import Simple
from pystache import Renderer
from pystache import TemplateSpec
+from pystache.common import TemplateNotFoundError
from pystache.loader import Loader
-from pystache.tests.common import get_data_path, AssertStringMixin
+from pystache.tests.common import get_data_path, AssertStringMixin, AssertExceptionMixin
from pystache.tests.data.views import SayHello
@@ -405,7 +406,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
# we no longer need to exercise all rendering code paths through
# the Renderer. It suffices to test rendering paths through the
# RenderEngine for the same amount of code coverage.
-class Renderer_MakeRenderEngineTests(unittest.TestCase):
+class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertExceptionMixin):
"""
Check the RenderEngine returned by Renderer._make_render_engine().
@@ -444,7 +445,20 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase):
self.assertEqual(actual, "abc")
self.assertEqual(type(actual), unicode)
- def test__load_partial__not_found(self):
+ def test__load_partial__not_found__default(self):
+ """
+ Check that load_partial provides a nice message when a template is not found.
+
+ """
+ renderer = Renderer()
+
+ engine = renderer._make_render_engine()
+ load_partial = engine.load_partial
+
+ self.assertException(TemplateNotFoundError, "File 'foo.mustache' not found in dirs: ['.']",
+ load_partial, "foo")
+
+ def test__load_partial__not_found__dict(self):
"""
Check that load_partial provides a nice message when a template is not found.
@@ -455,11 +469,10 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase):
engine = renderer._make_render_engine()
load_partial = engine.load_partial
- try:
- load_partial("foo")
- raise Exception("Shouldn't get here")
- except Exception, err:
- self.assertEqual(str(err), "Partial not found with name: 'foo'")
+ # Include dict directly since str(dict) is different in Python 2 and 3:
+ # <type 'dict'> versus <class 'dict'>, respectively.
+ self.assertException(TemplateNotFoundError, "Name 'foo' not found in partials: %s" % dict,
+ load_partial, "foo")
## Test the engine's literal attribute.
diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py
index 8332b28..24fb34d 100644
--- a/pystache/tests/test_specloader.py
+++ b/pystache/tests/test_specloader.py
@@ -16,6 +16,7 @@ from examples.lambdas import Lambdas
from examples.inverted import Inverted, InvertedLists
from pystache import Renderer
from pystache import TemplateSpec
+from pystache.common import TemplateNotFoundError
from pystache.locator import Locator
from pystache.loader import Loader
from pystache.specloader import SpecLoader
@@ -42,7 +43,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
view = Tagless()
renderer = Renderer()
- self.assertRaises(IOError, renderer.render, view)
+ self.assertRaises(TemplateNotFoundError, renderer.render, view)
# TODO: change this test to remove the following brittle line.
view.template_rel_directory = "examples"
@@ -60,7 +61,8 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
renderer1 = Renderer()
renderer2 = Renderer(search_dirs=EXAMPLES_DIR)
- self.assertRaises(IOError, renderer1.render, spec)
+ actual = renderer1.render(spec)
+ self.assertString(actual, u"Partial: ")
actual = renderer2.render(spec)
self.assertEqual(actual, "Partial: No tags...")