From 1eb56ef02a7fa825be99ddfb95f217a07dab1cdf Mon Sep 17 00:00:00 2001
From: Mike Bayer <mike_mp@zzzcomputing.com>
Date: Sun, 11 Nov 2012 14:38:56 -0500
Subject: [PATCH] - first pass at running a py3k compatible base in py2k as
 well. having some weird unicode issues I can't debug; the meaning of
 str.encode() seems to be changing globally somehow

---
 mako/_ast_util.py                         |   2 +-
 mako/ast.py                               |   6 +-
 mako/cache.py                             |   2 +-
 mako/codegen.py                           |  32 +-
 mako/exceptions.py                        |  16 +-
 mako/ext/babelplugin.py                   |  11 +-
 mako/ext/beaker_cache.py                  |   2 +-
 mako/ext/pygmentplugin.py                 |   4 +-
 mako/ext/turbogears.py                    |   4 +-
 mako/filters.py                           |  69 +--
 mako/lexer.py                             |  11 +-
 mako/parsetree.py                         |  22 +-
 mako/pygen.py                             |   3 +-
 mako/pyparser.py                          |  16 +-
 mako/runtime.py                           |  56 ++-
 mako/template.py                          |  29 +-
 mako/util.py                              | 107 +----
 test/__init__.py                          |  11 +-
 test/templates/foo/modtest.html.py        |   2 +-
 test/templates/subdir/foo/modtest.html.py |   2 +-
 test/test_ast.py                          |   4 +-
 test/test_babelplugin.py                  |  40 +-
 test/test_block.py                        |  14 +-
 test/test_cache.py                        |   2 +-
 test/test_call.py                         |  63 ++-
 test/test_decorators.py                   |   2 +-
 test/test_def.py                          |   8 +-
 test/test_exceptions.py                   |  63 ++-
 test/test_filters.py                      |   6 +-
 test/test_inheritance.py                  |  37 +-
 test/test_lexer.py                        | 542 +++++++++++-----------
 test/test_lookup.py                       |   4 +-
 test/test_loop.py                         |  10 +-
 test/test_lru.py                          |  61 +--
 test/test_namespace.py                    |   2 +-
 test/test_pygen.py                        |  24 +-
 test/test_template.py                     | 188 ++++----
 test/test_tgplugin.py                     |   2 +-
 test/test_util.py                         |   6 +-
 39 files changed, 718 insertions(+), 767 deletions(-)

diff --git a/mako/_ast_util.py b/mako/_ast_util.py
index a1bd54c..af29506 100644
--- a/mako/_ast_util.py
+++ b/mako/_ast_util.py
@@ -693,7 +693,7 @@ class SourceGenerator(NodeVisitor):
 
     def visit_Dict(self, node):
         self.write('{')
-        for idx, (key, value) in enumerate(zip(node.keys, node.values)):
+        for idx, (key, value) in enumerate(list(zip(node.keys, node.values))):
             if idx:
                 self.write(', ')
             self.visit(key)
diff --git a/mako/ast.py b/mako/ast.py
index 76311e9..0298a11 100644
--- a/mako/ast.py
+++ b/mako/ast.py
@@ -7,7 +7,7 @@
 """utilities for analyzing expressions and blocks of Python
 code, as well as generating Python from AST nodes"""
 
-from mako import exceptions, pyparser, util
+from mako import exceptions, pyparser, compat
 import re
 
 class PythonCode(object):
@@ -33,7 +33,7 @@ class PythonCode(object):
         # - AST is less likely to break with version changes
         # (for example, the behavior of co_names changed a little bit
         # in python version 2.5)
-        if isinstance(code, basestring):
+        if isinstance(code, compat.string_types):
             expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
         else:
             expr = code
@@ -48,7 +48,7 @@ class ArgumentList(object):
         self.args = []
         self.declared_identifiers = set()
         self.undeclared_identifiers = set()
-        if isinstance(code, basestring):
+        if isinstance(code, compat.string_types):
             if re.match(r"\S", code) and not re.match(r",\s*$", code):
                 # if theres text and no trailing comma, insure its parsed
                 # as a tuple by adding a trailing comma
diff --git a/mako/cache.py b/mako/cache.py
index f50ce58..c60f0e8 100644
--- a/mako/cache.py
+++ b/mako/cache.py
@@ -64,7 +64,7 @@ class Cache(object):
     def __init__(self, template, *args):
         # check for a stale template calling the
         # constructor
-        if isinstance(template, basestring) and args:
+        if isinstance(template, str) and args:
             return
         self.template = template
         self.id = template.module.__name__
diff --git a/mako/codegen.py b/mako/codegen.py
index 25265fa..f876db8 100644
--- a/mako/codegen.py
+++ b/mako/codegen.py
@@ -11,6 +11,8 @@ import time
 import re
 from mako.pygen import PythonPrinter
 from mako import util, ast, parsetree, filters, exceptions
+from mako import compat
+
 
 MAGIC_NUMBER = 8
 
@@ -40,7 +42,7 @@ def compile(node,
     # a bytestring itself, as we will be embedding it into
     # the generated source and we don't want to coerce the
     # result into a unicode object, in "disable_unicode" mode
-    if not util.py3k and isinstance(source_encoding, unicode):
+    if not compat.py3k and isinstance(source_encoding, compat.text_type):
         source_encoding = source_encoding.encode(source_encoding)
 
 
@@ -230,7 +232,7 @@ class _GenerateRenderMethod(object):
         self.compiler.identifiers = module_identifiers
         self.printer.writeline("_exports = %r" %
                             [n.name for n in
-                            main_identifiers.topleveldefs.values()]
+                            list(main_identifiers.topleveldefs.values())]
                         )
         self.printer.write("\n\n")
 
@@ -243,7 +245,7 @@ class _GenerateRenderMethod(object):
         elif len(namespaces):
             self.write_namespaces(namespaces)
 
-        return main_identifiers.topleveldefs.values()
+        return list(main_identifiers.topleveldefs.values())
 
     def write_render_callable(self, node, name, args, buffered, filtered,
             cached):
@@ -327,8 +329,8 @@ class _GenerateRenderMethod(object):
         self.printer.writeline("def _mako_generate_namespaces(context):")
 
 
-        for node in namespaces.values():
-            if node.attributes.has_key('import'):
+        for node in list(namespaces.values()):
+            if 'import' in node.attributes:
                 self.compiler.has_ns_imports = True
             self.write_source_comment(node)
             if len(node.nodes):
@@ -436,7 +438,7 @@ class _GenerateRenderMethod(object):
         # write closure functions for closures that we define
         # right here
         to_write = to_write.union(
-                        [c.funcname for c in identifiers.closuredefs.values()])
+                        [c.funcname for c in list(identifiers.closuredefs.values())])
 
         # remove identifiers that are declared in the argument
         # signature of the callable
@@ -463,8 +465,8 @@ class _GenerateRenderMethod(object):
         if toplevel and getattr(self.compiler, 'has_ns_imports', False):
             self.printer.writeline("_import_ns = {}")
             self.compiler.has_imports = True
-            for ident, ns in self.compiler.namespaces.iteritems():
-                if ns.attributes.has_key('import'):
+            for ident, ns in self.compiler.namespaces.items():
+                if 'import' in ns.attributes:
                     self.printer.writeline(
                             "_mako_get_namespace(context, %r)."\
                                     "_populate(_import_ns, %r)" %
@@ -699,7 +701,7 @@ class _GenerateRenderMethod(object):
                 "%s, lambda:__M_%s(%s),  context, %s__M_defname=%r)" % \
                             (cachekey, name, ','.join(pass_args),
                             ''.join(["%s=%s, " % (k,v)
-                            for k, v in cache_args.items()]),
+                            for k, v in list(cache_args.items())]),
                             name
                             )
             # apply buffer_filters
@@ -713,7 +715,7 @@ class _GenerateRenderMethod(object):
                     "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))" %
                     (cachekey, name, ','.join(pass_args),
                     ''.join(["%s=%s, " % (k,v)
-                        for k, v in cache_args.items()]),
+                        for k, v in list(cache_args.items())]),
                     name,
                     ),
                     "return ''",
@@ -791,10 +793,10 @@ class _GenerateRenderMethod(object):
             #          and end control lines, and
             #    3) any control line with no content other than comments
             if not children or (
-                    util.all(isinstance(c, (parsetree.Comment,
+                    compat.all(isinstance(c, (parsetree.Comment,
                                             parsetree.ControlLine))
                              for c in children) and
-                    util.all((node.is_ternary(c.keyword) or c.isend)
+                    compat.all((node.is_ternary(c.keyword) or c.isend)
                              for c in children
                              if isinstance(c, parsetree.ControlLine))):
                 self.printer.writeline("pass")
@@ -966,7 +968,7 @@ class _Identifiers(object):
                 # things that have already been declared
                 # in an enclosing namespace (i.e. names we can just use)
                 self.declared = set(parent.declared).\
-                         union([c.name for c in parent.closuredefs.values()]).\
+                         union([c.name for c in list(parent.closuredefs.values())]).\
                          union(parent.locally_declared).\
                          union(parent.argument_declared)
 
@@ -1037,8 +1039,8 @@ class _Identifiers(object):
                     list(self.declared),
                     list(self.locally_declared),
                     list(self.undeclared),
-                    [c.name for c in self.topleveldefs.values()],
-                    [c.name for c in self.closuredefs.values()],
+                    [c.name for c in list(self.topleveldefs.values())],
+                    [c.name for c in list(self.closuredefs.values())],
                     self.argument_declared)
 
     def check_declared(self, node):
diff --git a/mako/exceptions.py b/mako/exceptions.py
index b8d5ef3..fb9b3e6 100644
--- a/mako/exceptions.py
+++ b/mako/exceptions.py
@@ -6,8 +6,10 @@
 
 """exception classes"""
 
-import traceback, sys, re
-from mako import util
+import traceback
+import sys
+import re
+from mako import util, compat
 
 class MakoException(Exception):
     pass
@@ -84,12 +86,12 @@ class RichTraceback(object):
 
     @property
     def errorname(self):
-        return util.exception_name(self.error)
+        return compat.exception_name(self.error)
 
     def _init_message(self):
         """Find a unicode representation of self.error"""
         try:
-            self.message = unicode(self.error)
+            self.message = compat.text_type(self.error)
         except UnicodeError:
             try:
                 self.message = str(self.error)
@@ -97,8 +99,8 @@ class RichTraceback(object):
                 # Fallback to args as neither unicode nor
                 # str(Exception(u'\xe6')) work in Python < 2.6
                 self.message = self.error.args[0]
-        if not isinstance(self.message, unicode):
-            self.message = unicode(self.message, 'ascii', 'replace')
+        if not isinstance(self.message, compat.text_type):
+            self.message = compat.text_type(self.message, 'ascii', 'replace')
 
     def _get_reformatted_records(self, records):
         for rec in records:
@@ -150,7 +152,7 @@ class RichTraceback(object):
                     template_filename = info.template_filename or filename
                 except KeyError:
                     # A normal .py file (not a Template)
-                    if not util.py3k:
+                    if not compat.py3k:
                         try:
                             fp = open(filename, 'rb')
                             encoding = util.parse_encoding(fp)
diff --git a/mako/ext/babelplugin.py b/mako/ext/babelplugin.py
index 085938d..b620631 100644
--- a/mako/ext/babelplugin.py
+++ b/mako/ext/babelplugin.py
@@ -5,11 +5,10 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 """gettext message extraction via Babel: http://babel.edgewall.org/"""
-from StringIO import StringIO
-
 from babel.messages.extract import extract_python
-
-from mako import lexer, parsetree, util
+from mako.compat import StringIO
+from mako import compat
+from mako import lexer, parsetree
 
 def extract(fileobj, keywords, comment_tags, options):
     """Extract messages from Mako templates.
@@ -78,7 +77,7 @@ def extract_nodes(nodes, keywords, comment_tags, options):
             code = node.body_decl.code
         elif isinstance(node, parsetree.CallNamespaceTag):
             attribs = ', '.join(['%s=%s' % (key, val)
-                                 for key, val in node.attributes.iteritems()])
+                                 for key, val in node.attributes.items()])
             code = '{%s}' % attribs
             child_nodes = node.nodes
         elif isinstance(node, parsetree.ControlLine):
@@ -108,7 +107,7 @@ def extract_nodes(nodes, keywords, comment_tags, options):
             translator_comments = \
                 [comment[1] for comment in translator_comments]
 
-        if not util.py3k and isinstance(code, unicode):
+        if not compat.py3k and isinstance(code, str):
             code = code.encode('ascii', 'backslashreplace')
         code = StringIO(code)
         for lineno, funcname, messages, python_translator_comments \
diff --git a/mako/ext/beaker_cache.py b/mako/ext/beaker_cache.py
index f0b50fa..ed1a31c 100644
--- a/mako/ext/beaker_cache.py
+++ b/mako/ext/beaker_cache.py
@@ -19,7 +19,7 @@ class BeakerCacheImpl(CacheImpl):
         if _beaker_cache is None:
             try:
                 from beaker import cache as beaker_cache
-            except ImportError, e:
+            except ImportError as e:
                 raise exceptions.RuntimeException(
                             "the Beaker package is required to use cache "
                             "functionality.")
diff --git a/mako/ext/pygmentplugin.py b/mako/ext/pygmentplugin.py
index 773f47a..6fd683a 100644
--- a/mako/ext/pygmentplugin.py
+++ b/mako/ext/pygmentplugin.py
@@ -13,7 +13,7 @@ from pygments.token import \
      Text, Comment, Operator, Keyword, Name, String, Other
 from pygments.formatters.html import HtmlFormatter
 from pygments import highlight
-from mako import util
+from mako import compat
 
 class MakoLexer(RegexLexer):
     name = 'Mako'
@@ -110,7 +110,7 @@ pygments_html_formatter = HtmlFormatter(cssclass='syntax-highlighted',
                                         linenos=True)
 def syntax_highlight(filename='', language=None):
     mako_lexer = MakoLexer()
-    if util.py3k:
+    if compat.py3k:
         python_lexer = Python3Lexer()
     else:
         python_lexer = PythonLexer()
diff --git a/mako/ext/turbogears.py b/mako/ext/turbogears.py
index e453ada..74fcd8a 100644
--- a/mako/ext/turbogears.py
+++ b/mako/ext/turbogears.py
@@ -19,7 +19,7 @@ class TGPlugin(object):
 
         # Pull the options out and initialize the lookup
         lookup_options = {}
-        for k, v in options.iteritems():
+        for k, v in options.items():
             if k.startswith('mako.'):
                 lookup_options[k[5:]] = v
             elif k in ['directories', 'filesystem_checks', 'module_directory']:
@@ -46,7 +46,7 @@ class TGPlugin(object):
         return self.lookup.get_template(templatename)
 
     def render(self, info, format="html", fragment=False, template=None):
-        if isinstance(template, basestring):
+        if isinstance(template, str):
             template = self.load_template(template)
 
         # Load extra vars func if provided
diff --git a/mako/filters.py b/mako/filters.py
index b4f2684..bf2f136 100644
--- a/mako/filters.py
+++ b/mako/filters.py
@@ -5,16 +5,20 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 
-import re, urllib, htmlentitydefs, codecs
-from StringIO import StringIO
-from mako import util
+import re
+import codecs
+
+from mako.compat import quote_plus, unquote_plus, codepoint2name, \
+        name2codepoint
+
+from mako import compat
 
 xml_escapes = {
-    '&' : '&amp;',
-    '>' : '&gt;',
-    '<' : '&lt;',
-    '"' : '&#34;',   # also &quot; in html-only
-    "'" : '&#39;'    # also &apos; in html-only
+    '&': '&amp;',
+    '>': '&gt;',
+    '<': '&lt;',
+    '"': '&#34;',   # also &quot; in html-only
+    "'": '&#39;'    # also &apos; in html-only
 }
 
 # XXX: &quot; is valid in HTML and XML
@@ -40,10 +44,10 @@ def xml_escape(string):
 def url_escape(string):
     # convert into a list of octets
     string = string.encode("utf8")
-    return urllib.quote_plus(string)
+    return quote_plus(string)
 
 def url_unescape(string):
-    text = urllib.unquote_plus(string)
+    text = unquote_plus(string)
     if not is_ascii_str(text):
         text = text.decode("utf8")
     return text
@@ -55,12 +59,12 @@ def trim(string):
 class Decode(object):
     def __getattr__(self, key):
         def decode(x):
-            if isinstance(x, unicode):
+            if isinstance(x, compat.text_type):
                 return x
-            elif not isinstance(x, str):
-                return unicode(str(x), encoding=key)
+            elif not isinstance(x, compat.binary_type):
+                return compat.text_type(str(x), encoding=key)
             else:
-                return unicode(x, encoding=key)
+                return compat.text_type(x, encoding=key)
         return decode
 decode = Decode()
 
@@ -74,8 +78,8 @@ def is_ascii_str(text):
 
 class XMLEntityEscaper(object):
     def __init__(self, codepoint2name, name2codepoint):
-        self.codepoint2entity = dict([(c, u'&%s;' % n)
-                                      for c,n in codepoint2name.iteritems()])
+        self.codepoint2entity = dict([(c, '&%s;' % n)
+                                      for c, n in codepoint2name.items()])
         self.name2codepoint = name2codepoint
 
     def escape_entities(self, text):
@@ -83,7 +87,7 @@ class XMLEntityEscaper(object):
 
         Only characters corresponding to a named entity are replaced.
         """
-        return unicode(text).translate(self.codepoint2entity)
+        return str(text).translate(self.codepoint2entity)
 
     def __escape(self, m):
         codepoint = ord(m.group())
@@ -104,7 +108,7 @@ class XMLEntityEscaper(object):
 
         The return value is guaranteed to be ASCII.
         """
-        return self.__escapable.sub(self.__escape, unicode(text)
+        return self.__escapable.sub(self.__escape, compat.text_type(text)
                                     ).encode('ascii')
 
     # XXX: This regexp will not match all valid XML entity names__.
@@ -129,7 +133,7 @@ class XMLEntityEscaper(object):
             # U+FFFD = "REPLACEMENT CHARACTER"
         if codepoint < 128:
             return chr(codepoint)
-        return unichr(codepoint)
+        return chr(codepoint)
 
     def unescape(self, text):
         """Unescape character references.
@@ -140,8 +144,7 @@ class XMLEntityEscaper(object):
         return self.__characterrefs.sub(self.__unescape, text)
 
 
-_html_entities_escaper = XMLEntityEscaper(htmlentitydefs.codepoint2name,
-                                          htmlentitydefs.name2codepoint)
+_html_entities_escaper = XMLEntityEscaper(codepoint2name, name2codepoint)
 
 html_entities_escape = _html_entities_escaper.escape_entities
 html_entities_unescape = _html_entities_escaper.unescape
@@ -161,7 +164,7 @@ def htmlentityreplace_errors(ex):
         # Handle encoding errors
         bad_text = ex.object[ex.start:ex.end]
         text = _html_entities_escaper.escape(bad_text)
-        return (unicode(text), ex.end)
+        return (str(text), ex.end)
     raise ex
 
 codecs.register_error('htmlentityreplace', htmlentityreplace_errors)
@@ -170,20 +173,20 @@ codecs.register_error('htmlentityreplace', htmlentityreplace_errors)
 # TODO: options to make this dynamic per-compilation will be added in a later
 # release
 DEFAULT_ESCAPES = {
-    'x':'filters.xml_escape',
-    'h':'filters.html_escape',
-    'u':'filters.url_escape',
-    'trim':'filters.trim',
-    'entity':'filters.html_entities_escape',
-    'unicode':'unicode',
-    'decode':'decode',
-    'str':'str',
-    'n':'n'
+    'x': 'filters.xml_escape',
+    'h': 'filters.html_escape',
+    'u': 'filters.url_escape',
+    'trim': 'filters.trim',
+    'entity': 'filters.html_entities_escape',
+    'unicode': 'unicode',
+    'decode': 'decode',
+    'str': 'str',
+    'n': 'n'
 }
 
-if util.py3k:
+if compat.py3k:
     DEFAULT_ESCAPES.update({
-        'unicode':'str'
+        'unicode': 'str'
     })
 
 NON_UNICODE_ESCAPES = DEFAULT_ESCAPES.copy()
diff --git a/mako/lexer.py b/mako/lexer.py
index 267c0d1..26d1596 100644
--- a/mako/lexer.py
+++ b/mako/lexer.py
@@ -6,8 +6,9 @@
 
 """provides the Lexer class for parsing template strings into parse trees."""
 
-import re, codecs
-from mako import parsetree, exceptions, util
+import re
+import codecs
+from mako import parsetree, exceptions, compat
 from mako.pygen import adjust_whitespace
 
 _regexp_cache = {}
@@ -29,7 +30,7 @@ class Lexer(object):
         self.disable_unicode = disable_unicode
         self.encoding = input_encoding
 
-        if util.py3k and disable_unicode:
+        if compat.py3k and disable_unicode:
             raise exceptions.UnsupportedError(
                                     "Mako for Python 3 does not "
                                     "support disabling Unicode")
@@ -173,7 +174,7 @@ class Lexer(object):
            or raw if decode_raw=False
 
         """
-        if isinstance(text, unicode):
+        if isinstance(text, compat.text_type):
             m = self._coding_re.match(text)
             encoding = m and m.group(1) or known_encoding or 'ascii'
             return encoding, text
@@ -198,7 +199,7 @@ class Lexer(object):
         if decode_raw:
             try:
                 text = text.decode(parsed_encoding)
-            except UnicodeDecodeError, e:
+            except UnicodeDecodeError:
                 raise exceptions.CompileException(
                            "Unicode decode operation of encoding '%s' failed" %
                            parsed_encoding,
diff --git a/mako/parsetree.py b/mako/parsetree.py
index b5247f1..3db8c6b 100644
--- a/mako/parsetree.py
+++ b/mako/parsetree.py
@@ -6,7 +6,7 @@
 
 """defines the parse tree components for Mako templates."""
 
-from mako import exceptions, ast, util, filters
+from mako import exceptions, ast, util, filters, compat
 import re
 
 class Node(object):
@@ -20,8 +20,8 @@ class Node(object):
 
     @property
     def exception_kwargs(self):
-        return {'source':self.source, 'lineno':self.lineno,
-                'pos':self.pos, 'filename':self.filename}
+        return {'source': self.source, 'lineno': self.lineno,
+                'pos': self.pos, 'filename': self.filename}
 
     def get_children(self):
         return []
@@ -204,9 +204,9 @@ class _TagMeta(type):
     _classmap = {}
 
     def __init__(cls, clsname, bases, dict):
-        if cls.__keyword__ is not None:
+        if getattr(cls, '__keyword__', None) is not None:
             cls._classmap[cls.__keyword__] = cls
-            super(_TagMeta, cls).__init__(clsname, bases, dict)
+        super(_TagMeta, cls).__init__(clsname, bases, dict)
 
     def __call__(cls, keyword, attributes, **kwargs):
         if ":" in keyword:
@@ -226,7 +226,7 @@ class _TagMeta(type):
             )
         return type.__call__(cls, keyword, attributes, **kwargs)
 
-class Tag(Node):
+class Tag(compat.with_metaclass(_TagMeta, Node)):
     """abstract base class for tags.
 
     <%sometag/>
@@ -236,8 +236,6 @@ class Tag(Node):
     </%someothertag>
 
     """
-
-    __metaclass__ = _TagMeta
     __keyword__ = None
 
     def __init__(self, keyword, attributes, expressions,
@@ -396,7 +394,7 @@ class TextTag(Tag):
     def undeclared_identifiers(self):
         return self.filter_args.\
                             undeclared_identifiers.\
-                            difference(filters.DEFAULT_ESCAPES.keys()).union(
+                            difference(list(filters.DEFAULT_ESCAPES.keys())).union(
                         self.expression_undeclared_identifiers
                     )
 
@@ -449,7 +447,7 @@ class DefTag(Tag):
         return set(res).union(
             self.filter_args.\
                             undeclared_identifiers.\
-                            difference(filters.DEFAULT_ESCAPES.keys())
+                            difference(list(filters.DEFAULT_ESCAPES.keys()))
         ).union(
             self.expression_undeclared_identifiers
         ).difference(
@@ -509,7 +507,7 @@ class BlockTag(Tag):
     def undeclared_identifiers(self):
         return (self.filter_args.\
                             undeclared_identifiers.\
-                            difference(filters.DEFAULT_ESCAPES.keys())
+                            difference(list(filters.DEFAULT_ESCAPES.keys()))
                 ).union(self.expression_undeclared_identifiers)
 
 
@@ -547,7 +545,7 @@ class CallNamespaceTag(Tag):
                                 namespace,
                                 defname,
                                 ",".join(["%s=%s" % (k, v) for k, v in
-                                            self.parsed_attributes.iteritems()
+                                            self.parsed_attributes.items()
                                             if k != 'args'])
                             )
         self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
diff --git a/mako/pygen.py b/mako/pygen.py
index e946de5..e46edff 100644
--- a/mako/pygen.py
+++ b/mako/pygen.py
@@ -6,8 +6,7 @@
 
 """utilities for generating and formatting literal Python code."""
 
-import re, string
-from StringIO import StringIO
+import re
 from mako import exceptions
 
 class PythonPrinter(object):
diff --git a/mako/pyparser.py b/mako/pyparser.py
index 984c0d2..7dde1f9 100644
--- a/mako/pyparser.py
+++ b/mako/pyparser.py
@@ -10,11 +10,11 @@ Parsing to AST is done via _ast on Python > 2.5, otherwise the compiler
 module is used.
 """
 
-from StringIO import StringIO
-from mako import exceptions, util
+from mako import exceptions, util, compat
+from mako.compat import StringIO
 import operator
 
-if util.py3k:
+if compat.py3k:
     # words that cannot be assigned to (notably
     # smaller than the total keys in __builtins__)
     reserved = set(['True', 'False', 'None', 'print'])
@@ -33,7 +33,7 @@ else:
 try:
     import _ast
     util.restore__ast(_ast)
-    import _ast_util
+    from . import _ast_util
 except ImportError:
     _ast = None
     from compiler import parse as compiler_parse
@@ -48,10 +48,10 @@ def parse(code, mode='exec', **exception_kwargs):
         if _ast:
             return _ast_util.parse(code, '<unknown>', mode)
         else:
-            if isinstance(code, unicode):
+            if isinstance(code, compat.text_types):
                 code = code.encode('ascii', 'backslashreplace')
             return compiler_parse(code, mode)
-    except Exception, e:
+    except Exception as e:
         raise exceptions.SyntaxException(
                     "(%s) %s (%r)" % (
                         e.__class__.__name__,
@@ -92,7 +92,7 @@ if _ast:
                 self.visit(n)
             self.in_assign_targets = in_a
 
-        if util.py3k:
+        if compat.py3k:
 
             # ExceptHandler is in Python 2, but this block only works in
             # Python 3 (and is required there)
@@ -544,7 +544,7 @@ else:
     class walker(visitor.ASTVisitor):
 
         def dispatch(self, node, *args):
-            print 'Node:', str(node)
+            print('Node:', str(node))
 
             # print "dir:", dir(node)
 
diff --git a/mako/runtime.py b/mako/runtime.py
index f890c80..51e04d6 100644
--- a/mako/runtime.py
+++ b/mako/runtime.py
@@ -7,8 +7,11 @@
 """provides runtime services for templates, including Context,
 Namespace, and various helper functions."""
 
-from mako import exceptions, util
-import __builtin__, inspect, sys
+from mako import exceptions, util, compat
+from mako.compat import compat_builtins
+import inspect
+import sys
+import collections
 
 
 class Context(object):
@@ -32,7 +35,7 @@ class Context(object):
 
         # "capture" function which proxies to the
         # generic "capture" function
-        self._data['capture'] = util.partial(capture, self)
+        self._data['capture'] = compat.partial(capture, self)
 
         # "caller" stack used by def calls with content
         self.caller_stack = self._data['caller'] = CallerStack()
@@ -77,13 +80,13 @@ class Context(object):
     def keys(self):
         """Return a list of all names established in this :class:`.Context`."""
 
-        return self._data.keys()
+        return list(self._data.keys())
 
     def __getitem__(self, key):
         if key in self._data:
             return self._data[key]
         else:
-            return __builtin__.__dict__[key]
+            return compat_builtins.__dict__[key]
 
     def _push_writer(self):
         """push a capturing buffer onto this Context and return
@@ -116,7 +119,7 @@ class Context(object):
         """Return a value from this :class:`.Context`."""
 
         return self._data.get(key,
-                __builtin__.__dict__.get(key, default)
+                compat_builtins.__dict__.get(key, default)
                 )
 
     def write(self, string):
@@ -165,8 +168,13 @@ class Context(object):
 class CallerStack(list):
     def __init__(self):
         self.nextcaller = None
+
     def __nonzero__(self):
+        return self.__bool__()
+
+    def __bool__(self):
         return self._get_caller() and True or False
+
     def _get_caller(self):
         # this method can be removed once
         # codegen MAGIC_NUMBER moves past 7
@@ -192,7 +200,11 @@ class Undefined(object):
     """
     def __str__(self):
         raise NameError("Undefined")
+
     def __nonzero__(self):
+        return self.__bool__()
+
+    def __bool__(self):
         return False
 
 UNDEFINED = Undefined()
@@ -336,7 +348,7 @@ class Namespace(object):
         self.context = context
         self.inherits = inherits
         if callables is not None:
-            self.callables = dict([(c.func_name, c) for c in callables])
+            self.callables = dict([(c.__name__, c) for c in callables])
 
     callables = ()
 
@@ -502,7 +514,7 @@ class TemplateNamespace(Namespace):
         self.context = context
         self.inherits = inherits
         if callables is not None:
-            self.callables = dict([(c.func_name, c) for c in callables])
+            self.callables = dict([(c.__name__, c) for c in callables])
 
         if templateuri is not None:
             self.template = _lookup_template(context, templateuri,
@@ -554,7 +566,7 @@ class TemplateNamespace(Namespace):
                 yield (key, self.callables[key])
         def get(key):
             callable_ = self.template._get_def_callable(key)
-            return util.partial(callable_, self.context)
+            return compat.partial(callable_, self.context)
         for k in self.template.module._exports:
             yield (k, get(k))
 
@@ -563,7 +575,7 @@ class TemplateNamespace(Namespace):
             val = self.callables[key]
         elif self.template.has_def(key):
             callable_ = self.template._get_def_callable(key)
-            val = util.partial(callable_, self.context)
+            val = compat.partial(callable_, self.context)
         elif self.inherits:
             val = getattr(self.inherits, key)
 
@@ -584,7 +596,7 @@ class ModuleNamespace(Namespace):
         self.context = context
         self.inherits = inherits
         if callables is not None:
-            self.callables = dict([(c.func_name, c) for c in callables])
+            self.callables = dict([(c.__name__, c) for c in callables])
 
         mod = __import__(module)
         for token in module.split('.')[1:]:
@@ -604,7 +616,7 @@ class ModuleNamespace(Namespace):
                 yield (key, self.callables[key])
         def get(key):
             callable_ = getattr(self.module, key)
-            return util.partial(callable_, self.context)
+            return compat.partial(callable_, self.context)
         for k in dir(self.module):
             if k[0] != '_':
                 yield (k, get(k))
@@ -614,7 +626,7 @@ class ModuleNamespace(Namespace):
             val = self.callables[key]
         elif hasattr(self.module, key):
             callable_ = getattr(self.module, key)
-            val = util.partial(callable_, self.context)
+            val = compat.partial(callable_, self.context)
         elif self.inherits:
             val = getattr(self.inherits, key)
         else:
@@ -648,7 +660,7 @@ def capture(context, callable_, *args, **kwargs):
 
     """
 
-    if not callable(callable_):
+    if not isinstance(callable_, collections.Callable):
         raise exceptions.RuntimeException(
                            "capture() function expects a callable as "
                            "its argument (i.e. capture(func, *args, **kwargs))"
@@ -730,7 +742,7 @@ def _lookup_template(context, uri, relativeto):
     uri = lookup.adjust_uri(uri, relativeto)
     try:
         return lookup.get_template(uri)
-    except exceptions.TopLevelLookupException, e:
+    except exceptions.TopLevelLookupException as e:
         raise exceptions.TemplateLookupException(str(e))
 
 def _populate_self_namespace(context, template, self_ns=None):
@@ -750,12 +762,12 @@ def _render(template, callable_, args, data, as_unicode=False):
     output of the given template and template callable."""
 
     if as_unicode:
-        buf = util.FastEncodingBuffer(unicode=True)
+        buf = util.FastEncodingBuffer(as_unicode=True)
     elif template.bytestring_passthrough:
-        buf = util.StringIO()
+        buf = compat.StringIO()
     else:
         buf = util.FastEncodingBuffer(
-                        unicode=as_unicode,
+                        as_unicode=as_unicode,
                         encoding=template.output_encoding,
                         errors=template.encoding_errors)
     context = Context(buf, **data)
@@ -767,7 +779,7 @@ def _render(template, callable_, args, data, as_unicode=False):
     return context._pop_buffer().getvalue()
 
 def _kwargs_for_callable(callable_, data):
-    argspec = util.inspect_func_args(callable_)
+    argspec = compat.inspect_func_args(callable_)
     # for normal pages, **pageargs is usually present
     if argspec[2]:
         return data
@@ -781,7 +793,7 @@ def _kwargs_for_callable(callable_, data):
     return kwargs
 
 def _kwargs_for_include(callable_, data, **kwargs):
-    argspec = util.inspect_func_args(callable_)
+    argspec = compat.inspect_func_args(callable_)
     namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
     for arg in namedargs:
         if arg != 'context' and arg in data and arg not in kwargs:
@@ -815,7 +827,7 @@ def _exec_template(callable_, context, args=None, kwargs=None):
         error = None
         try:
             callable_(context, *args, **kwargs)
-        except Exception, e:
+        except Exception as e:
             _render_error(template, context, e)
         except:
             e = sys.exc_info()[0]
@@ -831,7 +843,7 @@ def _render_error(template, context, error):
     else:
         error_template = exceptions.html_error_template()
         if context._outputting_as_unicode:
-            context._buffer_stack[:] = [util.FastEncodingBuffer(unicode=True)]
+            context._buffer_stack[:] = [util.FastEncodingBuffer(as_unicode=True)]
         else:
             context._buffer_stack[:] = [util.FastEncodingBuffer(
                                             error_template.output_encoding,
diff --git a/mako/template.py b/mako/template.py
index 84d1ebb..d32e465 100644
--- a/mako/template.py
+++ b/mako/template.py
@@ -8,8 +8,15 @@
 template strings, as well as template runtime operations."""
 
 from mako.lexer import Lexer
-from mako import runtime, util, exceptions, codegen, cache
-import os, re, shutil, stat, sys, tempfile, types, weakref
+from mako import runtime, util, exceptions, codegen, cache, compat
+import os
+import re
+import shutil
+import stat
+import sys
+import tempfile
+import types
+import weakref
 
 
 class Template(object):
@@ -258,7 +265,7 @@ class Template(object):
         self.strict_undefined = strict_undefined
         self.module_writer = module_writer
 
-        if util.py3k and disable_unicode:
+        if compat.py3k and disable_unicode:
             raise exceptions.UnsupportedError(
                                     "Mako for Python 3 does not "
                                     "support disabling Unicode")
@@ -267,7 +274,7 @@ class Template(object):
                                     "output_encoding must be set to "
                                     "None when disable_unicode is used.")
         if default_filters is None:
-            if util.py3k or self.disable_unicode:
+            if compat.py3k or self.disable_unicode:
                 self.default_filters = ['str']
             else:
                 self.default_filters = ['unicode']
@@ -317,7 +324,7 @@ class Template(object):
             cache_impl, cache_enabled, cache_args,
             cache_type, cache_dir, cache_url
         )
-      
+
 
     @util.memoized_property
     def reserved_names(self):
@@ -507,7 +514,7 @@ class ModuleTemplate(Template):
         self.bytestring_passthrough = bytestring_passthrough or disable_unicode
         self.enable_loop = module._enable_loop
 
-        if util.py3k and disable_unicode:
+        if compat.py3k and disable_unicode:
             raise exceptions.UnsupportedError(
                                     "Mako for Python 3 does not "
                                     "support disabling Unicode")
@@ -588,7 +595,7 @@ class ModuleInfo(object):
     def source(self):
         if self.template_source is not None:
             if self.module._source_encoding and \
-                    not isinstance(self.template_source, unicode):
+                    not isinstance(self.template_source, compat.text_type):
                 return self.template_source.decode(
                                 self.module._source_encoding)
             else:
@@ -628,11 +635,11 @@ def _compile_text(template, text, filename):
                         generate_magic_comment=template.disable_unicode)
 
     cid = identifier
-    if not util.py3k and isinstance(cid, unicode):
+    if not compat.py3k and isinstance(cid, compat.text_type):
         cid = cid.encode()
     module = types.ModuleType(cid)
     code = compile(source, cid, 'exec')
-    exec code in module.__dict__, module.__dict__
+    exec(code, module.__dict__, module.__dict__)
     return (source, module)
 
 def _compile_module_file(template, text, filename, outputpath, module_writer):
@@ -640,7 +647,7 @@ def _compile_module_file(template, text, filename, outputpath, module_writer):
     source, lexer = _compile(template, text, filename,
                         generate_magic_comment=True)
 
-    if isinstance(source, unicode):
+    if isinstance(source, compat.text_type):
         source = source.encode(lexer.encoding or 'ascii')
 
     if module_writer:
@@ -656,7 +663,7 @@ def _compile_module_file(template, text, filename, outputpath, module_writer):
         shutil.move(name, outputpath)
 
 def _get_module_info_from_callable(callable_):
-    return _get_module_info(callable_.func_globals['__name__'])
+    return _get_module_info(callable_.__globals__['__name__'])
 
 def _get_module_info(filename):
     return ModuleInfo._modules[filename]
diff --git a/mako/util.py b/mako/util.py
index 4b88ae5..b71c380 100644
--- a/mako/util.py
+++ b/mako/util.py
@@ -5,38 +5,12 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 import imp
-import sys
-
-
-py3k = getattr(sys, 'py3kwarning', False) or sys.version_info >= (3, 0)
-py26 = sys.version_info >= (2, 6)
-py25 = sys.version_info >= (2, 5)
-jython = sys.platform.startswith('java')
-win32 = sys.platform.startswith('win')
-pypy = hasattr(sys, 'pypy_version_info')
-
-if py3k:
-    from io import StringIO
-else:
-    try:
-        from cStringIO import StringIO
-    except:
-        from StringIO import StringIO
-
-import codecs, re, weakref, os, time, operator
+import re
 import collections
-
-try:
-    import threading
-    import thread
-except ImportError:
-    import dummy_threading as threading
-    import dummy_thread as thread
-
-if win32 or jython:
-    time_func = time.clock
-else:
-    time_func = time.time
+import codecs
+import os
+from mako import compat
+import operator
 
 def function_named(fn, name):
     """Return a function with a given __name__.
@@ -48,34 +22,6 @@ def function_named(fn, name):
     fn.__name__ = name
     return fn
 
-try:
-    from functools import partial
-except:
-    def partial(func, *args, **keywords):
-        def newfunc(*fargs, **fkeywords):
-            newkeywords = keywords.copy()
-            newkeywords.update(fkeywords)
-            return func(*(args + fargs), **newkeywords)
-        return newfunc
-
-if not py25:
-    def all(iterable):
-        for i in iterable:
-            if not i:
-                return False
-        return True
-
-    def exception_name(exc):
-        try:
-            return exc.__class__.__name__
-        except AttributeError:
-            return exc.__name__
-else:
-    all = all
-
-    def exception_name(exc):
-        return exc.__class__.__name__
-
 
 class PluginLoader(object):
     def __init__(self, group):
@@ -114,7 +60,7 @@ def verify_directory(dir):
     while not os.path.exists(dir):
         try:
             tries += 1
-            os.makedirs(dir, 0775)
+            os.makedirs(dir, 0o775)
         except:
             if tries > 5:
                 raise
@@ -182,14 +128,14 @@ class FastEncodingBuffer(object):
     """a very rudimentary buffer that is faster than StringIO,
     but doesn't crash on unicode data like cStringIO."""
 
-    def __init__(self, encoding=None, errors='strict', unicode=False):
+    def __init__(self, encoding=None, errors='strict', as_unicode=False):
         self.data = collections.deque()
         self.encoding = encoding
-        if unicode:
-            self.delim = u''
+        if as_unicode:
+            self.delim = compat.u('')
         else:
             self.delim = ''
-        self.unicode = unicode
+        self.as_unicode = as_unicode
         self.errors = errors
         self.write = self.data.append
 
@@ -217,7 +163,7 @@ class LRUCache(dict):
         def __init__(self, key, value):
             self.key = key
             self.value = value
-            self.timestamp = time_func()
+            self.timestamp = compat.time_func()
         def __repr__(self):
             return repr(self.value)
 
@@ -227,7 +173,7 @@ class LRUCache(dict):
 
     def __getitem__(self, key):
         item = dict.__getitem__(self, key)
-        item.timestamp = time_func()
+        item.timestamp = compat.time_func()
         return item.value
 
     def values(self):
@@ -302,9 +248,8 @@ def parse_encoding(fp):
 
         if has_bom:
             if m:
-                raise SyntaxError, \
-                      "python refuses to compile code with both a UTF8" \
-                      " byte-order-mark and a magic encoding comment"
+                raise SyntaxError("python refuses to compile code with both a UTF8" \
+                      " byte-order-mark and a magic encoding comment")
             return 'utf_8'
         elif m:
             return m.group(1)
@@ -319,7 +264,7 @@ def sorted_dict_repr(d):
     Used by the lexer unit test to compare parse trees based on strings.
 
     """
-    keys = d.keys()
+    keys = list(d.keys())
     keys.sort()
     return "{" + ", ".join(["%r: %r" % (k, d[k]) for k in keys]) + "}"
 
@@ -399,28 +344,6 @@ mako in baz not in mako""", '<unknown>', 'exec', _ast.PyCF_ONLY_AST)
     _ast.NotIn = type(m.body[12].value.ops[1])
 
 
-try:
-    from inspect import CO_VARKEYWORDS, CO_VARARGS
-    def inspect_func_args(fn):
-        co = fn.func_code
-
-        nargs = co.co_argcount
-        names = co.co_varnames
-        args = list(names[:nargs])
-
-        varargs = None
-        if co.co_flags & CO_VARARGS:
-            varargs = co.co_varnames[nargs]
-            nargs = nargs + 1
-        varkw = None
-        if co.co_flags & CO_VARKEYWORDS:
-            varkw = co.co_varnames[nargs]
-
-        return args, varargs, varkw, fn.func_defaults
-except ImportError:
-    import inspect
-    def inspect_func_args(fn):
-        return inspect.getargspec(fn)
 
 def read_file(path, mode='rb'):
     fp = open(path, mode)
diff --git a/test/__init__.py b/test/__init__.py
index ded1a5d..c983c5d 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -1,6 +1,7 @@
 from mako.template import Template
-import unittest, os
-from mako.util import py3k, py26, py25
+import unittest
+import os
+from mako.compat import py3k, py26, py25
 from mako.util import function_named
 import re
 from mako.cache import CacheImpl, register_plugin
@@ -64,7 +65,7 @@ def assert_raises(except_cls, callable_, *args, **kw):
     try:
         callable_(*args, **kw)
         success = False
-    except except_cls, e:
+    except except_cls as e:
         success = True
 
     # assert outside the block so it works for AssertionError too !
@@ -74,9 +75,9 @@ def assert_raises_message(except_cls, msg, callable_, *args, **kwargs):
     try:
         callable_(*args, **kwargs)
         assert False, "Callable did not raise an exception"
-    except except_cls, e:
+    except except_cls as e:
         assert re.search(msg, str(e)), "%r !~ %s" % (msg, e)
-        print str(e)
+        print(str(e))
 
 def skip_if(predicate, reason=None):
     """Skip a test if predicate is true."""
diff --git a/test/templates/foo/modtest.html.py b/test/templates/foo/modtest.html.py
index c5fb76a..e6fc8d8 100644
--- a/test/templates/foo/modtest.html.py
+++ b/test/templates/foo/modtest.html.py
@@ -17,7 +17,7 @@ def render_body(context,**pageargs):
         __M_locals = __M_dict_builtin(pageargs=pageargs)
         __M_writer = context.writer()
         # SOURCE LINE 1
-        __M_writer(u'this is a test')
+        __M_writer('this is a test')
         return ''
     finally:
         context.caller_stack._pop_frame()
diff --git a/test/templates/subdir/foo/modtest.html.py b/test/templates/subdir/foo/modtest.html.py
index ca6f37b..b0f50c7 100644
--- a/test/templates/subdir/foo/modtest.html.py
+++ b/test/templates/subdir/foo/modtest.html.py
@@ -17,7 +17,7 @@ def render_body(context,**pageargs):
         __M_locals = __M_dict_builtin(pageargs=pageargs)
         __M_writer = context.writer()
         # SOURCE LINE 1
-        __M_writer(u'this is a test')
+        __M_writer('this is a test')
         return ''
     finally:
         context.caller_stack._pop_frame()
diff --git a/test/test_ast.py b/test/test_ast.py
index 8e02811..84508c7 100644
--- a/test/test_ast.py
+++ b/test/test_ast.py
@@ -1,6 +1,6 @@
 import unittest
 
-from mako import ast, exceptions, pyparser, util
+from mako import ast, exceptions, pyparser, util, compat
 from test import eq_, requires_python_2
 
 exception_kwargs = {
@@ -237,7 +237,7 @@ import x as bar
 
         parsed = ast.PythonFragment("try:", **exception_kwargs)
 
-        if util.py3k:
+        if compat.py3k:
             parsed = ast.PythonFragment(
                         "except MyException as e:", **exception_kwargs)
         else:
diff --git a/test/test_babelplugin.py b/test/test_babelplugin.py
index 8769da0..55be33f 100644
--- a/test/test_babelplugin.py
+++ b/test/test_babelplugin.py
@@ -19,26 +19,26 @@ class ExtractMakoTestCase(TemplateTest):
                                             'ungettext': (1, 2)},
                                 ['TRANSLATOR:'], {}))
         expected = \
-            [(1, '_', u'Page arg 1', []),
-             (1, '_', u'Page arg 2', []),
-             (10, 'gettext', u'Begin', []),
-             (14, '_', u'Hi there!', [u'TRANSLATOR: Hi there!']),
-             (19, '_', u'Hello', []),
-             (22, '_', u'Welcome', []),
-             (25, '_', u'Yo', []),
-             (36, '_', u'The', [u'TRANSLATOR: Ensure so and', u'so, thanks']),
-             (36, 'ungettext', (u'bunny', u'bunnies', None), []),
-             (41, '_', u'Goodbye', [u'TRANSLATOR: Good bye']),
-             (44, '_', u'Babel', []),
-             (45, 'ungettext', (u'hella', u'hellas', None), []),
-            (62, '_', u'The', [u'TRANSLATOR: Ensure so and', u'so, thanks']),
-            (62, 'ungettext', (u'bunny', u'bunnies', None), []),
-            (68, '_', u'Goodbye, really!', [u'TRANSLATOR: HTML comment']),
-            (71, '_', u'P.S. byebye', []),
-            (77, '_', u'Top', []),
-            (83, '_', u'foo', []),
-            (83, '_', u'baz', []),
-            (85, '_', u'bar', [])
+            [(1, '_', 'Page arg 1', []),
+             (1, '_', 'Page arg 2', []),
+             (10, 'gettext', 'Begin', []),
+             (14, '_', 'Hi there!', ['TRANSLATOR: Hi there!']),
+             (19, '_', 'Hello', []),
+             (22, '_', 'Welcome', []),
+             (25, '_', 'Yo', []),
+             (36, '_', 'The', ['TRANSLATOR: Ensure so and', 'so, thanks']),
+             (36, 'ungettext', ('bunny', 'bunnies', None), []),
+             (41, '_', 'Goodbye', ['TRANSLATOR: Good bye']),
+             (44, '_', 'Babel', []),
+             (45, 'ungettext', ('hella', 'hellas', None), []),
+            (62, '_', 'The', ['TRANSLATOR: Ensure so and', 'so, thanks']),
+            (62, 'ungettext', ('bunny', 'bunnies', None), []),
+            (68, '_', 'Goodbye, really!', ['TRANSLATOR: HTML comment']),
+            (71, '_', 'P.S. byebye', []),
+            (77, '_', 'Top', []),
+            (83, '_', 'foo', []),
+            (83, '_', 'baz', []),
+            (85, '_', 'bar', [])
              ]
         self.assertEqual(expected, messages)
 
diff --git a/test/test_block.py b/test/test_block.py
index e70b79c..0f5cf17 100644
--- a/test/test_block.py
+++ b/test/test_block.py
@@ -2,7 +2,7 @@ from mako.template import Template
 from mako.lookup import TemplateLookup
 from mako import exceptions
 from test import TemplateTest, assert_raises, assert_raises_message
-from util import flatten_result, result_lines
+from .util import flatten_result, result_lines
 
 
 
@@ -361,7 +361,7 @@ class BlockTest(TemplateTest):
                 <html>
             </%block>
         """)
-        self._do_test(template, [u'&lt;html&gt;'], 
+        self._do_test(template, ['&lt;html&gt;'], 
                     filters=result_lines)
 
     def test_anon_in_named(self):
@@ -506,7 +506,7 @@ class BlockTest(TemplateTest):
         """)
         self._do_test(
             l.get_template("caller"),
-            [u'foob, 3, 4'],
+            ['foob, 3, 4'],
             filters=result_lines
         )
 
@@ -518,7 +518,7 @@ class BlockTest(TemplateTest):
         """)
         self._do_test(
             t,
-            [u'foob, 3, 4'],
+            ['foob, 3, 4'],
             template_args={'val1':3, 'val2':4},
             filters=result_lines
         )
@@ -532,7 +532,7 @@ class BlockTest(TemplateTest):
         """)
         self._do_test(
             t,
-            [u'foob, 3, 4'],
+            ['foob, 3, 4'],
             template_args={'val1':3, 'val2':4},
             filters=result_lines
         )
@@ -545,7 +545,7 @@ class BlockTest(TemplateTest):
         """)
         self._do_test(
             t,
-            [u'foob, 3, 4'],
+            ['foob, 3, 4'],
             template_args={'val1':3, 'val2':4},
             filters=result_lines
         )
@@ -564,6 +564,6 @@ class BlockTest(TemplateTest):
         """)
         self._do_test(
             l.get_template("caller"),
-            [u'foob, 3, 4'],
+            ['foob, 3, 4'],
             filters=result_lines
         )
\ No newline at end of file
diff --git a/test/test_cache.py b/test/test_cache.py
index bd8cf47..40539fb 100644
--- a/test/test_cache.py
+++ b/test/test_cache.py
@@ -2,7 +2,7 @@ from mako.template import Template
 from mako.lookup import TemplateLookup
 from mako import lookup
 import shutil, unittest, os, time
-from util import result_lines
+from .util import result_lines
 from test import TemplateTest, template_base, module_base
 from test import eq_
 
diff --git a/test/test_call.py b/test/test_call.py
index 0bb6079..e735a72 100644
--- a/test/test_call.py
+++ b/test/test_call.py
@@ -1,6 +1,6 @@
 from mako.template import Template
 from mako import util
-from util import result_lines, flatten_result
+from .util import result_lines, flatten_result
 from test import TemplateTest, eq_
 
 class CallTest(TemplateTest):
@@ -9,7 +9,7 @@ class CallTest(TemplateTest):
         <%def name="foo()">
             hi im foo ${caller.body(y=5)}
         </%def>
- 
+
         <%call expr="foo()" args="y, **kwargs">
             this is the body, y is ${y}
         </%call>
@@ -23,16 +23,16 @@ class CallTest(TemplateTest):
         <%def name="bar()">
             this is bar
         </%def>
- 
+
         <%def name="comp1()">
             this comp1 should not be called
         </%def>
- 
+
         <%def name="foo()">
             foo calling comp1: ${caller.comp1(x=5)}
             foo calling body: ${caller.body()}
         </%def>
- 
+
         <%call expr="foo()">
             <%def name="comp1(x)">
                 this is comp1, ${x}
@@ -46,10 +46,10 @@ class CallTest(TemplateTest):
 
     def test_new_syntax(self):
         """test foo:bar syntax, including multiline args and expression eval."""
- 
+
         # note the trailing whitespace in the bottom ${} expr, need to strip
         # that off < python 2.7
- 
+
         t = Template("""
             <%def name="foo(x, y, q, z)">
                 ${x}
@@ -57,26 +57,26 @@ class CallTest(TemplateTest):
                 ${q}
                 ${",".join("%s->%s" % (a, b) for a, b in z)}
             </%def>
- 
+
             <%self:foo x="this is x" y="${'some ' + 'y'}" q="
                 this
                 is
                 q"
- 
+
                 z="${[
                 (1, 2),
                 (3, 4),
                 (5, 6)
             ]
- 
+
             }"/>
         """)
- 
+
         eq_(
             result_lines(t.render()),
              ['this is x', 'some y', 'this', 'is', 'q', '1->2,3->4,5->6']
         )
- 
+
     def test_ccall_caller(self):
         t = Template("""
         <%def name="outer_func()">
@@ -104,7 +104,7 @@ class CallTest(TemplateTest):
             "INNER END",
             "OUTER END",
         ]
- 
+
     def test_stack_pop(self):
         t = Template("""
         <%def name="links()" buffered="True">
@@ -130,7 +130,7 @@ class CallTest(TemplateTest):
         "</h1>",
         "Some links"
         ]
- 
+
     def test_conditional_call(self):
         """test that 'caller' is non-None only if the immediate <%def> was called via <%call>"""
 
@@ -169,12 +169,12 @@ class CallTest(TemplateTest):
             "BBB",
             "CCC"
         ]
- 
+
     def test_chained_call(self):
         """test %calls that are chained through their targets"""
         t = Template("""
             <%def name="a()">
-                this is a. 
+                this is a.
                 <%call expr="b()">
                     this is a's ccall.  heres my body: ${caller.body()}
                 </%call>
@@ -184,11 +184,11 @@ class CallTest(TemplateTest):
                 whats in the body's caller's body ?
                 ${context.caller_stack[-2].body()}
             </%def>
- 
+
             <%call expr="a()">
                 heres the main templ call
             </%call>
- 
+
 """)
         assert result_lines(t.render()) == [
             'this is a.',
@@ -225,7 +225,7 @@ class CallTest(TemplateTest):
             "bar:",
             "this is bar body: 10"
         ]
- 
+
     def test_nested_call_2(self):
         t = Template("""
             x is ${x}
@@ -240,13 +240,13 @@ class CallTest(TemplateTest):
             <%call expr="foo()">
                 <%def name="foosub(x)">
                 this is foo body: ${x}
- 
+
                 <%call expr="bar()">
                     <%def name="barsub()">
                     this is bar body: ${x}
                     </%def>
                 </%call>
- 
+
                 </%def>
 
             </%call>
@@ -278,7 +278,7 @@ class CallTest(TemplateTest):
 
         ''')
         assert flatten_result(template.render()) == "foo"
- 
+
     def test_nested_call_4(self):
         base = """
         <%def name="A()">
@@ -340,7 +340,7 @@ class CallTest(TemplateTest):
         t = Template("""
             <%def name="embedded()">
             <%def name="a()">
-                this is a. 
+                this is a.
                 <%call expr="b()">
                     this is a's ccall.  heres my body: ${caller.body()}
                 </%call>
@@ -366,7 +366,7 @@ class CallTest(TemplateTest):
             "whats in the body's caller's body ?",
             'heres the main templ call'
         ]
- 
+
     def test_call_in_nested(self):
         t = Template("""
             <%def name="a()">
@@ -407,7 +407,7 @@ class CallTest(TemplateTest):
                 context.write("a is done")
                 return ''
         %>
- 
+
         <%def name="b()">
             this is b
             our body: ${caller.body()}
@@ -430,8 +430,7 @@ class CallTest(TemplateTest):
         </%call>
 
 
-        """) 
-        #print t.code
+        """)
         assert result_lines(t.render()) == [
             "test 1",
             "this is a",
@@ -452,7 +451,7 @@ class CallTest(TemplateTest):
             "this is aa is done",
             "this is aa is done"
         ]
- 
+
     def test_call_in_nested_2(self):
         t = Template("""
             <%def name="a()">
@@ -483,14 +482,14 @@ class CallTest(TemplateTest):
 
 class SelfCacheTest(TemplateTest):
     """this test uses a now non-public API."""
- 
+
     def test_basic(self):
         t = Template("""
         <%!
             cached = None
         %>
         <%def name="foo()">
-            <% 
+            <%
                 global cached
                 if cached:
                     return "cached: " + cached
@@ -503,7 +502,7 @@ class SelfCacheTest(TemplateTest):
                 return cached
             %>
         </%def>
- 
+
         ${foo()}
         ${foo()}
 """)
@@ -512,4 +511,4 @@ class SelfCacheTest(TemplateTest):
             "cached:",
             "this is foo"
         ]
- 
+
diff --git a/test/test_decorators.py b/test/test_decorators.py
index fc8768b..74e674c 100644
--- a/test/test_decorators.py
+++ b/test/test_decorators.py
@@ -1,7 +1,7 @@
 from mako.template import Template
 from mako import lookup
 import unittest
-from util import flatten_result, result_lines
+from .util import flatten_result, result_lines
 
 class DecoratorTest(unittest.TestCase):
     def test_toplevel(self):
diff --git a/test/test_def.py b/test/test_def.py
index 5c7608f..73c1091 100644
--- a/test/test_def.py
+++ b/test/test_def.py
@@ -1,7 +1,7 @@
 from mako.template import Template
 from mako import lookup
 from test import TemplateTest
-from util import flatten_result, result_lines
+from .util import flatten_result, result_lines
 from test import eq_, assert_raises
 
 class DefTest(TemplateTest):
@@ -63,8 +63,8 @@ class DefTest(TemplateTest):
         </%def>
 """)
         # check that "a" is declared in "b", but not in "c"
-        assert "a" not in template.module.render_c.func_code.co_varnames
-        assert "a" in template.module.render_b.func_code.co_varnames
+        assert "a" not in template.module.render_c.__code__.co_varnames
+        assert "a" in template.module.render_b.__code__.co_varnames
 
         # then test output
         eq_(
@@ -390,7 +390,7 @@ class ScopeTest(TemplateTest):
             ${enclosing()}
 """)
         try:
-            print t.render()
+            print(t.render())
             assert False
         except UnboundLocalError:
             assert True
diff --git a/test/test_exceptions.py b/test/test_exceptions.py
index 5503a1f..d7feb9a 100644
--- a/test/test_exceptions.py
+++ b/test/test_exceptions.py
@@ -1,12 +1,12 @@
 # -*- coding: utf-8 -*-
 import sys
-import unittest
 
-from mako import exceptions, util
+from mako import exceptions, compat
 from mako.template import Template
 from mako.lookup import TemplateLookup
-from util import result_lines
-from test import template_base, module_base, TemplateTest
+from mako.compat import u
+from test.util import result_lines
+from test import TemplateTest
 from test import requires_pygments_14, requires_no_pygments, \
     requires_python_25_or_greater
 
@@ -21,7 +21,7 @@ class ExceptionsTest(TemplateTest):
             template = Template(code)
             template.render_unicode()
             assert False
-        except exceptions.CompileException, ce:
+        except exceptions.CompileException as ce:
             html_error = exceptions.html_error_template().render_unicode()
             assert ("CompileException: Fragment &#39;i = 0&#39; is not "
                     "a partial control statement at line: 2 char: 1") in html_error
@@ -50,7 +50,7 @@ class ExceptionsTest(TemplateTest):
             template = Template(code)
             template.render_unicode()
             assert False
-        except exceptions.CompileException, ce:
+        except exceptions.CompileException as ce:
             text_error = exceptions.text_error_template().render_unicode()
             assert 'Traceback (most recent call last):' in text_error
             assert ("CompileException: Fragment 'i = 0' is not a partial "
@@ -61,7 +61,7 @@ class ExceptionsTest(TemplateTest):
         """test the html_error_template with a Template containing utf8
         chars"""
 
-        if util.py3k:
+        if compat.py3k:
             code = """# -*- coding: utf-8 -*-
 % if 2 == 2: /an error
 ${'привет'}
@@ -76,9 +76,9 @@ ${u'привет'}
         try:
             template = Template(code)
             template.render_unicode()
-        except exceptions.CompileException, ce:
+        except exceptions.CompileException as ce:
             html_error = exceptions.html_error_template().render()
-            if util.py3k:
+            if compat.py3k:
                 assert ("CompileException: Fragment &#39;if 2 == 2: /an "
                     "error&#39; is not a partial control statement "
                     "at line: 2 char: 1").encode(sys.getdefaultencoding(), 'htmlentityreplace') in \
@@ -89,11 +89,11 @@ ${u'привет'}
                         "at line: 2 char: 1") in \
                         html_error
 
-            if util.py3k:
-                assert u"".encode(sys.getdefaultencoding(),
+            if compat.py3k:
+                assert "".encode(sys.getdefaultencoding(),
                                         'htmlentityreplace') in html_error
             else:
-                assert u'<pre>3</pre></div></td><td class="code">'\
+                assert '<pre>3</pre></div></td><td class="code">'\
                         '<div class="syntax-highlighted"><pre><span '\
                         'class="cp">${</span><span class="s">u&#39;'\
                         '&#x43F;&#x440;&#x438;&#x432;&#x435;&#x442;'\
@@ -109,7 +109,7 @@ ${u'привет'}
         """test the html_error_template with a Template containing utf8
         chars"""
 
-        if util.py3k:
+        if compat.py3k:
             code = """# -*- coding: utf-8 -*-
 % if 2 == 2: /an error
 ${'привет'}
@@ -124,9 +124,9 @@ ${u'привет'}
         try:
             template = Template(code)
             template.render_unicode()
-        except exceptions.CompileException, ce:
+        except exceptions.CompileException as ce:
             html_error = exceptions.html_error_template().render()
-            if util.py3k:
+            if compat.py3k:
                 assert ("CompileException: Fragment &#39;if 2 == 2: /an "
                     "error&#39; is not a partial control statement "
                     "at line: 2 char: 1").encode(sys.getdefaultencoding(), 'htmlentityreplace') in \
@@ -137,11 +137,11 @@ ${u'привет'}
                         "at line: 2 char: 1") in \
                         html_error
 
-            if util.py3k:
-                assert u"${&#39;привет&#39;}".encode(sys.getdefaultencoding(),
+            if compat.py3k:
+                assert "${&#39;привет&#39;}".encode(sys.getdefaultencoding(),
                                         'htmlentityreplace') in html_error
             else:
-                assert u"${u&#39;привет&#39;}".encode(sys.getdefaultencoding(),
+                assert "${u&#39;привет&#39;}".encode(sys.getdefaultencoding(),
                                         'htmlentityreplace') in html_error
         else:
             assert False, ("This function should trigger a CompileException, "
@@ -149,9 +149,8 @@ ${u'привет'}
 
     def test_format_closures(self):
         try:
-            exec "def foo():"\
-                 "    raise RuntimeError('test')"\
-                 in locals()
+            exec("def foo():"\
+                 "    raise RuntimeError('test')", locals())
             foo()
         except:
             html_error = exceptions.html_error_template().render()
@@ -160,23 +159,23 @@ ${u'привет'}
     @requires_python_25_or_greater
     def test_py_utf8_html_error_template(self):
         try:
-            foo = u'日本'
+            foo = '日本'
             raise RuntimeError('test')
         except:
             html_error = exceptions.html_error_template().render()
-            if util.py3k:
+            if compat.py3k:
                 assert 'RuntimeError: test' in html_error.decode('utf-8')
-                assert u"foo = &#39;日本&#39;" in html_error.decode('utf-8')
+                assert "foo = &#39;日本&#39;" in html_error.decode('utf-8')
             else:
                 assert 'RuntimeError: test' in html_error
                 assert "foo = u&#39;&#x65E5;&#x672C;&#39;" in html_error
 
     def test_py_unicode_error_html_error_template(self):
         try:
-            raise RuntimeError(u'日本')
+            raise RuntimeError(u('日本'))
         except:
             html_error = exceptions.html_error_template().render()
-            assert u"RuntimeError: 日本".encode('ascii', 'ignore') in html_error
+            assert u("RuntimeError: 日本").encode('ascii', 'ignore') in html_error
 
     @requires_pygments_14
     def test_format_exceptions_pygments(self):
@@ -220,12 +219,12 @@ ${foobar}
            exceptions reported with format_exceptions=True"""
 
         l = TemplateLookup(format_exceptions=True)
-        if util.py3k:
+        if compat.py3k:
             l.put_string("foo.html", """# -*- coding: utf-8 -*-\n${'привет' + foobar}""")
         else:
             l.put_string("foo.html", """# -*- coding: utf-8 -*-\n${u'привет' + foobar}""")
 
-        if util.py3k:
+        if compat.py3k:
             assert '<table class="error syntax-highlightedtable"><tr><td '\
                     'class="linenos"><div class="linenodiv"><pre>2</pre>'\
                     '</div></td><td class="code"><div class="error '\
@@ -251,13 +250,13 @@ ${foobar}
            exceptions reported with format_exceptions=True"""
 
         l = TemplateLookup(format_exceptions=True)
-        if util.py3k:
+        if compat.py3k:
             l.put_string("foo.html", """# -*- coding: utf-8 -*-\n${'привет' + foobar}""")
         else:
             l.put_string("foo.html", """# -*- coding: utf-8 -*-\n${u'привет' + foobar}""")
 
-        if util.py3k:
-            assert u'<div class="sourceline">${&#39;привет&#39; + foobar}</div>'\
+        if compat.py3k:
+            assert '<div class="sourceline">${&#39;привет&#39; + foobar}</div>'\
                 in result_lines(l.get_template("foo.html").render().decode('utf-8'))
         else:
             assert '${u&#39;&#x43F;&#x440;&#x438;&#x432;&#x435;'\
@@ -290,7 +289,7 @@ ${foobar}
         except:
             t, v, tback = sys.exc_info()
 
-        if not util.py3k:
+        if not compat.py3k:
             # blow away tracebaack info
             sys.exc_clear()
 
diff --git a/test/test_filters.py b/test/test_filters.py
index 658715f..d2a6d6f 100644
--- a/test/test_filters.py
+++ b/test/test_filters.py
@@ -3,7 +3,7 @@
 from mako.template import Template
 import unittest
 from test import TemplateTest, eq_, requires_python_2
-from util import result_lines, flatten_result
+from .util import result_lines, flatten_result
 
 class FilterTest(TemplateTest):
     def test_basic(self):
@@ -87,7 +87,7 @@ class FilterTest(TemplateTest):
             some stuff.... ${x}
         """, default_filters=['decode.utf8'])
         #print t.code
-        assert t.render_unicode(x="voix m’a réveillé").strip() == u"some stuff.... voix m’a réveillé"
+        assert t.render_unicode(x="voix m’a réveillé").strip() == "some stuff.... voix m’a réveillé"
 
     def test_custom_default(self):
         t = Template("""
@@ -311,7 +311,7 @@ class BufferTest(unittest.TestCase):
 
 """)
         try:
-            print template.render()
+            print(template.render())
             assert False
         except TypeError:
             assert True
diff --git a/test/test_inheritance.py b/test/test_inheritance.py
index a953847..08a46b3 100644
--- a/test/test_inheritance.py
+++ b/test/test_inheritance.py
@@ -1,7 +1,6 @@
-from mako.template import Template
-from mako import lookup, util
+from mako import lookup, compat
 import unittest
-from util import flatten_result, result_lines
+from test.util import result_lines
 
 class InheritanceTest(unittest.TestCase):
     def test_basic(self):
@@ -52,7 +51,7 @@ main_body ${parent.d()}
 full stack from the top:
     ${self.name} ${parent.name} ${parent.context['parent'].name} ${parent.context['parent'].context['parent'].name}
 """)
- 
+
         collection.put_string('layout', """
 <%inherit file="general"/>
 <%def name="d()">layout_d</%def>
@@ -94,11 +93,11 @@ ${next.body()}
              'full stack from the top:',
              'self:main self:layout self:general self:base'
         ]
- 
+
     def test_includes(self):
         """test that an included template also has its full hierarchy invoked."""
         collection = lookup.TemplateLookup()
- 
+
         collection.put_string("base", """
         <%def name="a()">base_a</%def>
         This is the base.
@@ -120,7 +119,7 @@ ${next.body()}
 """)
 
         assert result_lines(collection.get_template("index").render()) == [
-            'This is the base.', 
+            'This is the base.',
             'this is index.',
              'a is: base_a',
              'This is the base.',
@@ -134,7 +133,7 @@ ${next.body()}
         """test that templates used via <%namespace> have access to an inheriting 'self', and that
         the full 'self' is also exported."""
         collection = lookup.TemplateLookup()
- 
+
         collection.put_string("base", """
         <%def name="a()">base_a</%def>
         <%def name="b()">base_b</%def>
@@ -194,7 +193,7 @@ ${next.body()}
             <%def name="foo()">
                 ${next.body(**context.kwargs)}
             </%def>
- 
+
             ${foo()}
         """)
         collection.put_string("index", """
@@ -202,8 +201,8 @@ ${next.body()}
             <%page args="x, y, z=7"/>
             print ${x}, ${y}, ${z}
         """)
- 
-        if util.py3k:
+
+        if compat.py3k:
             assert result_lines(collection.get_template('index').render_unicode(x=5,y=10)) == [
                 "this is the base.",
                 "pageargs: (type: <class 'dict'>) [('x', 5), ('y', 10)]",
@@ -215,14 +214,14 @@ ${next.body()}
                 "pageargs: (type: <type 'dict'>) [('x', 5), ('y', 10)]",
                 "print 5, 10, 7"
             ]
- 
+
     def test_pageargs_2(self):
         collection = lookup.TemplateLookup()
         collection.put_string("base", """
             this is the base.
- 
+
             ${next.body(**context.kwargs)}
- 
+
             <%def name="foo(**kwargs)">
                 ${next.body(**kwargs)}
             </%def>
@@ -245,7 +244,7 @@ ${next.body()}
             "pageargs: 12, 15, 8",
             "pageargs: 5, 10, 16"
         ]
- 
+
     def test_pageargs_err(self):
         collection = lookup.TemplateLookup()
         collection.put_string("base", """
@@ -258,11 +257,11 @@ ${next.body()}
             print ${x}, ${y}, ${z}
         """)
         try:
-            print collection.get_template('index').render(x=5,y=10)
+            print(collection.get_template('index').render(x=5,y=10))
             assert False
         except TypeError:
             assert True
- 
+
     def test_toplevel(self):
         collection = lookup.TemplateLookup()
         collection.put_string("base", """
@@ -305,7 +304,7 @@ ${next.body()}
             'this is the base.',
             'this is index.'
         ]
- 
+
     def test_in_call(self):
         collection = lookup.TemplateLookup()
         collection.put_string("/layout.html","""
@@ -332,7 +331,7 @@ ${next.body()}
         </%def>
         <%inherit file="/layout.html"/>
         """)
- 
+
         collection.put_string("/subdir/renderedtemplate.html","""
         Holy smokes!
         <%inherit file="/subdir/layout.html"/>
diff --git a/test/test_lexer.py b/test/test_lexer.py
index 61204ff..f6be383 100644
--- a/test/test_lexer.py
+++ b/test/test_lexer.py
@@ -1,11 +1,9 @@
-import unittest
-
 from mako.lexer import Lexer
-from mako import exceptions, util
-from util import flatten_result, result_lines
+from mako import exceptions, util, compat
+from test.util import flatten_result
 from mako.template import Template
 import re
-from test import TemplateTest, template_base, skip_if, eq_, assert_raises_message
+from test import TemplateTest, eq_, assert_raises_message
 
 # create fake parsetree classes which are constructed
 # exactly as the repr() of a real parsetree object.
@@ -18,21 +16,31 @@ def repr_arg(x):
     else:
         return repr(x)
 
+def _as_unicode(arg):
+    if isinstance(arg, compat.string_types):
+        return compat.text_type(arg)
+    elif isinstance(arg, dict):
+        return dict(
+            (_as_unicode(k), _as_unicode(v))
+            for k, v in arg.items()
+        )
+    else:
+        return arg
 from mako import parsetree
-for cls in parsetree.__dict__.values():
+for cls in list(parsetree.__dict__.values()):
     if isinstance(cls, type) and \
         issubclass(cls, parsetree.Node):
         clsname = cls.__name__
-        exec ("""
+        exec(("""
 class %s(object):
     def __init__(self, *args):
-        self.args = args
+        self.args = [_as_unicode(arg) for arg in args]
     def __repr__(self):
         return "%%s(%%s)" %% (
             self.__class__.__name__,
             ", ".join(repr_arg(x) for x in self.args)
             )
-""" % clsname) in locals()
+""" % clsname), locals())
 
 # NOTE: most assertion expressions were generated, then formatted
 # by PyTidy, hence the dense formatting.
@@ -48,28 +56,28 @@ class LexerTest(TemplateTest):
         <%def name="foo()">
                 this is a def.
         </%def>
-        
+
         and some more text.
 """
         node = Lexer(template).parse()
         self._compare(node, TemplateNode({},
-                      [Text(u'''\n<b>Hello world</b>\n        ''', (1,
-                      1)), DefTag(u'def', {u'name': u'foo()'}, (3, 9),
-                      [Text(u'''\n                this is a def.\n        ''',
+                      [Text('''\n<b>Hello world</b>\n        ''', (1,
+                      1)), DefTag('def', {'name': 'foo()'}, (3, 9),
+                      [Text('''\n                this is a def.\n        ''',
                       (3, 28))]),
-                      Text(u'''\n        \n        and some more text.\n''',
+                      Text('''\n\n        and some more text.\n''',
                       (5, 16))]))
 
     def test_unclosed_tag(self):
         template = """
-        
+
             <%def name="foo()">
              other text
         """
         try:
             nodes = Lexer(template).parse()
             assert False
-        except exceptions.SyntaxException, e:
+        except exceptions.SyntaxException as e:
             assert str(e) == "Unclosed tag: <%def> at line: 5 char: 9"
 
     def test_onlyclosed_tag(self):
@@ -78,9 +86,9 @@ class LexerTest(TemplateTest):
             <%def name="foo()">
                 foo
             </%def>
-            
+
             </%namespace>
-            
+
             hi.
         """
         self.assertRaises(exceptions.SyntaxException,
@@ -102,8 +110,8 @@ class LexerTest(TemplateTest):
             foo
             </%namespace>
         </%def>
-        
-        
+
+
         hi.
 """
         self.assertRaises(exceptions.SyntaxException,
@@ -121,7 +129,7 @@ class LexerTest(TemplateTest):
             """
             <%DEF name="foo()">
             </%def>
-        
+
         """
         self.assertRaises(exceptions.CompileException,
                           Lexer(template).parse)
@@ -129,7 +137,7 @@ class LexerTest(TemplateTest):
     def test_percent_escape(self):
         template = \
             """
-        
+
 %% some whatever.
 
     %% more some whatever
@@ -137,12 +145,12 @@ class LexerTest(TemplateTest):
     % endif
         """
         node = Lexer(template).parse()
-        self._compare(node, TemplateNode({}, [Text(u'''\n        \n''',
-                      (1, 1)), Text(u'''% some whatever.\n\n''', (3, 2)),
-                      Text(u'   %% more some whatever\n', (5, 2)),
-                      ControlLine(u'if', u'if foo:', False, (6, 1)),
-                      ControlLine(u'if', u'endif', True, (7, 1)),
-                      Text(u'        ', (8, 1))]))
+        self._compare(node, TemplateNode({}, [Text('''\n\n''',
+                      (1, 1)), Text('''% some whatever.\n\n''', (3, 2)),
+                      Text('   %% more some whatever\n', (5, 2)),
+                      ControlLine('if', 'if foo:', False, (6, 1)),
+                      ControlLine('if', 'endif', True, (7, 1)),
+                      Text('        ', (8, 1))]))
 
     def test_text_tag(self):
         template = \
@@ -153,44 +161,40 @@ class LexerTest(TemplateTest):
         % endif
         <%text>
             # more code
-            
+
             % more code
             <%illegal compionent>/></>
             <%def name="laal()">def</%def>
-            
-            
+
+
         </%text>
 
         <%def name="foo()">this is foo</%def>
-        
+
         % if bar:
             code
         % endif
         """
         node = Lexer(template).parse()
-        self._compare(node, 
-            TemplateNode({}, [Text(u'\n', (1, 1)),
-              Comment(u'comment', (2, 1)), 
-              ControlLine(u'if', u'if foo:', False, (3, 1)),
-              Text(u'            hi\n', (4, 1)),
-              ControlLine(u'if', u'endif', True, (5, 1)),
-              Text(u'        ', (6, 1)), TextTag(u'text', {},
-              (6, 9),
-              [Text(u'''\n            # more code\n            '''
-              '''\n            % more code\n            '''
-              '''<%illegal compionent>/></>\n            '''
-              '''<%def name="laal()">def</%def>\n       '''
-              '''     \n            \n        ''',
-                      (6, 16))]), Text(u'''
-
-        ''', (14, 17)),
-                      DefTag(u'def', {u'name': u'foo()'}, (16, 9),
-                      [Text(u'this is foo', (16, 28))]),
-                      Text(u'''\n        \n''', (16, 46)),
-                      ControlLine(u'if', u'if bar:', False, (18, 1)),
-                      Text(u'            code\n', (19, 1)),
-                      ControlLine(u'if', u'endif', True, (20, 1)),
-                      Text(u'        ', (21, 1))]))
+        self._compare(node,
+            TemplateNode({}, [Text('\n', (1, 1)),
+                    Comment('comment', (2, 1)),
+                    ControlLine('if', 'if foo:', False, (3, 1)),
+                    Text('            hi\n', (4, 1)),
+                    ControlLine('if', 'endif', True, (5, 1)),
+                    Text('        ', (6, 1)),
+                    TextTag('text', {}, (6, 9),
+                    [Text('\n            # more code\n\n           '
+                        ' % more code\n            <%illegal compionent>/></>\n'
+                        '            <%def name="laal()">def</%def>\n\n\n        ',
+                        (6, 16))]), Text('\n\n        ', (14, 17)),
+                    DefTag('def', {'name': 'foo()'}, (16, 9),
+                    [Text('this is foo', (16, 28))]), Text('\n\n', (16, 46)),
+                    ControlLine('if', 'if bar:', False, (18, 1)),
+                    Text('            code\n', (19, 1)),
+                    ControlLine('if', 'endif', True, (20, 1)),
+                    Text('        ', (21, 1))])
+        )
 
     def test_def_syntax(self):
         template = \
@@ -220,26 +224,26 @@ class LexerTest(TemplateTest):
             </%def>
         """
         node = Lexer(template).parse()
-        self._compare(node, TemplateNode({}, [Text(u'\n            ',
-                      (1, 1)), DefTag(u'def', {u'name': u'adef()'}, (2,
+        self._compare(node, TemplateNode({}, [Text('\n            ',
+                      (1, 1)), DefTag('def', {'name': 'adef()'}, (2,
                       13),
-                      [Text(u'''\n              adef\n            ''',
-                      (2, 36))]), Text(u'\n        ', (4, 20))]))
+                      [Text('''\n              adef\n            ''',
+                      (2, 36))]), Text('\n        ', (4, 20))]))
 
     def test_ns_tag_closed(self):
         template = \
             """
-        
+
             <%self:go x="1" y="2" z="${'hi' + ' ' + 'there'}"/>
         """
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Text(u'''
-        
+                      [Text('''
+
             ''', (1, 1)),
-                      CallNamespaceTag(u'self:go', {u'x': u'1', u'y'
-                      : u'2', u'z': u"${'hi' + ' ' + 'there'}"}, (3,
-                      13), []), Text(u'\n        ', (3, 64))]))
+                      CallNamespaceTag('self:go', {'x': '1', 'y'
+                      : '2', 'z': "${'hi' + ' ' + 'there'}"}, (3,
+                      13), []), Text('\n        ', (3, 64))]))
 
     def test_ns_tag_empty(self):
         template = \
@@ -247,34 +251,34 @@ class LexerTest(TemplateTest):
             <%form:option value=""></%form:option>
         """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [Text(u'\n            ',
-                      (1, 1)), CallNamespaceTag(u'form:option',
-                      {u'value': u''}, (2, 13), []), Text(u'\n        '
+        self._compare(nodes, TemplateNode({}, [Text('\n            ',
+                      (1, 1)), CallNamespaceTag('form:option',
+                      {'value': ''}, (2, 13), []), Text('\n        '
                       , (2, 51))]))
 
     def test_ns_tag_open(self):
         template = \
             """
-        
+
             <%self:go x="1" y="${process()}">
                 this is the body
             </%self:go>
         """
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Text(u'''
-        
+                      [Text('''
+
             ''', (1, 1)),
-                      CallNamespaceTag(u'self:go', {u'x': u'1', u'y'
-                      : u'${process()}'}, (3, 13),
-                      [Text(u'''
+                      CallNamespaceTag('self:go', {'x': '1', 'y'
+                      : '${process()}'}, (3, 13),
+                      [Text('''
                 this is the body
             ''',
-                      (3, 46))]), Text(u'\n        ', (5, 24))]))
+                      (3, 46))]), Text('\n        ', (5, 24))]))
 
     def test_expr_in_attribute(self):
         """test some slightly trickier expressions.
-        
+
         you can still trip up the expression parsing, though, unless we
         integrated really deeply somehow with AST."""
 
@@ -284,26 +288,26 @@ class LexerTest(TemplateTest):
             <%call expr='foo<bar and hoho>lala and "x" + "y"'/>
         """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [Text(u'\n            ',
-                      (1, 1)), CallTag(u'call', {u'expr'
-                      : u"foo>bar and 'lala' or 'hoho'"}, (2, 13), []),
-                      Text(u'\n            ', (2, 57)), CallTag(u'call'
-                      , {u'expr': u'foo<bar and hoho>lala and "x" + "y"'
-                      }, (3, 13), []), Text(u'\n        ', (3, 64))]))
+        self._compare(nodes, TemplateNode({}, [Text('\n            ',
+                      (1, 1)), CallTag('call', {'expr'
+                      : "foo>bar and 'lala' or 'hoho'"}, (2, 13), []),
+                      Text('\n            ', (2, 57)), CallTag('call'
+                      , {'expr': 'foo<bar and hoho>lala and "x" + "y"'
+                      }, (3, 13), []), Text('\n        ', (3, 64))]))
 
     def test_pagetag(self):
         template = \
             """
             <%page cached="True", args="a, b"/>
-            
+
             some template
         """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [Text(u'\n            ',
-                      (1, 1)), PageTag(u'page', {u'args': u'a, b',
-                      u'cached': u'True'}, (2, 13), []),
-                      Text(u'''
-            
+        self._compare(nodes, TemplateNode({}, [Text('\n            ',
+                      (1, 1)), PageTag('page', {'args': 'a, b',
+                      'cached': 'True'}, (2, 13), []),
+                      Text('''
+
             some template
         ''',
                       (2, 48))]))
@@ -311,31 +315,31 @@ class LexerTest(TemplateTest):
     def test_nesting(self):
         template = \
             """
-        
+
         <%namespace name="ns">
             <%def name="lala(hi, there)">
                 <%call expr="something()"/>
             </%def>
         </%namespace>
-        
+
         """
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Text(u'''
-        
+                      [Text('''
+
         ''', (1, 1)),
-                      NamespaceTag(u'namespace', {u'name': u'ns'}, (3,
-                      9), [Text(u'\n            ', (3, 31)),
-                      DefTag(u'def', {u'name': u'lala(hi, there)'}, (4,
-                      13), [Text(u'\n                ', (4, 42)),
-                      CallTag(u'call', {u'expr': u'something()'}, (5,
-                      17), []), Text(u'\n            ', (5, 44))]),
-                      Text(u'\n        ', (6, 20))]),
-                      Text(u'''
-        
+                      NamespaceTag('namespace', {'name': 'ns'}, (3,
+                      9), [Text('\n            ', (3, 31)),
+                      DefTag('def', {'name': 'lala(hi, there)'}, (4,
+                      13), [Text('\n                ', (4, 42)),
+                      CallTag('call', {'expr': 'something()'}, (5,
+                      17), []), Text('\n            ', (5, 44))]),
+                      Text('\n        ', (6, 20))]),
+                      Text('''
+
         ''', (7, 22))]))
 
-    if util.py3k:
+    if compat.py3k:
         def test_code(self):
             template = \
 """text
@@ -350,14 +354,14 @@ more text
     %>
 """
             nodes = Lexer(template).parse()
-            self._compare(nodes, 
+            self._compare(nodes,
             TemplateNode({}, [
-                Text(u'text\n    ', (1, 1)), 
-                Code(u'\nprint("hi")\nfor x in range(1,5):\n    '
-                            'print(x)\n    \n', False, (2, 5)), 
-                Text(u'\nmore text\n    ', (6, 7)), 
-                Code(u'\nimport foo\n    \n', True, (8, 5)), 
-                Text(u'\n', (10, 7))])
+                Text('text\n    ', (1, 1)),
+                Code('\nprint("hi")\nfor x in range(1,5):\n    '
+                            'print(x)\n    \n', False, (2, 5)),
+                Text('\nmore text\n    ', (6, 7)),
+                Code('\nimport foo\n    \n', True, (8, 5)),
+                Text('\n', (10, 7))])
             )
 
 
@@ -377,14 +381,14 @@ more text
     %>
 """
             nodes = Lexer(template).parse()
-            self._compare(nodes, 
+            self._compare(nodes,
             TemplateNode({}, [
-                Text(u'text\n    ', (1, 1)), 
-                Code(u'\nprint "hi"\nfor x in range(1,5):\n    '
-                            'print x\n    \n', False, (2, 5)), 
-                Text(u'\nmore text\n    ', (6, 7)), 
-                Code(u'\nimport foo\n    \n', True, (8, 5)), 
-                Text(u'\n', (10, 7))])
+                Text('text\n    ', (1, 1)),
+                Code('\nprint "hi"\nfor x in range(1,5):\n    '
+                            'print x\n    \n', False, (2, 5)),
+                Text('\nmore text\n    ', (6, 7)),
+                Code('\nimport foo\n    \n', True, (8, 5)),
+                Text('\n', (10, 7))])
             )
 
     def test_code_and_tags(self):
@@ -409,20 +413,20 @@ more text
     result: <%call expr="foo.x(result)"/>
 """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [Text(u'\n', (1, 1)),
-                      NamespaceTag(u'namespace', {u'name': u'foo'}, (2,
-                      1), [Text(u'\n    ', (2, 24)), DefTag(u'def',
-                      {u'name': u'x()'}, (3, 5),
-                      [Text(u'''\n        this is x\n    ''', (3, 22))]),
-                      Text(u'\n    ', (5, 12)), DefTag(u'def', {u'name'
-                      : u'y()'}, (6, 5),
-                      [Text(u'''\n        this is y\n    ''', (6, 22))]),
-                      Text(u'\n', (8, 12))]), Text(u'''\n\n''', (9, 14)),
-                      Code(u'''\nresult = []\ndata = get_data()\n'''
+        self._compare(nodes, TemplateNode({}, [Text('\n', (1, 1)),
+                      NamespaceTag('namespace', {'name': 'foo'}, (2,
+                      1), [Text('\n    ', (2, 24)), DefTag('def',
+                      {'name': 'x()'}, (3, 5),
+                      [Text('''\n        this is x\n    ''', (3, 22))]),
+                      Text('\n    ', (5, 12)), DefTag('def', {'name'
+                      : 'y()'}, (6, 5),
+                      [Text('''\n        this is y\n    ''', (6, 22))]),
+                      Text('\n', (8, 12))]), Text('''\n\n''', (9, 14)),
+                      Code('''\nresult = []\ndata = get_data()\n'''
                       '''for x in data:\n    result.append(x+7)\n\n''',
-                      False, (11, 1)), Text(u'''\n\n    result: ''', (16,
-                      3)), CallTag(u'call', {u'expr': u'foo.x(result)'
-                      }, (18, 13), []), Text(u'\n', (18, 42))]))
+                      False, (11, 1)), Text('''\n\n    result: ''', (16,
+                      3)), CallTag('call', {'expr': 'foo.x(result)'
+                      }, (18, 13), []), Text('\n', (18, 42))]))
 
     def test_expression(self):
         template = \
@@ -435,77 +439,77 @@ more text
 """
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Text(u'\n        this is some ', (1, 1)),
-                      Expression(u'text', [], (2, 22)),
-                      Text(u' and this is ', (2, 29)),
-                      Expression(u'textwith ', ['escapes', 'moreescapes'
-                      ], (2, 42)), Text(u'\n        ', (2, 76)),
-                      DefTag(u'def', {u'name': u'hi()'}, (3, 9),
-                      [Text(u'\n            give me ', (3, 27)),
-                      Expression(u'foo()', [], (4, 21)), Text(u' and ',
-                      (4, 29)), Expression(u'bar()', [], (4, 34)),
-                      Text(u'\n        ', (4, 42))]), Text(u'\n        '
-                      , (5, 16)), Expression(u'hi()', [], (6, 9)),
-                      Text(u'\n', (6, 16))]))
+                      [Text('\n        this is some ', (1, 1)),
+                      Expression('text', [], (2, 22)),
+                      Text(' and this is ', (2, 29)),
+                      Expression('textwith ', ['escapes', 'moreescapes'
+                      ], (2, 42)), Text('\n        ', (2, 76)),
+                      DefTag('def', {'name': 'hi()'}, (3, 9),
+                      [Text('\n            give me ', (3, 27)),
+                      Expression('foo()', [], (4, 21)), Text(' and ',
+                      (4, 29)), Expression('bar()', [], (4, 34)),
+                      Text('\n        ', (4, 42))]), Text('\n        '
+                      , (5, 16)), Expression('hi()', [], (6, 9)),
+                      Text('\n', (6, 16))]))
 
 
     def test_tricky_expression(self):
         template = """
-        
+
             ${x and "|" or "hi"}
         """
         nodes = Lexer(template).parse()
         self._compare(
             nodes,
             TemplateNode({}, [
-                Text(u'\n        \n            ', (1, 1)), 
-                Expression(u'x and "|" or "hi"', [], (3, 13)), 
-                Text(u'\n        ', (3, 33))
+                Text('\n\n            ', (1, 1)),
+                Expression('x and "|" or "hi"', [], (3, 13)),
+                Text('\n        ', (3, 33))
             ])
         )
 
         template = """
-        
+
             ${hello + '''heres '{|}' text | | }''' | escape1}
         """
         nodes = Lexer(template).parse()
         self._compare(
             nodes,
             TemplateNode({}, [
-                Text(u'\n        \n            ', (1, 1)), 
-                Expression(u"hello + '''heres '{|}' text | | }''' ", 
-                                ['escape1'], (3, 13)), 
-                Text(u'\n        ', (3, 62))
+                Text('\n\n            ', (1, 1)),
+                Expression("hello + '''heres '{|}' text | | }''' ",
+                                ['escape1'], (3, 13)),
+                Text('\n        ', (3, 62))
             ])
         )
 
     def test_tricky_code(self):
-        if util.py3k:
+        if compat.py3k:
             template = """<% print('hi %>') %>"""
             nodes = Lexer(template).parse()
             self._compare(nodes, TemplateNode({},
-                          [Code(u"print('hi %>') \n", False, (1, 1))]))
+                          [Code("print('hi %>') \n", False, (1, 1))]))
         else:
             template = """<% print 'hi %>' %>"""
             nodes = Lexer(template).parse()
             self._compare(nodes, TemplateNode({},
-                          [Code(u"print 'hi %>' \n", False, (1, 1))]))
+                          [Code("print 'hi %>' \n", False, (1, 1))]))
 
     def test_tricky_code_2(self):
         template = \
-            """<% 
+            """<%
         # someone's comment
-        %>
+%>
         """
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Code(u""" 
+                      [Code("""
         # someone's comment
-        
+
 """,
-                      False, (1, 1)), Text(u'\n        ', (3, 11))]))
+                      False, (1, 1)), Text('\n        ', (3, 3))]))
 
-    if util.py3k:
+    if compat.py3k:
         def test_tricky_code_3(self):
             template = \
                 """<%
@@ -517,10 +521,10 @@ more text
         there
         ''')
             # someone else's comment
-        %> '''and now some text '''"""
+%> '''and now some text '''"""
             nodes = Lexer(template).parse()
             self._compare(nodes, TemplateNode({},
-                          [Code(u"""
+                          [Code("""
 print('hi')
 # this is a comment
 # another comment
@@ -529,11 +533,11 @@ print('''
         there
         ''')
 # someone else's comment
-        
+
 """,
                           False, (1, 1)),
-                          Text(u" '''and now some text '''", (10,
-                          11))]))
+                          Text(" '''and now some text '''", (10,
+                          3))]))
     else:
         def test_tricky_code_3(self):
             template = \
@@ -546,23 +550,23 @@ print('''
         there
         '''
             # someone else's comment
-        %> '''and now some text '''"""
+%> '''and now some text '''"""
             nodes = Lexer(template).parse()
             self._compare(nodes, TemplateNode({},
-                      [Code(u"""\nprint 'hi'\n# this is a comment\n"""
+                      [Code("""\nprint 'hi'\n# this is a comment\n"""
                       """# another comment\nx = 7 """
                       """# someone's '''comment\nprint '''\n        """
                       """there\n        '''\n# someone else's """
-                      """comment\n        \n""",
+                      """comment\n\n""",
                       False, (1, 1)),
-                      Text(u" '''and now some text '''", (10,11))]))
+                      Text(" '''and now some text '''", (10, 3))]))
 
     def test_tricky_code_4(self):
         template = \
             """<% foo = "\\"\\\\" %>"""
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Code(u"""foo = "\\"\\\\" \n""",
+                      [Code("""foo = "\\"\\\\" \n""",
                       False, (1, 1))]))
 
     def test_tricky_code_5(self):
@@ -570,9 +574,9 @@ print('''
             """before ${ {'key': 'value'} } after"""
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Text(u'before ', (1, 1)),
-                      Expression(u" {'key': 'value'} ", [], (1, 8)),
-                      Text(u' after', (1, 29))]))
+                      [Text('before ', (1, 1)),
+                      Expression(" {'key': 'value'} ", [], (1, 8)),
+                      Text(' after', (1, 29))]))
 
     def test_control_lines(self):
         template = \
@@ -587,21 +591,21 @@ text text la la
     tex tesl asdl l is ${l} kfmas d
       % endfor
     tetx text
-    
+
 """
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Text(u'''\ntext text la la\n''', (1, 1)),
-                      ControlLine(u'if', u'if foo():', False, (3, 1)),
-                      Text(u' mroe text la la blah blah\n', (4, 1)),
-                      ControlLine(u'if', u'endif', True, (5, 1)),
-                      Text(u'''\n        and osme more stuff\n''', (6,
-                      1)), ControlLine(u'for', u'for l in range(1,5):',
-                      False, (8, 1)), Text(u'    tex tesl asdl l is ',
-                      (9, 1)), Expression(u'l', [], (9, 24)),
-                      Text(u' kfmas d\n', (9, 28)), ControlLine(u'for',
-                      u'endfor', True, (10, 1)),
-                      Text(u'''    tetx text\n    \n''', (11, 1))]))
+                      [Text('''\ntext text la la\n''', (1, 1)),
+                      ControlLine('if', 'if foo():', False, (3, 1)),
+                      Text(' mroe text la la blah blah\n', (4, 1)),
+                      ControlLine('if', 'endif', True, (5, 1)),
+                      Text('''\n        and osme more stuff\n''', (6,
+                      1)), ControlLine('for', 'for l in range(1,5):',
+                      False, (8, 1)), Text('    tex tesl asdl l is ',
+                      (9, 1)), Expression('l', [], (9, 24)),
+                      Text(' kfmas d\n', (9, 28)), ControlLine('for',
+                      'endfor', True, (10, 1)),
+                      Text('''    tetx text\n\n''', (11, 1))]))
 
     def test_control_lines_2(self):
         template = \
@@ -610,10 +614,10 @@ text text la la
 % endfor
 """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [ControlLine(u'for',
-                      u"for file in requestattr['toc'].filenames:",
-                      False, (1, 1)), Text(u'    x\n', (2, 1)),
-                      ControlLine(u'for', u'endfor', True, (3, 1))]))
+        self._compare(nodes, TemplateNode({}, [ControlLine('for',
+                      "for file in requestattr['toc'].filenames:",
+                      False, (1, 1)), Text('    x\n', (2, 1)),
+                      ControlLine('for', 'endfor', True, (3, 1))]))
 
     def test_long_control_lines(self):
         template = \
@@ -627,13 +631,13 @@ text text la la
         self._compare(
             nodes,
             TemplateNode({}, [
-                Text(u'\n', (1, 1)), 
-                ControlLine(u'for', u"for file in \\\n        "
-                                "requestattr['toc'].filenames:", 
-                                False, (2, 1)), 
-                Text(u'        x\n', (4, 1)), 
-                ControlLine(u'for', u'endfor', True, (5, 1)), 
-                Text(u'        ', (6, 1))
+                Text('\n', (1, 1)),
+                ControlLine('for', "for file in \\\n        "
+                                "requestattr['toc'].filenames:",
+                                False, (2, 1)),
+                Text('        x\n', (4, 1)),
+                ControlLine('for', 'endfor', True, (5, 1)),
+                Text('        ', (6, 1))
             ])
         )
 
@@ -692,16 +696,16 @@ text text la la
         % endif
 """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [Text(u'\n', (1, 1)),
-                      ControlLine(u'if', u'if x:', False, (2, 1)),
-                      Text(u'            hi\n', (3, 1)),
-                      ControlLine(u'elif', u'elif y+7==10:', False, (4,
-                      1)), Text(u'            there\n', (5, 1)),
-                      ControlLine(u'elif', u'elif lala:', False, (6,
-                      1)), Text(u'            lala\n', (7, 1)),
-                      ControlLine(u'else', u'else:', False, (8, 1)),
-                      Text(u'            hi\n', (9, 1)),
-                      ControlLine(u'if', u'endif', True, (10, 1))]))
+        self._compare(nodes, TemplateNode({}, [Text('\n', (1, 1)),
+                      ControlLine('if', 'if x:', False, (2, 1)),
+                      Text('            hi\n', (3, 1)),
+                      ControlLine('elif', 'elif y+7==10:', False, (4,
+                      1)), Text('            there\n', (5, 1)),
+                      ControlLine('elif', 'elif lala:', False, (6,
+                      1)), Text('            lala\n', (7, 1)),
+                      ControlLine('else', 'else:', False, (8, 1)),
+                      Text('            hi\n', (9, 1)),
+                      ControlLine('if', 'endif', True, (10, 1))]))
 
     def test_integration(self):
         template = \
@@ -727,27 +731,27 @@ text text la la
 </table>
 """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [NamespaceTag(u'namespace'
-                      , {u'file': u'somefile.html', u'name': u'foo'},
-                      (1, 1), []), Text(u'\n', (1, 46)),
-                      Comment(u'inherit from foobar.html', (2, 1)),
-                      InheritTag(u'inherit', {u'file': u'foobar.html'},
-                      (3, 1), []), Text(u'''\n\n''', (3, 31)),
-                      DefTag(u'def', {u'name': u'header()'}, (5, 1),
-                      [Text(u'''\n     <div>header</div>\n''', (5,
-                      23))]), Text(u'\n', (7, 8)), DefTag(u'def',
-                      {u'name': u'footer()'}, (8, 1),
-                      [Text(u'''\n    <div> footer</div>\n''', (8,
-                      23))]), Text(u'''\n\n<table>\n''', (10, 8)),
-                      ControlLine(u'for', u'for j in data():', False,
-                      (13, 1)), Text(u'    <tr>\n', (14, 1)),
-                      ControlLine(u'for', u'for x in j:', False, (15,
-                      1)), Text(u'            <td>Hello ', (16, 1)),
-                      Expression(u'x', ['h'], (16, 23)), Text(u'</td>\n'
-                      , (16, 30)), ControlLine(u'for', u'endfor', True,
-                      (17, 1)), Text(u'    </tr>\n', (18, 1)),
-                      ControlLine(u'for', u'endfor', True, (19, 1)),
-                      Text(u'</table>\n', (20, 1))]))
+        self._compare(nodes, TemplateNode({}, [NamespaceTag('namespace'
+                      , {'file': 'somefile.html', 'name': 'foo'},
+                      (1, 1), []), Text('\n', (1, 46)),
+                      Comment('inherit from foobar.html', (2, 1)),
+                      InheritTag('inherit', {'file': 'foobar.html'},
+                      (3, 1), []), Text('''\n\n''', (3, 31)),
+                      DefTag('def', {'name': 'header()'}, (5, 1),
+                      [Text('''\n     <div>header</div>\n''', (5,
+                      23))]), Text('\n', (7, 8)), DefTag('def',
+                      {'name': 'footer()'}, (8, 1),
+                      [Text('''\n    <div> footer</div>\n''', (8,
+                      23))]), Text('''\n\n<table>\n''', (10, 8)),
+                      ControlLine('for', 'for j in data():', False,
+                      (13, 1)), Text('    <tr>\n', (14, 1)),
+                      ControlLine('for', 'for x in j:', False, (15,
+                      1)), Text('            <td>Hello ', (16, 1)),
+                      Expression('x', ['h'], (16, 23)), Text('</td>\n'
+                      , (16, 30)), ControlLine('for', 'endfor', True,
+                      (17, 1)), Text('    </tr>\n', (18, 1)),
+                      ControlLine('for', 'endfor', True, (19, 1)),
+                      Text('</table>\n', (20, 1))]))
 
     def test_comment_after_statement(self):
         template = \
@@ -759,12 +763,12 @@ text text la la
         % endif #end
 """
         nodes = Lexer(template).parse()
-        self._compare(nodes, TemplateNode({}, [Text(u'\n', (1, 1)),
-                      ControlLine(u'if', u'if x: #comment', False, (2,
-                      1)), Text(u'            hi\n', (3, 1)),
-                      ControlLine(u'else', u'else: #next', False, (4,
-                      1)), Text(u'            hi\n', (5, 1)),
-                      ControlLine(u'if', u'endif #end', True, (6, 1))]))
+        self._compare(nodes, TemplateNode({}, [Text('\n', (1, 1)),
+                      ControlLine('if', 'if x: #comment', False, (2,
+                      1)), Text('            hi\n', (3, 1)),
+                      ControlLine('else', 'else: #next', False, (4,
+                      1)), Text('            hi\n', (5, 1)),
+                      ControlLine('if', 'endif #end', True, (6, 1))]))
 
     def test_crlf(self):
         template = open(self._file_path("crlf.html"), 'rb').read()
@@ -772,23 +776,23 @@ text text la la
         self._compare(
             nodes,
             TemplateNode({}, [
-                Text(u'<html>\r\n\r\n', (1, 1)), 
-                PageTag(u'page', {
-                            u'args': u"a=['foo',\n                'bar']"
-                        }, (3, 1), []), 
-                Text(u'\r\n\r\nlike the name says.\r\n\r\n', (4, 26)), 
-                ControlLine(u'for', u'for x in [1,2,3]:', False, (8, 1)), 
-                Text(u'        ', (9, 1)), 
-                Expression(u'x', [], (9, 9)), 
-                ControlLine(u'for', u'endfor', True, (10, 1)), 
-                Text(u'\r\n', (11, 1)), 
-                Expression(u"trumpeter == 'Miles' and "
-                                "trumpeter or \\\n      'Dizzy'", 
-                                [], (12, 1)), 
-                Text(u'\r\n\r\n', (13, 15)), 
-                DefTag(u'def', {u'name': u'hi()'}, (15, 1), [
-                    Text(u'\r\n    hi!\r\n', (15, 19))]), 
-                    Text(u'\r\n\r\n</html>\r\n', (17, 8))
+                Text('<html>\r\n\r\n', (1, 1)),
+                PageTag('page', {
+                            'args': "a=['foo',\n                'bar']"
+                        }, (3, 1), []),
+                Text('\r\n\r\nlike the name says.\r\n\r\n', (4, 26)),
+                ControlLine('for', 'for x in [1,2,3]:', False, (8, 1)),
+                Text('        ', (9, 1)),
+                Expression('x', [], (9, 9)),
+                ControlLine('for', 'endfor', True, (10, 1)),
+                Text('\r\n', (11, 1)),
+                Expression("trumpeter == 'Miles' and "
+                                "trumpeter or \\\n      'Dizzy'",
+                                [], (12, 1)),
+                Text('\r\n\r\n', (13, 15)),
+                DefTag('def', {'name': 'hi()'}, (15, 1), [
+                    Text('\r\n    hi!\r\n', (15, 19))]),
+                    Text('\r\n\r\n</html>\r\n', (17, 8))
                 ])
         )
         assert flatten_result(Template(template).render()) \
@@ -806,7 +810,7 @@ text text la la
 # also not a comment
 
    ## this is a comment
-   
+
 this is ## not a comment
 
 <%doc> multiline
@@ -817,14 +821,14 @@ hi
 """
         nodes = Lexer(template).parse()
         self._compare(nodes, TemplateNode({},
-                      [Text(u'''\n<style>\n #someselector\n # '''
+                      [Text('''\n<style>\n #someselector\n # '''
                         '''other non comment stuff\n</style>\n''',
-                      (1, 1)), Comment(u'a comment', (6, 1)),
-                      Text(u'''\n# also not a comment\n\n''', (7, 1)),
-                      Comment(u'this is a comment', (10, 1)),
-                      Text(u'''   \nthis is ## not a comment\n\n''', (11,
-                      1)), Comment(u''' multiline\ncomment\n''', (14,
-                      1)), Text(u'''
+                      (1, 1)), Comment('a comment', (6, 1)),
+                      Text('''\n# also not a comment\n\n''', (7, 1)),
+                      Comment('this is a comment', (10, 1)),
+                      Text('''\nthis is ## not a comment\n\n''', (11,
+                      1)), Comment(''' multiline\ncomment\n''', (14,
+                      1)), Text('''
 
 hi
 ''', (16, 8))]))
@@ -842,17 +846,17 @@ hi
         </%def>
         """
         nodes = Lexer(template).parse()
-        self._compare(nodes, 
-            TemplateNode({}, [Text(u'\n        ', (1,
+        self._compare(nodes,
+            TemplateNode({}, [Text('\n        ', (1,
               1)),
-              Comment(u'''\n            this is a comment\n        ''',
-              (2, 9)), Text(u'\n        ', (4, 16)),
-              DefTag(u'def', {u'name': u'foo()'}, (5, 9),
-              [Text(u'\n            ', (5, 28)),
-              Comment(u'''\n                this is the foo func\n'''
+              Comment('''\n            this is a comment\n        ''',
+              (2, 9)), Text('\n        ', (4, 16)),
+              DefTag('def', {'name': 'foo()'}, (5, 9),
+              [Text('\n            ', (5, 28)),
+              Comment('''\n                this is the foo func\n'''
                 '''            ''',
-              (6, 13)), Text(u'\n        ', (8, 20))]),
-              Text(u'\n        ', (9, 16))]))
+              (6, 13)), Text('\n        ', (8, 20))]),
+              Text('\n        ', (9, 16))]))
 
     def test_preprocess(self):
 
@@ -866,6 +870,6 @@ hi
 # another comment
 """
         nodes = Lexer(template, preprocessor=preproc).parse()
-        self._compare(nodes, TemplateNode({}, [Text(u'''\n    hi\n''',
-                      (1, 1)), Comment(u'old style comment', (3, 1)),
-                      Comment(u'another comment', (4, 1))]))
+        self._compare(nodes, TemplateNode({}, [Text('''\n    hi\n''',
+                      (1, 1)), Comment('old style comment', (3, 1)),
+                      Comment('another comment', (4, 1))]))
diff --git a/test/test_lookup.py b/test/test_lookup.py
index 40b9009..4cf82fe 100644
--- a/test/test_lookup.py
+++ b/test/test_lookup.py
@@ -1,7 +1,7 @@
 from mako.template import Template
 from mako import lookup, exceptions, runtime
 from mako.util import FastEncodingBuffer
-from util import flatten_result, result_lines
+from .util import flatten_result, result_lines
 import unittest
 import os
 
@@ -46,7 +46,7 @@ class LookupTest(unittest.TestCase):
         try:
             t.render()
             assert False
-        except exceptions.TemplateLookupException, e:
+        except exceptions.TemplateLookupException as e:
             assert str(e) == \
                 "Template 'memory:%s' has no TemplateLookup associated" % \
                 hex(id(t))
diff --git a/test/test_loop.py b/test/test_loop.py
index 14912ee..acff53f 100644
--- a/test/test_loop.py
+++ b/test/test_loop.py
@@ -10,7 +10,7 @@ from mako.runtime import LoopStack, LoopContext
 from mako import exceptions
 from test import assert_raises_message
 from test import TemplateTest, eq_
-from util import flatten_result, result_lines
+from .util import flatten_result, result_lines
 
 class TestLoop(unittest.TestCase):
 
@@ -37,7 +37,7 @@ ${x}
         code = template.code
         assert not re.match(r"loop = __M_loop._enter\(:", code), "No need to "\
                 "generate a loop context if the loop variable wasn't accessed"
-        print template.render()
+        print(template.render())
 
     def test_loop_demo(self):
         template = Template("""x|index|reverse_index|first|last|cycle|even|odd
@@ -54,7 +54,7 @@ ${x}|${loop.index}|${loop.reverse_index}|${loop.first}|${loop.last}|${loop.cycle
         assert "loop = __M_loop._enter(" in code, "Generated a loop context since "\
                 "the loop variable was accessed"
         rendered = template.render()
-        print rendered
+        print(rendered)
         for line in expected:
             assert line in rendered, "Loop variables give information about "\
                     "the progress of the loop"
@@ -101,7 +101,7 @@ ${x} ${loop.index} <- outer loop
                 "b 1 <- parent loop"
             ]
         for line in expected:
-            print code
+            print(code)
             assert line in rendered, "The parent attribute of a loop gives "\
                     "you the previous loop context in the stack"
 
@@ -174,7 +174,7 @@ class TestLoopContext(unittest.TestCase):
         length = len(self.iterable)
         expected = tuple([length-i-1 for i in range(length)])
         actual = tuple(self.ctx.reverse_index for i in self.ctx)
-        print expected, actual
+        print(expected, actual)
         assert expected == actual, "The reverse_index is the number of "\
                 "iterations until the end"
 
diff --git a/test/test_lru.py b/test/test_lru.py
index ade48a3..cde5601 100644
--- a/test/test_lru.py
+++ b/test/test_lru.py
@@ -1,7 +1,10 @@
 from mako.util import LRUCache
-import string, unittest, time, random
+import string
+import unittest
+import time
+import random
 
-import thread
+from mako.compat import thread
 
 class item:
     def __init__(self, id):
@@ -13,19 +16,19 @@ class item:
 class LRUTest(unittest.TestCase):
 
 
-    def testlru(self): 
+    def testlru(self):
         l = LRUCache(10, threshold=.2)
- 
+
         for id in range(1,20):
             l[id] = item(id)
- 
+
         # first couple of items should be gone
-        self.assert_(not l.has_key(1)) 
-        self.assert_(not l.has_key(2))
- 
+        self.assert_(1 not in l)
+        self.assert_(2 not in l)
+
         # next batch over the threshold of 10 should be present
         for id in range(11,20):
-            self.assert_(l.has_key(id))
+            self.assert_(id in l)
 
         l[12]
         l[15]
@@ -35,25 +38,25 @@ class LRUTest(unittest.TestCase):
         l[26] = item(26)
         l[27] = item(27)
 
-        self.assert_(not l.has_key(11))
-        self.assert_(not l.has_key(13))
- 
+        self.assert_(11 not in l)
+        self.assert_(13 not in l)
+
         for id in (25, 24, 23, 14, 12, 19, 18, 17, 16, 15):
-            self.assert_(l.has_key(id)) 
+            self.assert_(id in l)
 
     def _disabled_test_threaded(self):
         size = 100
         threshold = .5
         all_elems = 2000
-        hot_zone = range(30,40)
+        hot_zone = list(range(30,40))
         cache = LRUCache(size, threshold)
- 
+
         # element to store
         class Element(object):
             def __init__(self, id):
                 self.id = id
                 self.regets = 0
- 
+
         # return an element.  we will favor ids in the relatively small
         # "hot zone" 25% of  the time.
         def get_elem():
@@ -61,7 +64,7 @@ class LRUTest(unittest.TestCase):
                 return hot_zone[random.randint(0, len(hot_zone) - 1)]
             else:
                 return random.randint(1, all_elems)
- 
+
         total = [0]
         # request thread.
         def request_elem():
@@ -74,23 +77,23 @@ class LRUTest(unittest.TestCase):
                 except KeyError:
                     e = Element(id)
                     cache[id] = e
- 
+
                 time.sleep(random.random() / 1000)
 
         for x in range(0,20):
-            thread.start_new_thread(request_elem, ())
- 
+            _thread.start_new_thread(request_elem, ())
+
         # assert size doesn't grow unbounded, doesnt shrink well below size
         for x in range(0,5):
             time.sleep(1)
-            print "size:", len(cache)
+            print("size:", len(cache))
             assert len(cache) < size + size * threshold * 2
             assert len(cache) > size - (size * .1)
- 
+
         # computs the average number of times a range of elements were "reused",
         # i.e. without being removed from the cache.
         def average_regets_in_range(start, end):
-            elem = [e for e in cache.values() if e.id >= start and e.id <= end]
+            elem = [e for e in list(cache.values()) if e.id >= start and e.id <= end]
             if len(elem) == 0:
                 return 0
             avg = sum([e.regets for e in elem]) / len(elem)
@@ -99,13 +102,13 @@ class LRUTest(unittest.TestCase):
         hotzone_avg = average_regets_in_range(30, 40)
         control_avg = average_regets_in_range(450,760)
         total_avg = average_regets_in_range(0, 2000)
- 
+
         # hotzone should be way above the others
-        print "total fetches", total[0], "hotzone", \
+        print("total fetches", total[0], "hotzone", \
                                 hotzone_avg, "control", \
-                                control_avg, "total", total_avg
- 
+                                control_avg, "total", total_avg)
+
         assert hotzone_avg > total_avg * 5 > control_avg * 5
- 
- 
+
+
 
diff --git a/test/test_namespace.py b/test/test_namespace.py
index 3c4c689..5f6d253 100644
--- a/test/test_namespace.py
+++ b/test/test_namespace.py
@@ -1,6 +1,6 @@
 from mako.template import Template
 from mako import lookup
-from util import flatten_result, result_lines
+from .util import flatten_result, result_lines
 from test import TemplateTest, eq_
 
 class NamespaceTest(TemplateTest):
diff --git a/test/test_pygen.py b/test/test_pygen.py
index 58265ff..7671bd9 100644
--- a/test/test_pygen.py
+++ b/test/test_pygen.py
@@ -1,7 +1,9 @@
 import unittest
 
 from mako.pygen import PythonPrinter, adjust_whitespace
-from StringIO import StringIO
+from mako.compat import StringIO
+from test import eq_
+
 
 class GeneratePythonTest(unittest.TestCase):
     def test_generate_normal(self):
@@ -48,7 +50,7 @@ if x > 7:
                     print "hi"
                 print "there"
                 foo(lala)
-        """
+"""
         stream = StringIO()
         printer = PythonPrinter(stream)
         printer.writeline("import lala")
@@ -59,7 +61,7 @@ if x > 7:
         printer.writeline("print y")
         printer.close()
         #print "->" + stream.getvalue().replace(' ', '#') + "<-"
-        assert stream.getvalue() == \
+        eq_(stream.getvalue(),
 """import lala
 for x in foo:
     print x
@@ -71,9 +73,9 @@ for x in foo:
         print "hi"
     print "there"
     foo(lala)
-        
+
 print y
-"""
+""")
     def test_multi_line(self):
         block = \
 """
@@ -99,7 +101,7 @@ and more block.
     do_more_stuff(g)
 
 """
-    
+
     def test_false_unindentor(self):
         stream = StringIO()
         printer = PythonPrinter(stream)
@@ -114,7 +116,7 @@ and more block.
             None
         ]:
             printer.writeline(line)
-        
+
         assert stream.getvalue() == \
 """try:
     elsemyvar = 12
@@ -123,8 +125,8 @@ and more block.
 finally:
     dosomething
 """    , stream.getvalue()
-        
-        
+
+
     def test_backslash_line(self):
         block = \
 """
@@ -167,9 +169,9 @@ print "hi"
     def test_blank_lines(self):
         text = """
     print "hi"  # a comment
-    
+
     # more comments
-    
+
     print g
 """
         assert adjust_whitespace(text) == \
diff --git a/test/test_template.py b/test/test_template.py
index 2821910..ff82581 100644
--- a/test/test_template.py
+++ b/test/test_template.py
@@ -4,10 +4,12 @@ from mako.template import Template, ModuleTemplate
 from mako.lookup import TemplateLookup
 from mako.ext.preprocessors import convert_comments
 from mako import exceptions, util, runtime
+from mako import compat
 import re
 import os
 from util import flatten_result, result_lines
 import codecs
+from mako.compat import u
 from test import TemplateTest, eq_, template_base, module_base, \
     requires_python_26_or_greater, assert_raises, assert_raises_message, \
     requires_python_2
@@ -26,7 +28,7 @@ class EncodingTest(TemplateTest):
         except:
             # <h3>Exception: <span style="color:red">Foobar</span></h3>
             markup = html_error_template().render(full=False, css=False)
-            if util.py3k:
+            if compat.py3k:
                 assert '<span style="color:red">Foobar</span></h3>'\
                             .encode('ascii') not in markup
                 assert '&lt;span style=&#34;color:red&#34;'\
@@ -40,35 +42,35 @@ class EncodingTest(TemplateTest):
 
     def test_unicode(self):
         self._do_memory_test(
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         )
 
     def test_encoding_doesnt_conflict(self):
         self._do_memory_test(
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
             output_encoding='utf-8'
         )
 
     def test_unicode_arg(self):
-        val = u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+        val = u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         self._do_memory_test(
             "${val}",
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
             template_args={'val':val}
         )
 
     def test_unicode_file(self):
         self._do_file_test(
             "unicode.html",
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         )
 
     def test_unicode_file_code(self):
         self._do_file_test(
             'unicode_code.html',
-            u"""hi, drôle de petite voix m’a réveillé.""",
+            u("""hi, drôle de petite voix m’a réveillé."""),
             filters=flatten_result
         )
 
@@ -77,24 +79,24 @@ class EncodingTest(TemplateTest):
                     directories=[template_base],
                     output_encoding='utf-8',
                     default_filters=['decode.utf8'])
-        if util.py3k:
+        if compat.py3k:
             template = lookup.get_template('/chs_unicode_py3k.html')
         else:
             template = lookup.get_template('/chs_unicode.html')
         eq_(
             flatten_result(template.render_unicode(name='毛泽东')),
-            u'毛泽东 是 新中国的主席<br/> Welcome 你 to 北京.'
+            u('毛泽东 是 新中国的主席<br/> Welcome 你 to 北京.')
         )
 
     def test_unicode_bom(self):
         self._do_file_test(
             'bom.html',
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         )
 
         self._do_file_test(
             'bommagic.html',
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         )
 
         self.assertRaises(
@@ -104,105 +106,105 @@ class EncodingTest(TemplateTest):
         )
 
     def test_unicode_memory(self):
-        val = u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+        val = u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         self._do_memory_test(
             ("## -*- coding: utf-8 -*-\n" + val).encode('utf-8'),
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         )
 
     def test_unicode_text(self):
-        val = u"""<%text>Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »</%text>"""
+        val = u("""<%text>Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »</%text>""")
         self._do_memory_test(
             ("## -*- coding: utf-8 -*-\n" + val).encode('utf-8'),
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
         )
 
     def test_unicode_text_ccall(self):
-        val = u"""
+        val = u("""
         <%def name="foo()">
             ${capture(caller.body)}
         </%def>
         <%call expr="foo()">
         <%text>Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »</%text>
-        </%call>"""
+        </%call>""")
         self._do_memory_test(
             ("## -*- coding: utf-8 -*-\n" + val).encode('utf-8'),
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
             filters=flatten_result
         )
 
     def test_unicode_literal_in_expr(self):
-        if util.py3k:
+        if compat.py3k:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 ${"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"}
-                """.encode('utf-8'),
-                u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+                """).encode('utf-8'),
+                u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
                 filters = lambda s:s.strip()
             )
         else:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 ${u"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"}
-                """.encode('utf-8'),
-                u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+                """).encode('utf-8'),
+                u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
                 filters = lambda s:s.strip()
             )
 
     def test_unicode_literal_in_expr_file(self):
         self._do_file_test(
             'unicode_expr.html',
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
             lambda t:t.strip()
         )
 
     def test_unicode_literal_in_code(self):
-        if util.py3k:
+        if compat.py3k:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%
                     context.write("Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »")
                 %>
-                """.encode('utf-8'),
-                u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+                """).encode('utf-8'),
+                u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
                 filters=lambda s:s.strip()
             )
         else:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%
                     context.write(u"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »")
                 %>
-                """.encode('utf-8'),
-                u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+                """).encode('utf-8'),
+                u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
                 filters=lambda s:s.strip()
             )
 
     def test_unicode_literal_in_controlline(self):
-        if util.py3k:
+        if compat.py3k:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%
                     x = "drôle de petite voix m’a réveillé."
                 %>
                 % if x=="drôle de petite voix m’a réveillé.":
                     hi, ${x}
                 % endif
-                """.encode('utf-8'),
-                u"""hi, drôle de petite voix m’a réveillé.""",
+                """).encode('utf-8'),
+                u("""hi, drôle de petite voix m’a réveillé."""),
                 filters=lambda s:s.strip(),
             )
         else:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%
                     x = u"drôle de petite voix m’a réveillé."
                 %>
                 % if x==u"drôle de petite voix m’a réveillé.":
                     hi, ${x}
                 % endif
-                """.encode('utf-8'),
-                u"""hi, drôle de petite voix m’a réveillé.""",
+                """).encode('utf-8'),
+                u("""hi, drôle de petite voix m’a réveillé."""),
                 filters=lambda s:s.strip(),
             )
 
@@ -210,10 +212,10 @@ class EncodingTest(TemplateTest):
         self._do_file_test(
             "unicode_arguments.html",
             [
-                u'x is: drôle de petite voix m’a réveillé',
-                u'x is: drôle de petite voix m’a réveillé',
-                u'x is: drôle de petite voix m’a réveillé',
-                u'x is: drôle de petite voix m’a réveillé',
+                u('x is: drôle de petite voix m’a réveillé'),
+                u('x is: drôle de petite voix m’a réveillé'),
+                u('x is: drôle de petite voix m’a réveillé'),
+                u('x is: drôle de petite voix m’a réveillé'),
             ],
             filters=result_lines
         )
@@ -221,59 +223,59 @@ class EncodingTest(TemplateTest):
         self._do_memory_test(
             open(self._file_path("unicode_arguments.html"), 'rb').read(),
             [
-                u'x is: drôle de petite voix m’a réveillé',
-                u'x is: drôle de petite voix m’a réveillé',
-                u'x is: drôle de petite voix m’a réveillé',
-                u'x is: drôle de petite voix m’a réveillé',
+                u('x is: drôle de petite voix m’a réveillé'),
+                u('x is: drôle de petite voix m’a réveillé'),
+                u('x is: drôle de petite voix m’a réveillé'),
+                u('x is: drôle de petite voix m’a réveillé'),
             ],
             filters=result_lines
         )
 
     def test_unicode_literal_in_def(self):
-        if util.py3k:
+        if compat.py3k:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%def name="bello(foo, bar)">
                 Foo: ${ foo }
                 Bar: ${ bar }
                 </%def>
                 <%call expr="bello(foo='árvíztűrő tükörfúrógép', bar='ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
-                </%call>""".encode('utf-8'),
-                u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+                </%call>""").encode('utf-8'),
+                u("""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP"""),
                 filters=flatten_result
             )
 
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%def name="hello(foo='árvíztűrő tükörfúrógép', bar='ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
                 Foo: ${ foo }
                 Bar: ${ bar }
                 </%def>
-                ${ hello() }""".encode('utf-8'),
-                u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+                ${ hello() }""").encode('utf-8'),
+                u("""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP"""),
                 filters=flatten_result
             )
         else:
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%def name="bello(foo, bar)">
                 Foo: ${ foo }
                 Bar: ${ bar }
                 </%def>
                 <%call expr="bello(foo=u'árvíztűrő tükörfúrógép', bar=u'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
-                </%call>""".encode('utf-8'),
-                u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+                </%call>""").encode('utf-8'),
+                u("""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP"""),
                 filters=flatten_result
             )
 
             self._do_memory_test(
-                u"""## -*- coding: utf-8 -*-
+                u("""## -*- coding: utf-8 -*-
                 <%def name="hello(foo=u'árvíztűrő tükörfúrógép', bar=u'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
                 Foo: ${ foo }
                 Bar: ${ bar }
                 </%def>
-                ${ hello() }""".encode('utf-8'),
-                u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+                ${ hello() }""").encode('utf-8'),
+                u("""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP"""),
                 filters=flatten_result
             )
 
@@ -281,31 +283,31 @@ class EncodingTest(TemplateTest):
         """test the 'input_encoding' flag on Template, and that unicode
             objects arent double-decoded"""
 
-        if util.py3k:
+        if compat.py3k:
             self._do_memory_test(
-                u"hello ${f('śląsk')}",
-                u"hello śląsk",
+                u("hello ${f('śląsk')}"),
+                u("hello śląsk"),
                 input_encoding='utf-8',
-                template_args={'f':lambda x:x}
+                template_args={'f': lambda x:x}
             )
 
             self._do_memory_test(
-                u"## -*- coding: utf-8 -*-\nhello ${f('śląsk')}",
-                u"hello śląsk",
-                template_args={'f':lambda x:x}
+                u("## -*- coding: utf-8 -*-\nhello ${f('śląsk')}"),
+                u("hello śląsk"),
+                template_args={'f': lambda x:x}
             )
         else:
             self._do_memory_test(
-                u"hello ${f(u'śląsk')}",
-                u"hello śląsk",
+                u("hello ${f(u'śląsk')}"),
+                u("hello śląsk"),
                 input_encoding='utf-8',
-                template_args={'f':lambda x:x}
+                template_args={'f': lambda x:x}
             )
 
             self._do_memory_test(
-                u"## -*- coding: utf-8 -*-\nhello ${f(u'śląsk')}",
-                u"hello śląsk",
-                template_args={'f':lambda x:x}
+                u("## -*- coding: utf-8 -*-\nhello ${f(u'śląsk')}"),
+                u("hello śląsk"),
+                template_args={'f': lambda x:x}
             )
 
     def test_raw_strings(self):
@@ -315,7 +317,7 @@ class EncodingTest(TemplateTest):
         """
 
         self._do_memory_test(
-            u"## -*- coding: utf-8 -*-\nhello ${x}",
+            u("## -*- coding: utf-8 -*-\nhello ${x}"),
             "hello śląsk",
             default_filters=[],
             template_args={'x':'śląsk'},
@@ -326,23 +328,23 @@ class EncodingTest(TemplateTest):
 
         # now, the way you *should* be doing it....
         self._do_memory_test(
-            u"## -*- coding: utf-8 -*-\nhello ${x}",
-            u"hello śląsk",
-            template_args={'x':u'śląsk'}
+            u("## -*- coding: utf-8 -*-\nhello ${x}"),
+            u("hello śląsk"),
+            template_args={'x':u('śląsk')}
         )
 
     def test_encoding(self):
         self._do_memory_test(
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
-            u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""".encode('utf-8'),
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""),
+            u("""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petite voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""").encode('utf-8'),
             output_encoding='utf-8',
             unicode_=False
         )
 
     def test_encoding_errors(self):
         self._do_memory_test(
-            u"""KGB (transliteration of "КГБ") is the Russian-language abbreviation for Committee for State Security, (Russian: Комит́ет Госуд́арственной Безоп́асности (help·info); Komitet Gosudarstvennoy Bezopasnosti)""",
-            u"""KGB (transliteration of "КГБ") is the Russian-language abbreviation for Committee for State Security, (Russian: Комит́ет Госуд́арственной Безоп́асности (help·info); Komitet Gosudarstvennoy Bezopasnosti)""".encode('iso-8859-1', 'replace'),
+            u("""KGB (transliteration of "КГБ") is the Russian-language abbreviation for Committee for State Security, (Russian: Комит́ет Госуд́арственной Безоп́асности (help·info); Komitet Gosudarstvennoy Bezopasnosti)"""),
+            u("""KGB (transliteration of "КГБ") is the Russian-language abbreviation for Committee for State Security, (Russian: Комит́ет Госуд́арственной Безоп́асности (help·info); Komitet Gosudarstvennoy Bezopasnosti)""").encode('iso-8859-1', 'replace'),
             output_encoding='iso-8859-1', encoding_errors='replace',
             unicode_=False
         )
@@ -350,7 +352,7 @@ class EncodingTest(TemplateTest):
     def test_read_unicode(self):
         lookup = TemplateLookup(directories=[template_base],
                                 filesystem_checks=True, output_encoding='utf-8')
-        if util.py3k:
+        if compat.py3k:
             template = lookup.get_template('/read_unicode_py3k.html')
         else:
             template = lookup.get_template('/read_unicode.html')
@@ -420,11 +422,7 @@ class PageArgsTest(TemplateTest):
 
         assert flatten_result(template.render(x=5, y=10)) == "this is page, 5, 10, 7"
         assert flatten_result(template.render(x=5, y=10, z=32)) == "this is page, 5, 10, 32"
-        try:
-            template.render(y=10)
-            assert False
-        except TypeError, e:
-            assert True
+        assert_raises(TypeError, template.render, y=10)
 
     def test_inherits(self):
         lookup = TemplateLookup()
@@ -1012,11 +1010,11 @@ class RichTracebackTest(TemplateTest):
     def _do_test_traceback(self, utf8, memory, syntax):
         if memory:
             if syntax:
-                source = u'## coding: utf-8\n<% print "m’a réveillé. '\
-                        u'Elle disait: « S’il vous plaît… dessine-moi un mouton! » %>'
+                source = u('## coding: utf-8\n<% print "m’a réveillé. '\
+                        'Elle disait: « S’il vous plaît… dessine-moi un mouton! » %>')
             else:
-                source = u'## coding: utf-8\n<% print u"m’a réveillé. '\
-                        u'Elle disait: « S’il vous plaît… dessine-moi un mouton! »" + str(5/0) %>'
+                source = u('## coding: utf-8\n<% print u"m’a réveillé. '\
+                        'Elle disait: « S’il vous plaît… dessine-moi un mouton! »" + str(5/0) %>')
             if utf8:
                 source = source.encode('utf-8')
             else:
@@ -1036,7 +1034,7 @@ class RichTracebackTest(TemplateTest):
             if not syntax:
                 template.render_unicode()
             assert False
-        except Exception, e:
+        except Exception:
             tback = exceptions.RichTraceback()
             if utf8:
                 assert tback.source == source.decode('utf-8')
diff --git a/test/test_tgplugin.py b/test/test_tgplugin.py
index 3aa6122..560c2fe 100644
--- a/test/test_tgplugin.py
+++ b/test/test_tgplugin.py
@@ -1,7 +1,7 @@
 import unittest
 
 from mako.ext.turbogears import TGPlugin
-from util import flatten_result, result_lines
+from .util import flatten_result, result_lines
 from test import TemplateTest, template_base
 
 tl = TGPlugin(options=dict(directories=[template_base]), extension='html')
diff --git a/test/test_util.py b/test/test_util.py
index b875f53..a7da274 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -2,7 +2,7 @@
 
 import os
 import unittest
-from mako import util, exceptions
+from mako import util, exceptions, compat
 from test import eq_, skip_if, assert_raises_message
 
 class UtilTest(unittest.TestCase):
@@ -22,7 +22,7 @@ class UtilTest(unittest.TestCase):
         eq_(buf.getvalue(), "string c string d")
 
     def test_fast_buffer_encoded(self):
-        s = u"drôl m’a rée « S’il"
+        s = "drôl m’a rée « S’il"
         buf = util.FastEncodingBuffer(encoding='utf-8')
         buf.write(s[0:10])
         buf.write(s[10:])
@@ -34,7 +34,7 @@ class UtilTest(unittest.TestCase):
         data = util.read_file(fn, 'rb')
         self.failUnless('test_util' in str(data)) # str() for py3k
 
-    @skip_if(lambda: util.pypy, "Pypy does this differently")
+    @skip_if(lambda: compat.pypy, "Pypy does this differently")
     def test_load_module(self):
         fn = os.path.join(os.path.dirname(__file__), 'test_util.py')
         module = util.load_module('mako.template', fn)
-- 
GitLab