aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES7
-rw-r--r--doc/build/content/runtime.txt47
-rw-r--r--mako/__init__.py2
-rw-r--r--mako/codegen.py43
-rw-r--r--mako/lookup.py2
-rw-r--r--mako/template.py10
-rw-r--r--test/__init__.py9
-rw-r--r--test/test_template.py50
8 files changed, 152 insertions, 18 deletions
diff --git a/CHANGES b/CHANGES
index 65d54ce..ecc9430 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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("""