diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-09 19:44:52 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-09 19:44:52 -0500 |
commit | c8598ab628a0da0d55857196627753dad9849a39 (patch) | |
tree | 70530c81a36bc5011602c92be4dcd86380d7bb77 | |
parent | f33e3cf9ec62456dae60703a373057fe3221b956 (diff) | |
download | external_python_mako-c8598ab628a0da0d55857196627753dad9849a39.tar.gz external_python_mako-c8598ab628a0da0d55857196627753dad9849a39.tar.bz2 external_python_mako-c8598ab628a0da0d55857196627753dad9849a39.zip |
- New flag on Template, TemplateLookup -
strict_undefined=True, will cause
variables not found in the context to
raise a NameError immediately, instead of
defaulting to the UNDEFINED value.
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | doc/build/content/runtime.txt | 47 | ||||
-rw-r--r-- | mako/__init__.py | 2 | ||||
-rw-r--r-- | mako/codegen.py | 43 | ||||
-rw-r--r-- | mako/lookup.py | 2 | ||||
-rw-r--r-- | mako/template.py | 10 | ||||
-rw-r--r-- | test/__init__.py | 9 | ||||
-rw-r--r-- | test/test_template.py | 50 |
8 files changed, 152 insertions, 18 deletions
@@ -1,3 +1,10 @@ +0.3.6 +- New flag on Template, TemplateLookup - + strict_undefined=True, will cause + variables not found in the context to + raise a NameError immediately, instead of + defaulting to the UNDEFINED value. + 0.3.5 - The <%namespace> tag allows expressions for the `file` argument, i.e. with ${}. diff --git a/doc/build/content/runtime.txt b/doc/build/content/runtime.txt index 61cfa5f..829b057 100644 --- a/doc/build/content/runtime.txt +++ b/doc/build/content/runtime.txt @@ -19,11 +19,48 @@ The actual buffer may or may not be the original buffer sent to the `Context` ob #### Context Variables {@name=variables} -When your template is compiled into a Python module, the body content is enclosed within a Python function called `render_body`. Other top-level defs defined in the template are defined within their own function bodies which are named after the def's name with the prefix `render_` (i.e. `render_mydef`). One of the first things that happens within these functions is that all variable names that are referenced within the function which are not defined in some other way (i.e. such as via assignment, module level imports, etc.) are pulled from the `Context` object's dictionary of variables. This is how you're able to freely reference variable names in a template which automatically correspond to what was passed into the current `Context`. - -* **What happens if I reference a variable name that is not in the current context?** - the value you get back is a special value called `UNDEFINED`. This is just a simple global variable with the class `mako.runtime.Undefined`. The `UNDEFINED` object throws an error when you call `str()` on it, which is what happens if you try to use it in an expression. -* **Why not just return None?** Using `UNDEFINED` is more explicit and allows differentiation between a value of `None` that was explicitly passed to the `Context` and a value that wasn't present at all. -* **Why raise an exception when you call str() on it ? Why not just return a blank string?** - Mako tries to stick to the Python philosophy of "explicit is better than implicit". In this case, its decided that the template author should be made to specifically handle a missing value rather than experiencing what may be a silent failure. Since `UNDEFINED` is a singleton object just like Python's `True` or `False`, you can use the `is` operator to check for it: +When your template is compiled into a Python module, the body +content is enclosed within a Python function called +`render_body`. Other top-level defs defined in the template are +defined within their own function bodies which are named after +the def's name with the prefix `render_` (i.e. `render_mydef`). +One of the first things that happens within these functions is +that all variable names that are referenced within the function +which are not defined in some other way (i.e. such as via +assignment, module level imports, etc.) are pulled from the +`Context` object's dictionary of variables. This is how you're +able to freely reference variable names in a template which +automatically correspond to what was passed into the current +`Context`. + +* **What happens if I reference a variable name that is not in + the current context?** - the value you get back is a special + value called `UNDEFINED`, or if the `strict_undefined=True` flag + is used a `NameError` is raised. `UNDEFINED` is just a simple global + variable with the class `mako.runtime.Undefined`. The + `UNDEFINED` object throws an error when you call `str()` on + it, which is what happens if you try to use it in an + expression. +* **UNDEFINED makes it hard for me to find what name is missing** - An alternative + introduced in version 0.3.6 is to specify the option + `strict_undefined=True` + to the `Template` or `TemplateLookup`. This will cause + any non-present variables to raise an immediate `NameError` + which includes the name of the variable in its message + when `render()` is called - `UNDEFINED` is not used. +* **Why not just return None?** Using `UNDEFINED`, or + raising a `NameError` is more + explicit and allows differentiation between a value of `None` + that was explicitly passed to the `Context` and a value that + wasn't present at all. +* **Why raise an exception when you call str() on it ? Why not + just return a blank string?** - Mako tries to stick to the + Python philosophy of "explicit is better than implicit". In + this case, its decided that the template author should be made + to specifically handle a missing value rather than + experiencing what may be a silent failure. Since `UNDEFINED` + is a singleton object just like Python's `True` or `False`, + you can use the `is` operator to check for it: % if someval is UNDEFINED: someval is: no value diff --git a/mako/__init__.py b/mako/__init__.py index 8914734..36a8286 100644 --- a/mako/__init__.py +++ b/mako/__init__.py @@ -5,5 +5,5 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php -__version__ = '0.3.5' +__version__ = '0.3.6' diff --git a/mako/codegen.py b/mako/codegen.py index 650eb82..1a64944 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -21,7 +21,8 @@ def compile(node, imports=None, source_encoding=None, generate_magic_comment=True, - disable_unicode=False): + disable_unicode=False, + strict_undefined=False): """Generate module source code given a parsetree node, uri, and optional source filename""" @@ -45,7 +46,8 @@ def compile(node, imports, source_encoding, generate_magic_comment, - disable_unicode), + disable_unicode, + strict_undefined), node) return buf.getvalue() @@ -58,7 +60,8 @@ class _CompileContext(object): imports, source_encoding, generate_magic_comment, - disable_unicode): + disable_unicode, + strict_undefined): self.uri = uri self.filename = filename self.default_filters = default_filters @@ -67,6 +70,7 @@ class _CompileContext(object): self.source_encoding = source_encoding self.generate_magic_comment = generate_magic_comment self.disable_unicode = disable_unicode + self.strict_undefined = strict_undefined class _GenerateRenderMethod(object): """A template visitor object which generates the @@ -406,11 +410,36 @@ class _GenerateRenderMethod(object): ) else: if getattr(self.compiler, 'has_ns_imports', False): - self.printer.writeline( - "%s = _import_ns.get(%r, context.get(%r, UNDEFINED))" % - (ident, ident, ident)) + if self.compiler.strict_undefined: + self.printer.writelines( + "%s = _import_ns.get(%r, UNDEFINED)" % + (ident, ident), + "if %s is UNDEFINED:" % ident, + "try:", + "%s = context[%r]" % (ident, ident), + "except KeyError:", + "raise NameError(\"'%s' is not defined\")" % + ident, + None, None + ) + else: + self.printer.writeline( + "%s = _import_ns.get(%r, context.get(%r, UNDEFINED))" % + (ident, ident, ident)) else: - self.printer.writeline("%s = context.get(%r, UNDEFINED)" % (ident, ident)) + if self.compiler.strict_undefined: + self.printer.writelines( + "try:", + "%s = context[%r]" % (ident, ident), + "except KeyError:", + "raise NameError(\"'%s' is not defined\")" % + ident, + None + ) + else: + self.printer.writeline( + "%s = context.get(%r, UNDEFINED)" % (ident, ident) + ) self.printer.writeline("__M_writer = context.writer()") diff --git a/mako/lookup.py b/mako/lookup.py index 8514a8a..0852355 100644 --- a/mako/lookup.py +++ b/mako/lookup.py @@ -60,6 +60,7 @@ class TemplateLookup(TemplateCollection): modulename_callable=None, default_filters=None, buffer_filters=(), + strict_undefined=False, imports=None, input_encoding=None, preprocessor=None): @@ -86,6 +87,7 @@ class TemplateLookup(TemplateCollection): 'cache_enabled':cache_enabled, 'default_filters':default_filters, 'buffer_filters':buffer_filters, + 'strict_undefined':strict_undefined, 'imports':imports, 'preprocessor':preprocessor} diff --git a/mako/template.py b/mako/template.py index ce36fa1..ba4976c 100644 --- a/mako/template.py +++ b/mako/template.py @@ -33,6 +33,7 @@ class Template(object): disable_unicode=False, default_filters=None, buffer_filters=(), + strict_undefined=False, imports=None, preprocessor=None, cache_enabled=True): @@ -68,6 +69,7 @@ class Template(object): self.output_encoding = output_encoding self.encoding_errors = encoding_errors self.disable_unicode = disable_unicode + self.strict_undefined = strict_undefined if util.py3k and disable_unicode: raise exceptions.UnsupportedError( @@ -125,7 +127,7 @@ class Template(object): self.cache_dir = cache_dir self.cache_url = cache_url self.cache_enabled = cache_enabled - + def _compile_from_file(self, path, filename): if path is not None: util.verify_directory(os.path.dirname(path)) @@ -364,7 +366,8 @@ def _compile_text(template, text, filename): imports=template.imports, source_encoding=lexer.encoding, generate_magic_comment=template.disable_unicode, - disable_unicode=template.disable_unicode) + disable_unicode=template.disable_unicode, + strict_undefined=template.strict_undefined) cid = identifier if not util.py3k and isinstance(cid, unicode): @@ -391,7 +394,8 @@ def _compile_module_file(template, text, filename, outputpath): imports=template.imports, source_encoding=lexer.encoding, generate_magic_comment=True, - disable_unicode=template.disable_unicode) + disable_unicode=template.disable_unicode, + strict_undefined=template.strict_undefined) # make tempfiles in the same location as the ultimate # location. this ensures they're on the same filesystem, diff --git a/test/__init__.py b/test/__init__.py index 77eae86..3280216 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -57,6 +57,15 @@ def teardown(): import shutil shutil.rmtree(module_base, True) +def assert_raises(except_cls, callable_, *args, **kw): + try: + callable_(*args, **kw) + success = False + except except_cls, e: + success = True + + # assert outside the block so it works for AssertionError too ! + assert success, "Callable did not raise an exception" def skip_if(predicate, reason=None): """Skip a test if predicate is true.""" diff --git a/test/test_template.py b/test/test_template.py index b713f36..ddf746a 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -7,8 +7,7 @@ from mako import exceptions, util import re, os from util import flatten_result, result_lines import codecs - -from test import TemplateTest, eq_, template_base, module_base, skip_if +from test import TemplateTest, eq_, template_base, module_base, skip_if, assert_raises class EncodingTest(TemplateTest): def test_unicode(self): @@ -544,7 +543,54 @@ class IncludeTest(TemplateTest): </%b:bar> """) assert flatten_result(lookup.get_template("c").render()) == "bar: calling bar this is a" + +class UndefinedVarsTest(TemplateTest): + def test_undefined(self): + t = Template(""" + % if x is UNDEFINED: + undefined + % else: + x: ${x} + % endif + """) + + assert result_lines(t.render(x=12)) == ["x: 12"] + assert result_lines(t.render(y=12)) == ["undefined"] + + def test_strict(self): + t = Template(""" + % if x is UNDEFINED: + undefined + % else: + x: ${x} + % endif + """, strict_undefined=True) + assert result_lines(t.render(x=12)) == ['x: 12'] + + assert_raises( + NameError, + t.render, y=12 + ) + + l = TemplateLookup(strict_undefined=True) + l.put_string("a", "some template") + l.put_string("b", """ + <%namespace name='a' file='a' import='*'/> + % if x is UNDEFINED: + undefined + % else: + x: ${x} + % endif + """) + + assert result_lines(t.render(x=12)) == ['x: 12'] + + assert_raises( + NameError, + t.render, y=12 + ) + class ControlTest(TemplateTest): def test_control(self): t = Template(""" |