summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2019-10-28 09:00:01 -0700
committerDavid Lord <davidism@gmail.com>2019-10-28 09:00:01 -0700
commit32027ea0e608552fda84334bff33e84209b8a056 (patch)
treed0ace44301f77a25ebab5f6f79ad99d55d202c24
parent9a7dd7b28b50fd8adc019ab2702b50ae5c6ed782 (diff)
downloadjinja2-native-template-env.tar.gz
creating a NativeTemplate creates a NativeEnvironmentnative-template-env
-rw-r--r--CHANGES.rst3
-rw-r--r--jinja2/environment.py29
-rw-r--r--jinja2/nativetypes.py14
-rw-r--r--tests/test_nativetypes.py242
4 files changed, 153 insertions, 135 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index f64047b..022da5a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -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`
diff --git a/jinja2/environment.py b/jinja2/environment.py
index b467343..3714bfe 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -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,
diff --git a/jinja2/nativetypes.py b/jinja2/nativetypes.py
index b638c91..46bc568 100644
--- a/jinja2/nativetypes.py
+++ b/jinja2/nativetypes.py
@@ -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
diff --git a/tests/test_nativetypes.py b/tests/test_nativetypes.py
index a98ac5b..6761a5f 100644
--- a/tests/test_nativetypes.py
+++ b/tests/test_nativetypes.py
@@ -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)