From f5ee31f92b3a5761082bbefac386f6c37d97253b Mon Sep 17 00:00:00 2001 From: BJ Dierkes Date: Tue, 12 Jul 2016 15:02:59 -0500 Subject: [PATCH] Resolves Issue #381 --- cement/core/output.py | 60 +++++++++++++++++++++++++-------------- cement/ext/ext_jinja2.py | 20 +++++++++---- tests/ext/jinja2_tests.py | 28 ++++++++++++------ 3 files changed, 73 insertions(+), 35 deletions(-) diff --git a/cement/core/output.py b/cement/core/output.py index 02119656..6bdd2010 100644 --- a/cement/core/output.py +++ b/cement/core/output.py @@ -3,6 +3,7 @@ import os import sys import pkgutil +import re from ..core import exc, interface, handler from ..utils.misc import minimal_logger from ..utils import fs @@ -121,17 +122,19 @@ class TemplateOutputHandler(CementOutputHandler): content = open(full_path, 'r').read() LOG.debug("loaded output template from file %s" % full_path) - return content + return (content, full_path) else: LOG.debug("output template file %s does not exist" % full_path) continue - return None + return (None, None) def _load_template_from_module(self, template_path): template_module = self.app._meta.template_module template_path = template_path.lstrip('/') + full_module_path = "%s.%s" % (template_module, + re.sub('/', '.', template_path)) LOG.debug("attemping to load output template '%s' from module %s" % (template_path, template_module)) @@ -143,20 +146,40 @@ class TemplateOutputHandler(CementOutputHandler): except ImportError as e: LOG.debug("unable to import template module '%s'." % template_module) - return None + return (None, None) # get the template content try: content = pkgutil.get_data(template_module, template_path) LOG.debug("loaded output template '%s' from module %s" % (template_path, template_module)) - return content + return (content, full_module_path) except IOError as e: LOG.debug("output template '%s' does not exist in module %s" % (template_path, template_module)) - return None + return (None, None) - def load_template(self, template_path, with_location=False): + def load_template(self, template_path): + """ + Loads a template file first from ``self.app._meta.template_dirs`` and + secondly from ``self.app._meta.template_module``. The + ``template_dirs`` have presedence. + :param template_path: The secondary path of the template **after** + either ``template_module`` or ``template_dirs`` prefix (set via + ``CementApp.Meta``) + :returns: The content of the template (str) + :raises: FrameworkError if the template does not exist in either the + ``template_module`` or ``template_dirs``. + """ + res = self.load_template_with_location(template_path) + content, template_type, path = res + + # only return content for backward compatibility + return content + + # FIX ME: Should eventually replace ``load_template()`` (but that breaks + # compatibility) + def load_template_with_location(self, template_path): """ Loads a template file first from ``self.app._meta.template_dirs`` and secondly from ``self.app._meta.template_module``. The @@ -165,11 +188,9 @@ class TemplateOutputHandler(CementOutputHandler): :param template_path: The secondary path of the template **after** either ``template_module`` or ``template_dirs`` prefix (set via ``CementApp.Meta``) - :param with_location: Request the location where the template was found - ('dirs' or 'module') to be returned along with the template content. - This changes the return - :returns: The content of the template (str) - or a tuple (content, location) (str, str) if with_location=True + :returns: A tuple that includes the content of the template (str), + the type of template (str which is one of: ``directory``, or + ``module``), and the ``path`` (str) of the directory or module) :raises: FrameworkError if the template does not exist in either the ``template_module`` or ``template_dirs``. """ @@ -178,21 +199,18 @@ class TemplateOutputHandler(CementOutputHandler): template_path) # first attempt to load from file - content = self._load_template_from_file(template_path) + content, path = self._load_template_from_file(template_path) if content is None: # second attempt to load from module - content = self._load_template_from_module(template_path) - location = 'module' + content, path = self._load_template_from_module(template_path) + template_type = 'module' else: - location = 'dirs' + template_type = 'directory' # if content is None, that means we didn't find a template file in # either and that is an exception - if content is not None: - if with_location: - return content, location - else: - return content - else: + if content is None: raise exc.FrameworkError("Could not locate template: %s" % template_path) + + return (content, template_type, path) diff --git a/cement/ext/ext_jinja2.py b/cement/ext/ext_jinja2.py index 8e2eb67a..db892769 100644 --- a/cement/ext/ext_jinja2.py +++ b/cement/ext/ext_jinja2.py @@ -60,6 +60,7 @@ would then put a Jinja2 template file in """ +import sys from ..core import output from ..utils.misc import minimal_logger from jinja2 import Environment, FileSystemLoader, PackageLoader @@ -88,8 +89,9 @@ class Jinja2OutputHandler(output.TemplateOutputHandler): def __init__(self, *args, **kw): super(Jinja2OutputHandler, self).__init__(*args, **kw) + # expose Jinja2 Environment instance so that we can manipulate it - # higher in application code + # higher in application code if necessary self.env = Environment(keep_trailing_newline=True) def render(self, data_dict, template=None, **kw): @@ -108,15 +110,23 @@ class Jinja2OutputHandler(output.TemplateOutputHandler): """ LOG.debug("rendering output using '%s' as a template." % template) - content, location = self.load_template(template, with_location=True) + content, _type, path = self.load_template_with_location(template) - if location == 'dirs': + if _type == 'directory': self.env.loader = FileSystemLoader(self.app._meta.template_dirs) - elif location == 'module': + elif _type == 'module': parts = self.app._meta.template_module.rsplit('.', 1) self.env.loader = PackageLoader(parts[0], package_path=parts[1]) - tmpl = self.env.from_string(content.decode('utf-8')) + if sys.version_info[0] >= 3: + if not isinstance(content, str): + content = content.decode('utf-8') + else: + if not isinstance(content, unicode): # pragma: nocover # noqa + content = content.decode('utf-8') # pragma: nocover + + tmpl = self.env.from_string(content) + return tmpl.render(**data_dict) diff --git a/tests/ext/jinja2_tests.py b/tests/ext/jinja2_tests.py index 8cfd1d0b..7696e4fa 100644 --- a/tests/ext/jinja2_tests.py +++ b/tests/ext/jinja2_tests.py @@ -35,23 +35,33 @@ class Jinja2ExtTestCase(test.CementExtTestCase): self.eq(res, jinja2_res) def test_jinja2_filesystemloader(self): - self.app._meta.template_dirs = ['/tmp/cement_tests'] - if os.path.isdir('/tmp/cement_tests'): - rmtree('/tmp/cement_tests') - os.makedirs('/tmp/cement_tests') - tests_dir = os.path.dirname(os.path.dirname(__file__)) - copyfile( - '%s/templates/test_template_parent.jinja2' % tests_dir, - '/tmp/cement_tests/test_template_parent.jinja2') self.app.setup() + self.app._meta.template_dirs = [self.tmp_dir] + + # make sure it doesn't load from the tests directory module regardless + self.app._meta.template_module = 'some.bogus.module.path' + + tests_dir = os.path.dirname(os.path.dirname(__file__)) + + from_file = os.path.join(tests_dir, 'templates', + 'test_template_parent.jinja2') + to_file = os.path.join(self.tmp_dir, 'test_template_parent.jinja2') + copyfile(from_file, to_file) + + from_file = os.path.join(tests_dir, 'templates', + 'test_template_child.jinja2') + to_file = os.path.join(self.tmp_dir, 'test_template_child.jinja2') + copyfile(from_file, to_file) + rando = random.random() res = self.app.render(dict(foo=rando), 'test_template_child.jinja2') jinja2_res = "foo equals %s\n" % rando self.eq(res, jinja2_res) def test_jinja2_packageloader(self): - self.app._meta.template_module = 'tests.templates' self.app.setup() + self.app._meta.template_module = 'tests.templates' + self.app._meta.template_dirs = [] rando = random.random() res = self.app.render(dict(foo=rando), 'test_template_child.jinja2') jinja2_res = "foo equals %s\n" % rando