]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
creating a NativeTemplate creates a NativeEnvironment 1095/head
authorDavid Lord <davidism@gmail.com>
Mon, 28 Oct 2019 16:00:01 +0000 (09:00 -0700)
committerDavid Lord <davidism@gmail.com>
Mon, 28 Oct 2019 16:00:01 +0000 (09:00 -0700)
CHANGES.rst
jinja2/environment.py
jinja2/nativetypes.py
tests/test_nativetypes.py

index f64047bd6cf65a96ece1dece3b5e8c88e5cc7083..022da5a6d005b1f28fb2848914aa7e43343d9430 100644 (file)
@@ -55,6 +55,9 @@ Unreleased
 -   :class:`~nativetypes.NativeTemplate` correctly handles quotes
     between expressions. ``"'{{ a }}', '{{ b }}'"`` renders as the tuple
     ``('1', '2')`` rather than the string ``'1, 2'``. :issue:`1020`
+-   Creating a :class:`~nativetypes.NativeTemplate` directly creates a
+    :class:`~nativetypes.NativeEnvironment` instead of a default
+    :class:`Environment`. :issue:`1091`
 -   After calling ``LRUCache.copy()``, the copy's queue methods point to
     the correct queue. :issue:`843`
 
index b467343c41414852185f84ce93bee100fb33859d..3714bfeea15cef2f51edde75d5b9ba246b774982 100644 (file)
@@ -41,20 +41,22 @@ _spontaneous_environments = LRUCache(10)
 _make_traceback = None
 
 
-def get_spontaneous_environment(*args):
-    """Return a new spontaneous environment.  A spontaneous environment is an
-    unnamed and unaccessible (in theory) environment that is used for
-    templates generated from a string and not from the file system.
+def get_spontaneous_environment(cls, *args):
+    """Return a new spontaneous environment. A spontaneous environment
+    is used for templates created directly rather than through an
+    existing environment.
+
+    :param cls: Environment class to create.
+    :param args: Positional arguments passed to environment.
     """
+    key = (cls, args)
+
     try:
-        env = _spontaneous_environments.get(args)
-    except TypeError:
-        return Environment(*args)
-    if env is not None:
+        return _spontaneous_environments[key]
+    except KeyError:
+        _spontaneous_environments[key] = env = cls(*args)
+        env.shared = True
         return env
-    _spontaneous_environments[args] = env = Environment(*args)
-    env.shared = True
-    return env
 
 
 def create_cache(size):
@@ -918,6 +920,10 @@ class Template(object):
     StopIteration
     """
 
+    #: Type of environment to create when creating a template directly
+    #: rather than through an existing environment.
+    environment_class = Environment
+
     def __new__(cls, source,
                 block_start_string=BLOCK_START_STRING,
                 block_end_string=BLOCK_END_STRING,
@@ -938,6 +944,7 @@ class Template(object):
                 autoescape=False,
                 enable_async=False):
         env = get_spontaneous_environment(
+            cls.environment_class,
             block_start_string, block_end_string, variable_start_string,
             variable_end_string, comment_start_string, comment_end_string,
             line_statement_prefix, line_comment_prefix, trim_blocks,
index b638c91df0335e402d7b7377dabab488380d84d1..46bc56839ac8e0a17dceda9f0e3463ba9f948f6e 100644 (file)
@@ -212,7 +212,15 @@ class NativeCodeGenerator(CodeGenerator):
             self.outdent()
 
 
+class NativeEnvironment(Environment):
+    """An environment that renders templates to native Python types."""
+
+    code_generator_class = NativeCodeGenerator
+
+
 class NativeTemplate(Template):
+    environment_class = NativeEnvironment
+
     def render(self, *args, **kwargs):
         """Render the template to produce a native Python type. If the
         result is a single node, its value is returned. Otherwise, the
@@ -231,8 +239,4 @@ class NativeTemplate(Template):
         return self.environment.handle_exception(exc_info, True)
 
 
-class NativeEnvironment(Environment):
-    """An environment that renders templates to native Python types."""
-
-    code_generator_class = NativeCodeGenerator
-    template_class = NativeTemplate
+NativeEnvironment.template_class = NativeTemplate
index a98ac5b8d1ef3b14240afa65683e706a885714f5..6761a5f46f0f062928fdd984746ce0f6189aa754 100644 (file)
@@ -3,6 +3,7 @@ import pytest
 from jinja2._compat import text_type
 from jinja2.exceptions import UndefinedError
 from jinja2.nativetypes import NativeEnvironment
+from jinja2.nativetypes import NativeTemplate
 from jinja2.runtime import Undefined
 
 
@@ -11,122 +12,125 @@ def env():
     return NativeEnvironment()
 
 
-class TestNativeEnvironment(object):
-    def test_is_defined_native_return(self, env):
-        t = env.from_string('{{ missing is defined }}')
-        assert not t.render()
-
-    def test_undefined_native_return(self, env):
-        t = env.from_string('{{ missing }}')
-        assert isinstance(t.render(), Undefined)
-
-    def test_adding_undefined_native_return(self, env):
-        t = env.from_string('{{ 3 + missing }}')
-
-        with pytest.raises(UndefinedError):
-            t.render()
-
-    def test_cast_int(self, env):
-        t = env.from_string("{{ anumber|int }}")
-        result = t.render(anumber='3')
-        assert isinstance(result, int)
-        assert result == 3
-
-    def test_list_add(self, env):
-        t = env.from_string("{{ listone + listtwo }}")
-        result = t.render(listone=['a', 'b'], listtwo=['c', 'd'])
-        assert isinstance(result, list)
-        assert result == ['a', 'b', 'c', 'd']
-
-    def test_multi_expression_add(self, env):
-        t = env.from_string("{{ listone }} + {{ listtwo }}")
-        result = t.render(listone=['a', 'b'], listtwo=['c', 'd'])
-        assert not isinstance(result, list)
-        assert result == "['a', 'b'] + ['c', 'd']"
-
-    def test_loops(self, env):
-        t = env.from_string("{% for x in listone %}{{ x }}{% endfor %}")
-        result = t.render(listone=['a', 'b', 'c', 'd'])
-        assert isinstance(result, text_type)
-        assert result == 'abcd'
-
-    def test_loops_with_ints(self, env):
-        t = env.from_string("{% for x in listone %}{{ x }}{% endfor %}")
-        result = t.render(listone=[1, 2, 3, 4])
-        assert isinstance(result, int)
-        assert result == 1234
-
-    def test_loop_look_alike(self, env):
-        t = env.from_string("{% for x in listone %}{{ x }}{% endfor %}")
-        result = t.render(listone=[1])
-        assert isinstance(result, int)
-        assert result == 1
-
-    def test_booleans(self, env):
-        t = env.from_string("{{ boolval }}")
-        result = t.render(boolval=True)
-        assert isinstance(result, bool)
-        assert result is True
-
-        t = env.from_string("{{ boolval }}")
-        result = t.render(boolval=False)
-        assert isinstance(result, bool)
-        assert result is False
-
-        t = env.from_string("{{ 1 == 1 }}")
-        result = t.render()
-        assert isinstance(result, bool)
-        assert result is True
-
-        t = env.from_string("{{ 2 + 2 == 5 }}")
-        result = t.render()
-        assert isinstance(result, bool)
-        assert result is False
-
-        t = env.from_string("{{ None == None }}")
-        result = t.render()
-        assert isinstance(result, bool)
-        assert result is True
-
-        t = env.from_string("{{ '' == None }}")
-        result = t.render()
-        assert isinstance(result, bool)
-        assert result is False
-
-    def test_variable_dunder(self, env):
-        t = env.from_string("{{ x.__class__ }}")
-        result = t.render(x=True)
-        assert isinstance(result, type)
-
-    def test_constant_dunder(self, env):
-        t = env.from_string("{{ true.__class__ }}")
-        result = t.render()
-        assert isinstance(result, type)
-
-    def test_constant_dunder_to_string(self, env):
-        t = env.from_string("{{ true.__class__|string }}")
-        result = t.render()
-        assert not isinstance(result, type)
-        assert result in ["<type 'bool'>", "<class 'bool'>"]
-
-    def test_string_literal_var(self, env):
-        t = env.from_string("[{{ 'all' }}]")
-        result = t.render()
-        assert isinstance(result, text_type)
-        assert result == "[all]"
-
-    def test_string_top_level(self, env):
-        t = env.from_string("'Jinja'")
-        result = t.render()
-        assert result == 'Jinja'
-
-    def test_tuple_of_variable_strings(self, env):
-        t = env.from_string("'{{ a }}', 'data', '{{ b }}', b'{{ c }}'")
-        result = t.render(a=1, b=2, c="bytes")
-        assert isinstance(result, tuple)
-        assert result == ("1", "data", "2", b"bytes")
-
-    def test_concat_strings_with_quotes(self, env):
-        t = env.from_string("--host='{{ host }}' --user \"{{ user }}\"")
-        result = t.render(host="localhost", user="Jinja")
-        assert result == "--host='localhost' --user \"Jinja\""
+def test_is_defined_native_return(env):
+    t = env.from_string('{{ missing is defined }}')
+    assert not t.render()
+
+
+def test_undefined_native_return(env):
+    t = env.from_string('{{ missing }}')
+    assert isinstance(t.render(), Undefined)
+
+
+def test_adding_undefined_native_return(env):
+    t = env.from_string('{{ 3 + missing }}')
+
+    with pytest.raises(UndefinedError):
+        t.render()
+
+
+def test_cast_int(env):
+    t = env.from_string("{{ value|int }}")
+    result = t.render(value='3')
+    assert isinstance(result, int)
+    assert result == 3
+
+
+def test_list_add(env):
+    t = env.from_string("{{ a + b }}")
+    result = t.render(a=['a', 'b'], b=['c', 'd'])
+    assert isinstance(result, list)
+    assert result == ['a', 'b', 'c', 'd']
+
+
+def test_multi_expression_add(env):
+    t = env.from_string("{{ a }} + {{ b }}")
+    result = t.render(a=['a', 'b'], b=['c', 'd'])
+    assert not isinstance(result, list)
+    assert result == "['a', 'b'] + ['c', 'd']"
+
+
+def test_loops(env):
+    t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
+    result = t.render(value=['a', 'b', 'c', 'd'])
+    assert isinstance(result, text_type)
+    assert result == 'abcd'
+
+
+def test_loops_with_ints(env):
+    t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
+    result = t.render(value=[1, 2, 3, 4])
+    assert isinstance(result, int)
+    assert result == 1234
+
+
+def test_loop_look_alike(env):
+    t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
+    result = t.render(value=[1])
+    assert isinstance(result, int)
+    assert result == 1
+
+
+@pytest.mark.parametrize(("source", "expect"), (
+    ("{{ value }}", True),
+    ("{{ value }}", False),
+    ("{{ 1 == 1 }}", True),
+    ("{{ 2 + 2 == 5 }}", False),
+    ("{{ None is none }}", True),
+    ("{{ '' == None }}", False),
+))
+def test_booleans(env, source, expect):
+    t = env.from_string(source)
+    result = t.render(value=expect)
+    assert isinstance(result, bool)
+    assert result is expect
+
+
+def test_variable_dunder(env):
+    t = env.from_string("{{ x.__class__ }}")
+    result = t.render(x=True)
+    assert isinstance(result, type)
+
+
+def test_constant_dunder(env):
+    t = env.from_string("{{ true.__class__ }}")
+    result = t.render()
+    assert isinstance(result, type)
+
+
+def test_constant_dunder_to_string(env):
+    t = env.from_string("{{ true.__class__|string }}")
+    result = t.render()
+    assert not isinstance(result, type)
+    assert result in {"<type 'bool'>", "<class 'bool'>"}
+
+
+def test_string_literal_var(env):
+    t = env.from_string("[{{ 'all' }}]")
+    result = t.render()
+    assert isinstance(result, text_type)
+    assert result == "[all]"
+
+
+def test_string_top_level(env):
+    t = env.from_string("'Jinja'")
+    result = t.render()
+    assert result == 'Jinja'
+
+
+def test_tuple_of_variable_strings(env):
+    t = env.from_string("'{{ a }}', 'data', '{{ b }}', b'{{ c }}'")
+    result = t.render(a=1, b=2, c="bytes")
+    assert isinstance(result, tuple)
+    assert result == ("1", "data", "2", b"bytes")
+
+
+def test_concat_strings_with_quotes(env):
+    t = env.from_string("--host='{{ host }}' --user \"{{ user }}\"")
+    result = t.render(host="localhost", user="Jinja")
+    assert result == "--host='localhost' --user \"Jinja\""
+
+
+def test_spontaneous_env():
+    t = NativeTemplate("{{ true }}")
+    assert isinstance(t.environment, NativeEnvironment)