From 14936312c0755f811e73e691ab495e39afc6b7d8 Mon Sep 17 00:00:00 2001 From: ThiefMaster Date: Mon, 6 Apr 2015 13:54:14 +0200 Subject: [PATCH 1/3] Let the Environment override the CodeGenerator see #404 --- docs/api.rst | 6 ++++++ jinja2/compiler.py | 3 ++- jinja2/environment.py | 6 +++++- tests/test_api.py | 22 ++++++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index d143ebc..aaf262b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -154,6 +154,12 @@ useful if you want to dig deeper into Jinja2 or :ref:`develop extensions to modify this dict. For more details see :ref:`global-namespace`. For valid object names have a look at :ref:`identifier-naming`. + .. attribute:: code_generator_class + + The class used for code generation. This should not be changed + in most cases, unless you need to modify the Python code a + template compiles to. + .. automethod:: overlay([options]) .. method:: undefined([hint, obj, name, exc]) diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 96b32f7..5cd872d 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -57,7 +57,8 @@ def generate(node, environment, name, filename, stream=None, """Generate the python source for a node tree.""" if not isinstance(node, nodes.Template): raise TypeError('Can\'t compile non template nodes') - generator = CodeGenerator(environment, name, filename, stream, defer_init) + generator = environment.code_generator_class(environment, name, filename, + stream, defer_init) generator.visit(node) if stream is None: return generator.stream.getvalue() diff --git a/jinja2/environment.py b/jinja2/environment.py index 1f6496b..3bd8fa7 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -21,7 +21,7 @@ from jinja2.lexer import get_lexer, TokenStream from jinja2.parser import Parser from jinja2.nodes import EvalContext from jinja2.optimizer import optimize -from jinja2.compiler import generate +from jinja2.compiler import generate, CodeGenerator from jinja2.runtime import Undefined, new_context from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \ TemplatesNotFound, TemplateRuntimeError @@ -238,6 +238,10 @@ class Environment(object): exception_handler = None exception_formatter = None + #: the class that is used for code generation. See + #: :class:`~jinja2.compiler.CodeGenerator` for more information. + code_generator_class = CodeGenerator + def __init__(self, block_start_string=BLOCK_START_STRING, block_end_string=BLOCK_END_STRING, diff --git a/tests/test_api.py b/tests/test_api.py index 40a6b3d..5cd7f86 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -16,6 +16,7 @@ import pytest from jinja2 import Environment, Undefined, DebugUndefined, \ StrictUndefined, UndefinedError, meta, \ is_undefined, Template, DictLoader, make_logging_undefined +from jinja2.compiler import CodeGenerator from jinja2.utils import Cycler @@ -290,3 +291,24 @@ class TestUndefined(): assert e.message == "'int object' has no attribute 'upper'" else: assert False, 'expected exception' + + +@pytest.mark.api +@pytest.mark.lowlevel +class TestLowLevel(): + + def test_custom_code_generator(self): + class CustomCodeGenerator(CodeGenerator): + def visit_Const(self, node, frame=None): + # This method is pure nonsense, but works fine for testing... + if node.value == 'foo': + self.write(repr('bar')) + else: + super(CustomCodeGenerator, self).visit_Const(node, frame) + + class CustomEnvironment(Environment): + code_generator_class = CustomCodeGenerator + + env = CustomEnvironment() + tmpl = env.from_string('{% set foo = "foo" %}{{ foo }}') + assert tmpl.render() == 'bar' From f22fdd5ffe81aab743f78290071b0aa506705533 Mon Sep 17 00:00:00 2001 From: ThiefMaster Date: Mon, 6 Apr 2015 14:08:46 +0200 Subject: [PATCH 2/3] Let the Environment override the Context closes #404 --- docs/api.rst | 7 +++++++ jinja2/environment.py | 6 +++++- jinja2/runtime.py | 3 ++- tests/test_api.py | 13 +++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index aaf262b..3fa8061 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -160,6 +160,13 @@ useful if you want to dig deeper into Jinja2 or :ref:`develop extensions in most cases, unless you need to modify the Python code a template compiles to. + .. attribute:: context_class + + The context used for templates. This should not be changed + in most cases, unless you need to modify internals of how + template variables are handled. For details, see + :class:`~jinja2.runtime.Context`. + .. automethod:: overlay([options]) .. method:: undefined([hint, obj, name, exc]) diff --git a/jinja2/environment.py b/jinja2/environment.py index 3bd8fa7..8b2572b 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -22,7 +22,7 @@ from jinja2.parser import Parser from jinja2.nodes import EvalContext from jinja2.optimizer import optimize from jinja2.compiler import generate, CodeGenerator -from jinja2.runtime import Undefined, new_context +from jinja2.runtime import Undefined, new_context, Context from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \ TemplatesNotFound, TemplateRuntimeError from jinja2.utils import import_string, LRUCache, Markup, missing, \ @@ -242,6 +242,10 @@ class Environment(object): #: :class:`~jinja2.compiler.CodeGenerator` for more information. code_generator_class = CodeGenerator + #: the context class thatis used for templates. See + #: :class:`~jinja2.runtime.Context` for more information. + context_class = Context + def __init__(self, block_start_string=BLOCK_START_STRING, block_end_string=BLOCK_END_STRING, diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 9e818df..685a12d 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -69,7 +69,8 @@ def new_context(environment, template_name, blocks, vars=None, for key, value in iteritems(locals): if key[:2] == 'l_' and value is not missing: parent[key[2:]] = value - return Context(environment, parent, template_name, blocks) + return environment.context_class(environment, parent, template_name, + blocks) class TemplateReference(object): diff --git a/tests/test_api.py b/tests/test_api.py index 5cd7f86..99d8dc1 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -17,6 +17,7 @@ from jinja2 import Environment, Undefined, DebugUndefined, \ StrictUndefined, UndefinedError, meta, \ is_undefined, Template, DictLoader, make_logging_undefined from jinja2.compiler import CodeGenerator +from jinja2.runtime import Context from jinja2.utils import Cycler @@ -312,3 +313,15 @@ class TestLowLevel(): env = CustomEnvironment() tmpl = env.from_string('{% set foo = "foo" %}{{ foo }}') assert tmpl.render() == 'bar' + + def test_custom_context(self): + class CustomContext(Context): + def resolve(self, key): + return 'resolve-' + key + + class CustomEnvironment(Environment): + context_class = CustomContext + + env = CustomEnvironment() + tmpl = env.from_string('{{ foo }}') + assert tmpl.render() == 'resolve-foo' From 8f7290a005aac399ce1747988c05cfa091eb7d3d Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 6 Apr 2015 14:27:03 +0200 Subject: [PATCH 3/3] Add changelog for #404 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index cb5e784..241a95c 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,9 @@ Version 2.8 change makes ``{% macro m(x, y=1, z) %}...{% endmacro %}`` a syntax error. The previous behavior for this code was broken anyway (resulting in the default value being applied to `y`). +- Add ability to use custom subclasses of ``jinja2.compiler.CodeGenerator`` and + ``jinja2.runtime.Context`` by adding two new attributes to the environment + (`code_generator_class` and `context_class`) (pull request ``#404``). Version 2.7.3 -------------