diff options
author | David Lord <davidism@gmail.com> | 2019-10-28 09:00:01 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2019-10-28 09:00:01 -0700 |
commit | 32027ea0e608552fda84334bff33e84209b8a056 (patch) | |
tree | d0ace44301f77a25ebab5f6f79ad99d55d202c24 | |
parent | 9a7dd7b28b50fd8adc019ab2702b50ae5c6ed782 (diff) | |
download | jinja2-native-template-env.tar.gz |
creating a NativeTemplate creates a NativeEnvironmentnative-template-env
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | jinja2/environment.py | 29 | ||||
-rw-r--r-- | jinja2/nativetypes.py | 14 | ||||
-rw-r--r-- | tests/test_nativetypes.py | 242 |
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) |