From 5a94c93b9ad263bf8497576ca4a5b8b0ca4cd581 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Thu, 3 May 2012 06:38:04 -0700 Subject: Addressed issue #115: "Match spec expectation for partials not found" --- pystache/commands/render.py | 3 ++- pystache/common.py | 12 +++++++++++- pystache/locator.py | 6 +++--- pystache/parsed.py | 3 +++ pystache/parser.py | 15 ++++++++++----- pystache/renderengine.py | 3 ++- pystache/renderer.py | 6 +++--- pystache/tests/common.py | 14 ++++++++++++++ pystache/tests/test_locator.py | 8 +++++--- pystache/tests/test_renderer.py | 29 +++++++++++++++++++++-------- pystache/tests/test_specloader.py | 6 ++++-- 11 files changed, 78 insertions(+), 27 deletions(-) (limited to 'pystache') 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: + # versus , 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...") -- cgit v1.2.1