]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
remove _compat module
authorDavid Lord <davidism@gmail.com>
Tue, 28 Jan 2020 06:05:00 +0000 (22:05 -0800)
committerDavid Lord <davidism@gmail.com>
Wed, 5 Feb 2020 16:37:40 +0000 (08:37 -0800)
29 files changed:
src/jinja2/_compat.py [deleted file]
src/jinja2/bccache.py
src/jinja2/compiler.py
src/jinja2/debug.py
src/jinja2/defaults.py
src/jinja2/environment.py
src/jinja2/exceptions.py
src/jinja2/ext.py
src/jinja2/filters.py
src/jinja2/idtracking.py
src/jinja2/lexer.py
src/jinja2/loaders.py
src/jinja2/meta.py
src/jinja2/nativetypes.py
src/jinja2/nodes.py
src/jinja2/parser.py
src/jinja2/runtime.py
src/jinja2/sandbox.py
src/jinja2/tests.py
src/jinja2/utils.py
tests/test_ext.py
tests/test_features.py
tests/test_filters.py
tests/test_lexnparse.py
tests/test_loader.py
tests/test_nativetypes.py
tests/test_regression.py
tests/test_security.py
tests/test_utils.py

diff --git a/src/jinja2/_compat.py b/src/jinja2/_compat.py
deleted file mode 100644 (file)
index 1f04495..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-# flake8: noqa
-import marshal
-import sys
-
-PY2 = sys.version_info[0] == 2
-PYPY = hasattr(sys, "pypy_translation_info")
-_identity = lambda x: x
-
-if not PY2:
-    unichr = chr
-    range_type = range
-    text_type = str
-    string_types = (str,)
-    integer_types = (int,)
-
-    iterkeys = lambda d: iter(d.keys())
-    itervalues = lambda d: iter(d.values())
-    iteritems = lambda d: iter(d.items())
-
-    import pickle
-    from io import BytesIO, StringIO
-
-    NativeStringIO = StringIO
-
-    def reraise(tp, value, tb=None):
-        if value.__traceback__ is not tb:
-            raise value.with_traceback(tb)
-        raise value
-
-    ifilter = filter
-    imap = map
-    izip = zip
-    intern = sys.intern
-
-    implements_iterator = _identity
-    implements_to_string = _identity
-    encode_filename = _identity
-
-    marshal_dump = marshal.dump
-    marshal_load = marshal.load
-
-else:
-    unichr = unichr
-    text_type = unicode
-    range_type = xrange
-    string_types = (str, unicode)
-    integer_types = (int, long)
-
-    iterkeys = lambda d: d.iterkeys()
-    itervalues = lambda d: d.itervalues()
-    iteritems = lambda d: d.iteritems()
-
-    import cPickle as pickle
-    from cStringIO import StringIO as BytesIO, StringIO
-
-    NativeStringIO = BytesIO
-
-    exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
-
-    from itertools import imap, izip, ifilter
-
-    intern = intern
-
-    def implements_iterator(cls):
-        cls.next = cls.__next__
-        del cls.__next__
-        return cls
-
-    def implements_to_string(cls):
-        cls.__unicode__ = cls.__str__
-        cls.__str__ = lambda x: x.__unicode__().encode("utf-8")
-        return cls
-
-    def encode_filename(filename):
-        if isinstance(filename, unicode):
-            return filename.encode("utf-8")
-        return filename
-
-    def marshal_dump(code, f):
-        if isinstance(f, file):
-            marshal.dump(code, f)
-        else:
-            f.write(marshal.dumps(code))
-
-    def marshal_load(f):
-        if isinstance(f, file):
-            return marshal.load(f)
-        return marshal.loads(f.read())
-
-
-def with_metaclass(meta, *bases):
-    """Create a base class with a metaclass."""
-    # This requires a bit of explanation: the basic idea is to make a
-    # dummy metaclass for one level of class instantiation that replaces
-    # itself with the actual metaclass.
-    class metaclass(type):
-        def __new__(cls, name, this_bases, d):
-            return meta(name, bases, d)
-
-    return type.__new__(metaclass, "temporary_class", (), {})
-
-
-try:
-    from urllib.parse import quote_from_bytes as url_quote
-except ImportError:
-    from urllib import quote as url_quote
-
-
-try:
-    from collections import abc
-except ImportError:
-    import collections as abc
-
-
-try:
-    from os import fspath
-except ImportError:
-    try:
-        from pathlib import PurePath
-    except ImportError:
-        PurePath = None
-
-    def fspath(path):
-        if hasattr(path, "__fspath__"):
-            return path.__fspath__()
-
-        # Python 3.5 doesn't have __fspath__ yet, use str.
-        if PurePath is not None and isinstance(path, PurePath):
-            return str(path)
-
-        return path
index ff4d606fd512d0272dbe861a3cf9f07883fa1fa9..b328b3b08aba0e78cece8921add4b5a4ffda0dd7 100644 (file)
@@ -8,19 +8,15 @@ are initialized on the first request.
 """
 import errno
 import fnmatch
+import marshal
 import os
+import pickle
 import stat
 import sys
 import tempfile
 from hashlib import sha1
-from os import listdir
-from os import path
-
-from ._compat import BytesIO
-from ._compat import marshal_dump
-from ._compat import marshal_load
-from ._compat import pickle
-from ._compat import text_type
+from io import BytesIO
+
 from .utils import open_if_exists
 
 bc_version = 4
@@ -67,7 +63,7 @@ class Bucket(object):
             return
         # if marshal_load fails then we need to reload
         try:
-            self.code = marshal_load(f)
+            self.code = marshal.load(f)
         except (EOFError, ValueError, TypeError):
             self.reset()
             return
@@ -78,7 +74,7 @@ class Bucket(object):
             raise TypeError("can't write empty bucket")
         f.write(bc_magic)
         pickle.dump(self.checksum, f, 2)
-        marshal_dump(self.code, f)
+        marshal.dump(self.code, f)
 
     def bytecode_from_string(self, string):
         """Load bytecode from a string."""
@@ -145,7 +141,7 @@ class BytecodeCache(object):
         hash = sha1(name.encode("utf-8"))
         if filename is not None:
             filename = "|" + filename
-            if isinstance(filename, text_type):
+            if isinstance(filename, str):
                 filename = filename.encode("utf-8")
             hash.update(filename)
         return hash.hexdigest()
@@ -241,7 +237,7 @@ class FileSystemBytecodeCache(BytecodeCache):
         return actual_dir
 
     def _get_cache_filename(self, bucket):
-        return path.join(self.directory, self.pattern % bucket.key)
+        return os.path.join(self.directory, self.pattern % bucket.key)
 
     def load_bytecode(self, bucket):
         f = open_if_exists(self._get_cache_filename(bucket), "rb")
@@ -264,10 +260,10 @@ class FileSystemBytecodeCache(BytecodeCache):
         # normally.
         from os import remove
 
-        files = fnmatch.filter(listdir(self.directory), self.pattern % "*")
+        files = fnmatch.filter(os.listdir(self.directory), self.pattern % "*")
         for filename in files:
             try:
-                remove(path.join(self.directory, filename))
+                remove(os.path.join(self.directory, filename))
             except OSError:
                 pass
 
index 63297b42c30f17b0c0ae08547047d070e7a53d3c..8965b320d38aff0e31614d9257e707082d36e907 100644 (file)
@@ -2,6 +2,7 @@
 """Compiles nodes from the parser into Python code."""
 from collections import namedtuple
 from functools import update_wrapper
+from io import StringIO
 from itertools import chain
 from keyword import iskeyword as is_python_keyword
 
@@ -9,13 +10,6 @@ from markupsafe import escape
 from markupsafe import Markup
 
 from . import nodes
-from ._compat import imap
-from ._compat import iteritems
-from ._compat import izip
-from ._compat import NativeStringIO
-from ._compat import range_type
-from ._compat import string_types
-from ._compat import text_type
 from .exceptions import TemplateAssertionError
 from .idtracking import Symbols
 from .idtracking import VAR_LOAD_ALIAS
@@ -38,30 +32,6 @@ operators = {
     "notin": "not in",
 }
 
-# what method to iterate over items do we want to use for dict iteration
-# in generated code?  on 2.x let's go with iteritems, on 3.x with items
-if hasattr(dict, "iteritems"):
-    dict_item_iter = "iteritems"
-else:
-    dict_item_iter = "items"
-
-code_features = ["division"]
-
-# does this python version support generator stops? (PEP 0479)
-try:
-    exec("from __future__ import generator_stop")
-    code_features.append("generator_stop")
-except SyntaxError:
-    pass
-
-# does this python version support yield from?
-try:
-    exec("def f(): yield from x()")
-except SyntaxError:
-    supports_yield_from = False
-else:
-    supports_yield_from = True
-
 
 def optimizeconst(f):
     def new_func(self, node, frame, **kwargs):
@@ -93,20 +63,16 @@ def has_safe_repr(value):
     """Does the node have a safe representation?"""
     if value is None or value is NotImplemented or value is Ellipsis:
         return True
-    if type(value) in (bool, int, float, complex, range_type, Markup) + string_types:
-        return True
-    if type(value) in (tuple, list, set, frozenset):
-        for item in value:
-            if not has_safe_repr(item):
-                return False
-        return True
-    elif type(value) is dict:
-        for key, value in iteritems(value):
-            if not has_safe_repr(key):
-                return False
-            if not has_safe_repr(value):
-                return False
+
+    if type(value) in {bool, int, float, complex, range, str, Markup}:
         return True
+
+    if type(value) in {tuple, list, set, frozenset}:
+        return all(has_safe_repr(v) for v in value)
+
+    if type(value) is dict:
+        return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())
+
     return False
 
 
@@ -249,7 +215,7 @@ class CodeGenerator(NodeVisitor):
         self, environment, name, filename, stream=None, defer_init=False, optimized=True
     ):
         if stream is None:
-            stream = NativeStringIO()
+            stream = StringIO()
         self.environment = environment
         self.name = name
         self.filename = filename
@@ -432,7 +398,7 @@ class CodeGenerator(NodeVisitor):
                 self.write(", ")
                 self.visit(kwarg, frame)
             if extra_kwargs is not None:
-                for key, value in iteritems(extra_kwargs):
+                for key, value in extra_kwargs.items():
                     self.write(", %s=%s" % (key, value))
         if node.dyn_args:
             self.write(", *")
@@ -448,7 +414,7 @@ class CodeGenerator(NodeVisitor):
                 self.visit(kwarg.value, frame)
                 self.write(", ")
             if extra_kwargs is not None:
-                for key, value in iteritems(extra_kwargs):
+                for key, value in extra_kwargs.items():
                     self.write("%r: %s, " % (key, value))
             if node.dyn_kwargs is not None:
                 self.write("}, **")
@@ -477,7 +443,7 @@ class CodeGenerator(NodeVisitor):
 
     def enter_frame(self, frame):
         undefs = []
-        for target, (action, param) in iteritems(frame.symbols.loads):
+        for target, (action, param) in frame.symbols.loads.items():
             if action == VAR_LOAD_PARAMETER:
                 pass
             elif action == VAR_LOAD_RESOLVE:
@@ -494,7 +460,7 @@ class CodeGenerator(NodeVisitor):
     def leave_frame(self, frame, with_python_scope=False):
         if not with_python_scope:
             undefs = []
-            for target, _ in iteritems(frame.symbols.loads):
+            for target in frame.symbols.loads:
                 undefs.append(target)
             if undefs:
                 self.writeline("%s = missing" % " = ".join(undefs))
@@ -612,7 +578,7 @@ class CodeGenerator(NodeVisitor):
     def dump_local_context(self, frame):
         return "{%s}" % ", ".join(
             "%r: %s" % (name, target)
-            for name, target in iteritems(frame.symbols.dump_stores())
+            for name, target in frame.symbols.dump_stores().items()
         )
 
     def write_commons(self):
@@ -704,7 +670,7 @@ class CodeGenerator(NodeVisitor):
             else:
                 self.writeline(
                     "context.exported_vars.update((%s))"
-                    % ", ".join(imap(repr, public_names))
+                    % ", ".join(map(repr, public_names))
                 )
 
     # -- Statement Visitors
@@ -715,7 +681,6 @@ class CodeGenerator(NodeVisitor):
 
         from .runtime import exported
 
-        self.writeline("from __future__ import %s" % ", ".join(code_features))
         self.writeline("from jinja2.runtime import " + ", ".join(exported))
 
         if self.environment.is_async:
@@ -781,7 +746,7 @@ class CodeGenerator(NodeVisitor):
                 self.indent()
                 self.writeline("if parent_template is not None:")
             self.indent()
-            if supports_yield_from and not self.environment.is_async:
+            if not self.environment.is_async:
                 self.writeline("yield from parent_template.root_render_func(context)")
             else:
                 self.writeline(
@@ -795,7 +760,7 @@ class CodeGenerator(NodeVisitor):
             self.outdent(1 + (not self.has_known_extends))
 
         # at this point we now have the blocks collected and can visit them too.
-        for name, block in iteritems(self.blocks):
+        for name, block in self.blocks.items():
             self.writeline(
                 "%s(context, missing=missing%s):"
                 % (self.func("block_" + name), envenv),
@@ -851,11 +816,7 @@ class CodeGenerator(NodeVisitor):
         else:
             context = self.get_context_ref()
 
-        if (
-            supports_yield_from
-            and not self.environment.is_async
-            and frame.buffer is None
-        ):
+        if not self.environment.is_async and frame.buffer is None:
             self.writeline(
                 "yield from context.blocks[%r][0](%s)" % (node.name, context), node
             )
@@ -900,9 +861,7 @@ class CodeGenerator(NodeVisitor):
         self.writeline("parent_template = environment.get_template(", node)
         self.visit(node.template, frame)
         self.write(", %r)" % self.name)
-        self.writeline(
-            "for name, parent_block in parent_template.blocks.%s():" % dict_item_iter
-        )
+        self.writeline("for name, parent_block in parent_template.blocks.items():")
         self.indent()
         self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
         self.outdent()
@@ -924,7 +883,7 @@ class CodeGenerator(NodeVisitor):
 
         func_name = "get_or_select_template"
         if isinstance(node.template, nodes.Const):
-            if isinstance(node.template.value, string_types):
+            if isinstance(node.template.value, str):
                 func_name = "get_template"
             elif isinstance(node.template.value, (tuple, list)):
                 func_name = "select_template"
@@ -958,13 +917,8 @@ class CodeGenerator(NodeVisitor):
                 "._body_stream:"
             )
         else:
-            if supports_yield_from:
-                self.writeline("yield from template._get_default_module()._body_stream")
-                skip_event_yield = True
-            else:
-                self.writeline(
-                    "for event in template._get_default_module()._body_stream:"
-                )
+            self.writeline("yield from template._get_default_module()._body_stream")
+            skip_event_yield = True
 
         if not skip_event_yield:
             self.indent()
@@ -1071,7 +1025,7 @@ class CodeGenerator(NodeVisitor):
             else:
                 self.writeline(
                     "context.exported_vars.difference_"
-                    "update((%s))" % ", ".join(imap(repr, discarded_names))
+                    "update((%s))" % ", ".join(map(repr, discarded_names))
                 )
 
     def visit_For(self, node, frame):
@@ -1262,7 +1216,7 @@ class CodeGenerator(NodeVisitor):
         with_frame = frame.inner()
         with_frame.symbols.analyze_node(node)
         self.enter_frame(with_frame)
-        for target, expr in izip(node.targets, node.values):
+        for target, expr in zip(node.targets, node.values):
             self.newline()
             self.visit(target, with_frame)
             self.write(" = ")
@@ -1278,7 +1232,7 @@ class CodeGenerator(NodeVisitor):
     #: The default finalize function if the environment isn't configured
     #: with one. Or if the environment has one, this is called on that
     #: function's output for constants.
-    _default_finalize = text_type
+    _default_finalize = str
     _finalize = None
 
     def _make_finalize(self):
@@ -1344,7 +1298,7 @@ class CodeGenerator(NodeVisitor):
 
         # Template data doesn't go through finalize.
         if isinstance(node, nodes.TemplateData):
-            return text_type(const)
+            return str(const)
 
         return finalize.const(const)
 
@@ -1353,11 +1307,11 @@ class CodeGenerator(NodeVisitor):
         ``Output`` node.
         """
         if frame.eval_ctx.volatile:
-            self.write("(escape if context.eval_ctx.autoescape else to_string)(")
+            self.write("(escape if context.eval_ctx.autoescape else str)(")
         elif frame.eval_ctx.autoescape:
             self.write("escape(")
         else:
-            self.write("to_string(")
+            self.write("str(")
 
         if finalize.src is not None:
             self.write(finalize.src)
@@ -1615,11 +1569,11 @@ class CodeGenerator(NodeVisitor):
     @optimizeconst
     def visit_Concat(self, node, frame):
         if frame.eval_ctx.volatile:
-            func_name = "(context.eval_ctx.volatile and markup_join or unicode_join)"
+            func_name = "(context.eval_ctx.volatile and markup_join or str_join)"
         elif frame.eval_ctx.autoescape:
             func_name = "markup_join"
         else:
-            func_name = "unicode_join"
+            func_name = "str_join"
         self.write("%s((" % func_name)
         for arg in node.nodes:
             self.visit(arg, frame)
index d2c5a06bf6c1b17087a2663d84b2b735766f4a34..2854a9f7730469245bd5f6ddff4a4a4ed359964d 100644 (file)
@@ -1,8 +1,8 @@
+import platform
 import sys
 from types import CodeType
 
 from . import TemplateSyntaxError
-from ._compat import PYPY
 from .utils import internal_code
 from .utils import missing
 
@@ -14,13 +14,11 @@ def rewrite_traceback_stack(source=None):
 
     This must be called within an ``except`` block.
 
-    :param exc_info: A :meth:`sys.exc_info` tuple. If not provided,
-        the current ``exc_info`` is used.
     :param source: For ``TemplateSyntaxError``, the original source if
         known.
-    :return: A :meth:`sys.exc_info` tuple that can be re-raised.
+    :return: The original exception with the rewritten traceback.
     """
-    exc_type, exc_value, tb = sys.exc_info()
+    _, exc_value, tb = sys.exc_info()
 
     if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
         exc_value.translated = True
@@ -70,7 +68,7 @@ def rewrite_traceback_stack(source=None):
     for tb in reversed(stack):
         tb_next = tb_set_next(tb, tb_next)
 
-    return exc_type, exc_value, tb_next
+    return exc_value.with_traceback(tb_next)
 
 
 def fake_traceback(exc_value, tb, filename, lineno):
@@ -215,7 +213,7 @@ if sys.version_info >= (3, 7):
         return tb
 
 
-elif PYPY:
+elif platform.python_implementation() == "PyPy":
     # PyPy might have special support, and won't work with ctypes.
     try:
         import tputil
index 8e0e7d771076a8fe0dc71584018338d2caa35fb5..72a935789093eb94111b201143c81e9be0a4bce6 100644 (file)
@@ -1,5 +1,4 @@
 # -*- coding: utf-8 -*-
-from ._compat import range_type
 from .filters import FILTERS as DEFAULT_FILTERS  # noqa: F401
 from .tests import TESTS as DEFAULT_TESTS  # noqa: F401
 from .utils import Cycler
@@ -24,7 +23,7 @@ KEEP_TRAILING_NEWLINE = False
 # default filters, tests and namespace
 
 DEFAULT_NAMESPACE = {
-    "range": range_type,
+    "range": range,
     "dict": dict,
     "lipsum": generate_lorem_ipsum,
     "cycler": Cycler,
index 63ac0c8bed27d13050f19caea0958a3cc1a0db83..da7a09f3e9cfd11d0f7d6cc71361ea4a74431d0a 100644 (file)
@@ -11,13 +11,6 @@ from functools import reduce
 from markupsafe import Markup
 
 from . import nodes
-from ._compat import encode_filename
-from ._compat import implements_iterator
-from ._compat import implements_to_string
-from ._compat import iteritems
-from ._compat import reraise
-from ._compat import string_types
-from ._compat import text_type
 from .compiler import CodeGenerator
 from .compiler import generate
 from .defaults import BLOCK_END_STRING
@@ -102,7 +95,7 @@ def load_extensions(environment, extensions):
     """
     result = {}
     for extension in extensions:
-        if isinstance(extension, string_types):
+        if isinstance(extension, str):
             extension = import_string(extension)
         result[extension.identifier] = extension(environment)
     return result
@@ -376,7 +369,7 @@ class Environment(object):
         yet.  This is used by :ref:`extensions <writing-extensions>` to register
         callbacks and configuration values without breaking inheritance.
         """
-        for key, value in iteritems(attributes):
+        for key, value in attributes.items():
             if not hasattr(self, key):
                 setattr(self, key, value)
 
@@ -421,7 +414,7 @@ class Environment(object):
         rv.overlayed = True
         rv.linked_to = self
 
-        for key, value in iteritems(args):
+        for key, value in args.items():
             if value is not missing:
                 setattr(rv, key, value)
 
@@ -431,7 +424,7 @@ class Environment(object):
             rv.cache = copy_cache(self.cache)
 
         rv.extensions = {}
-        for key, value in iteritems(self.extensions):
+        for key, value in self.extensions.items():
             rv.extensions[key] = value.bind(rv)
         if extensions is not missing:
             rv.extensions.update(load_extensions(rv, extensions))
@@ -449,7 +442,7 @@ class Environment(object):
         try:
             return obj[argument]
         except (AttributeError, TypeError, LookupError):
-            if isinstance(argument, string_types):
+            if isinstance(argument, str):
                 try:
                     attr = str(argument)
                 except Exception:
@@ -534,7 +527,7 @@ class Environment(object):
 
     def _parse(self, source, name, filename):
         """Internal parsing function used by `parse` and `compile`."""
-        return Parser(self, source, name, encode_filename(filename)).parse()
+        return Parser(self, source, name, filename).parse()
 
     def lex(self, source, name=None, filename=None):
         """Lex the given sourcecode and return a generator that yields
@@ -546,7 +539,7 @@ class Environment(object):
         of the extensions to be applied you have to filter source through
         the :meth:`preprocess` method.
         """
-        source = text_type(source)
+        source = str(source)
         try:
             return self.lexer.tokeniter(source, name, filename)
         except TemplateSyntaxError:
@@ -560,7 +553,7 @@ class Environment(object):
         return reduce(
             lambda s, e: e.preprocess(s, name, filename),
             self.iter_extensions(),
-            text_type(source),
+            str(source),
         )
 
     def _tokenize(self, source, name, filename=None, state=None):
@@ -621,7 +614,7 @@ class Environment(object):
         """
         source_hint = None
         try:
-            if isinstance(source, string_types):
+            if isinstance(source, str):
                 source_hint = source
                 source = self._parse(source, name, filename)
             source = self._generate(source, name, filename, defer_init=defer_init)
@@ -629,8 +622,6 @@ class Environment(object):
                 return source
             if filename is None:
                 filename = "<template>"
-            else:
-                filename = encode_filename(filename)
             return self._compile(source, filename)
         except TemplateSyntaxError:
             self.handle_exception(source=source_hint)
@@ -718,7 +709,7 @@ class Environment(object):
                 info.external_attr = 0o755 << 16
                 zip_file.writestr(info, data)
             else:
-                if isinstance(data, text_type):
+                if isinstance(data, str):
                     data = data.encode("utf8")
 
                 with open(os.path.join(target, filename), "wb") as f:
@@ -795,7 +786,7 @@ class Environment(object):
         """
         from .debug import rewrite_traceback_stack
 
-        reraise(*rewrite_traceback_stack(source=source))
+        raise rewrite_traceback_stack(source=source)
 
     def join_path(self, template, parent):
         """Join a template with the parent.  By default all the lookups are
@@ -892,7 +883,7 @@ class Environment(object):
 
         .. versionadded:: 2.3
         """
-        if isinstance(template_name_or_list, (string_types, Undefined)):
+        if isinstance(template_name_or_list, (str, Undefined)):
             return self.get_template(template_name_or_list, parent, globals)
         elif isinstance(template_name_or_list, Template):
             return template_name_or_list
@@ -1185,7 +1176,6 @@ class Template(object):
         return "<%s %s>" % (self.__class__.__name__, name)
 
 
-@implements_to_string
 class TemplateModule(object):
     """Represents an imported template.  All the exported names of the
     template are available as attributes on this object.  Additionally
@@ -1239,7 +1229,6 @@ class TemplateExpression(object):
         return rv
 
 
-@implements_iterator
 class TemplateStream(object):
     """A template stream works pretty much like an ordinary python generator
     but it can buffer multiple items to reduce the number of total iterations.
@@ -1265,7 +1254,7 @@ class TemplateStream(object):
             Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
         """
         close = False
-        if isinstance(fp, string_types):
+        if isinstance(fp, str):
             if encoding is None:
                 encoding = "utf-8"
             fp = open(fp, "wb")
index ac62c3b482aeb95f8d14fa841afc529846ba1fb7..8d5e89d7ccc39ebbded5047eca6ee8b6767a5e0c 100644 (file)
@@ -1,44 +1,15 @@
-# -*- coding: utf-8 -*-
-from ._compat import imap
-from ._compat import implements_to_string
-from ._compat import PY2
-from ._compat import text_type
-
-
 class TemplateError(Exception):
     """Baseclass for all template errors."""
 
-    if PY2:
-
-        def __init__(self, message=None):
-            if message is not None:
-                message = text_type(message).encode("utf-8")
-            Exception.__init__(self, message)
-
-        @property
-        def message(self):
-            if self.args:
-                message = self.args[0]
-                if message is not None:
-                    return message.decode("utf-8", "replace")
-
-        def __unicode__(self):
-            return self.message or u""
-
-    else:
-
-        def __init__(self, message=None):
-            Exception.__init__(self, message)
+    def __init__(self, message=None):
+        super().__init__(message)
 
-        @property
-        def message(self):
-            if self.args:
-                message = self.args[0]
-                if message is not None:
-                    return message
+    @property
+    def message(self):
+        if self.args:
+            return self.args[0]
 
 
-@implements_to_string
 class TemplateNotFound(IOError, LookupError, TemplateError):
     """Raised if a template does not exist.
 
@@ -95,13 +66,12 @@ class TemplatesNotFound(TemplateNotFound):
                     parts.append(name)
 
             message = u"none of the templates given were found: " + u", ".join(
-                imap(text_type, parts)
+                map(str, parts)
             )
         TemplateNotFound.__init__(self, names and names[-1] or None, message)
         self.templates = list(names)
 
 
-@implements_to_string
 class TemplateSyntaxError(TemplateError):
     """Raised to tell the user that there is a problem with the template."""
 
index 99ecb34f1095084748a2574e770b754b6bddda40..fd36e2d4c84e5725cf9ce4144467ceb8fb396bf9 100644 (file)
@@ -7,9 +7,6 @@ from sys import version_info
 from markupsafe import Markup
 
 from . import nodes
-from ._compat import iteritems
-from ._compat import string_types
-from ._compat import with_metaclass
 from .defaults import BLOCK_END_STRING
 from .defaults import BLOCK_START_STRING
 from .defaults import COMMENT_END_STRING
@@ -47,7 +44,7 @@ class ExtensionRegistry(type):
         return rv
 
 
-class Extension(with_metaclass(ExtensionRegistry, object)):
+class Extension(metaclass=ExtensionRegistry):
     """Extensions can be used to add extra functionality to the Jinja template
     system at the parser level.  Custom extensions are bound to an environment
     but may not store environment specific data on `self`.  The reason for
@@ -222,7 +219,7 @@ class InternationalizationExtension(Extension):
             self.environment.globals.pop(key, None)
 
     def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
-        if isinstance(source, string_types):
+        if isinstance(source, str):
             source = self.environment.parse(source)
         return extract_from_ast(source, gettext_functions)
 
@@ -409,7 +406,7 @@ class InternationalizationExtension(Extension):
         # enough to handle the variable expansion and autoescape
         # handling itself
         if self.environment.newstyle_gettext:
-            for key, value in iteritems(variables):
+            for key, value in variables.items():
                 # the function adds that later anyways in case num was
                 # called num, so just skip it.
                 if num_called_num and key == "num":
@@ -554,7 +551,7 @@ def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, babel_style=True
 
         strings = []
         for arg in node.args:
-            if isinstance(arg, nodes.Const) and isinstance(arg.value, string_types):
+            if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
                 strings.append(arg.value)
             else:
                 strings.append(None)
index 963c03ab547f90cffd86de41150111fea1a7cf70..9000083dc4e9d4518077479ab8c82d6f53ce1bb5 100644 (file)
@@ -3,28 +3,24 @@
 import math
 import random
 import re
+from collections import abc
 from collections import namedtuple
 from itertools import chain
 from itertools import groupby
 
 from markupsafe import escape
 from markupsafe import Markup
-from markupsafe import soft_unicode
+from markupsafe import soft_str
 
-from ._compat import abc
-from ._compat import imap
-from ._compat import iteritems
-from ._compat import string_types
-from ._compat import text_type
 from .exceptions import FilterArgumentError
 from .runtime import Undefined
 from .utils import htmlsafe_json_dumps
 from .utils import pformat
-from .utils import unicode_urlencode
+from .utils import url_quote
 from .utils import urlize
 
-_word_re = re.compile(r"\w+", re.UNICODE)
-_word_beginning_split_re = re.compile(r"([-\s\(\{\[\<]+)", re.UNICODE)
+_word_re = re.compile(r"\w+")
+_word_beginning_split_re = re.compile(r"([-\s({\[<]+)")
 
 
 def contextfilter(f):
@@ -57,7 +53,7 @@ def environmentfilter(f):
 def ignore_case(value):
     """For use as a postprocessor for :func:`make_attrgetter`. Converts strings
     to lowercase and returns other types as-is."""
-    return value.lower() if isinstance(value, string_types) else value
+    return value.lower() if isinstance(value, str) else value
 
 
 def make_attrgetter(environment, attribute, postprocess=None, default=None):
@@ -95,7 +91,7 @@ def make_multi_attrgetter(environment, attribute, postprocess=None):
     Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
     """
     attribute_parts = (
-        attribute.split(",") if isinstance(attribute, string_types) else [attribute]
+        attribute.split(",") if isinstance(attribute, str) else [attribute]
     )
     attribute = [
         _prepare_attribute_parts(attribute_part) for attribute_part in attribute_parts
@@ -120,7 +116,7 @@ def make_multi_attrgetter(environment, attribute, postprocess=None):
 def _prepare_attribute_parts(attr):
     if attr is None:
         return []
-    elif isinstance(attr, string_types):
+    elif isinstance(attr, str):
         return [int(x) if x.isdigit() else x for x in attr.split(".")]
     else:
         return [attr]
@@ -130,7 +126,7 @@ def do_forceescape(value):
     """Enforce HTML escaping.  This will probably double escape variables."""
     if hasattr(value, "__html__"):
         value = value.__html__()
-    return escape(text_type(value))
+    return escape(str(value))
 
 
 def do_urlencode(value):
@@ -149,16 +145,16 @@ def do_urlencode(value):
 
     .. versionadded:: 2.7
     """
-    if isinstance(value, string_types) or not isinstance(value, abc.Iterable):
-        return unicode_urlencode(value)
+    if isinstance(value, str) or not isinstance(value, abc.Iterable):
+        return url_quote(value)
 
     if isinstance(value, dict):
-        items = iteritems(value)
+        items = value.items()
     else:
         items = iter(value)
 
     return u"&".join(
-        "%s=%s" % (unicode_urlencode(k, for_qs=True), unicode_urlencode(v, for_qs=True))
+        "%s=%s" % (url_quote(k, for_qs=True), url_quote(v, for_qs=True))
         for k, v in items
     )
 
@@ -182,7 +178,7 @@ def do_replace(eval_ctx, s, old, new, count=None):
     if count is None:
         count = -1
     if not eval_ctx.autoescape:
-        return text_type(s).replace(text_type(old), text_type(new), count)
+        return str(s).replace(str(old), str(new), count)
     if (
         hasattr(old, "__html__")
         or hasattr(new, "__html__")
@@ -190,18 +186,18 @@ def do_replace(eval_ctx, s, old, new, count=None):
     ):
         s = escape(s)
     else:
-        s = soft_unicode(s)
-    return s.replace(soft_unicode(old), soft_unicode(new), count)
+        s = soft_str(s)
+    return s.replace(soft_str(old), soft_str(new), count)
 
 
 def do_upper(s):
     """Convert a value to uppercase."""
-    return soft_unicode(s).upper()
+    return soft_str(s).upper()
 
 
 def do_lower(s):
     """Convert a value to lowercase."""
-    return soft_unicode(s).lower()
+    return soft_str(s).lower()
 
 
 @evalcontextfilter
@@ -230,7 +226,7 @@ def do_xmlattr(_eval_ctx, d, autospace=True):
     """
     rv = u" ".join(
         u'%s="%s"' % (escape(key), escape(value))
-        for key, value in iteritems(d)
+        for key, value in d.items()
         if value is not None and not isinstance(value, Undefined)
     )
     if autospace and rv:
@@ -244,7 +240,7 @@ def do_capitalize(s):
     """Capitalize a value. The first character will be uppercase, all others
     lowercase.
     """
-    return soft_unicode(s).capitalize()
+    return soft_str(s).capitalize()
 
 
 def do_title(s):
@@ -254,7 +250,7 @@ def do_title(s):
     return "".join(
         [
             item[0].upper() + item[1:].lower()
-            for item in _word_beginning_split_re.split(soft_unicode(s))
+            for item in _word_beginning_split_re.split(soft_str(s))
             if item
         ]
     )
@@ -471,11 +467,11 @@ def do_join(eval_ctx, value, d=u"", attribute=None):
        The `attribute` parameter was added.
     """
     if attribute is not None:
-        value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
+        value = map(make_attrgetter(eval_ctx.environment, attribute), value)
 
     # no automatic escaping?  joining is a lot easier then
     if not eval_ctx.autoescape:
-        return text_type(d).join(imap(text_type, value))
+        return str(d).join(map(str, value))
 
     # if the delimiter doesn't have an html representation we check
     # if any of the items has.  If yes we do a coercion to Markup
@@ -486,20 +482,20 @@ def do_join(eval_ctx, value, d=u"", attribute=None):
             if hasattr(item, "__html__"):
                 do_escape = True
             else:
-                value[idx] = text_type(item)
+                value[idx] = str(item)
         if do_escape:
             d = escape(d)
         else:
-            d = text_type(d)
+            d = str(d)
         return d.join(value)
 
     # no html involved, to normal joining
-    return soft_unicode(d).join(imap(soft_unicode, value))
+    return soft_str(d).join(map(soft_str, value))
 
 
 def do_center(value, width=80):
     """Centers the value in a field of a given width."""
-    return text_type(value).center(width)
+    return str(value).center(width)
 
 
 @environmentfilter
@@ -764,7 +760,7 @@ def do_int(value, default=0, base=10):
     The base is ignored for decimal numbers and non-string values.
     """
     try:
-        if isinstance(value, string_types):
+        if isinstance(value, str):
             return int(value, base)
         return int(value)
     except (TypeError, ValueError):
@@ -810,19 +806,19 @@ def do_format(value, *args, **kwargs):
         raise FilterArgumentError(
             "can't handle positional and keyword arguments at the same time"
         )
-    return soft_unicode(value) % (kwargs or args)
+    return soft_str(value) % (kwargs or args)
 
 
 def do_trim(value, chars=None):
     """Strip leading and trailing characters, by default whitespace."""
-    return soft_unicode(value).strip(chars)
+    return soft_str(value).strip(chars)
 
 
 def do_striptags(value):
     """Strip SGML/XML tags and replace adjacent whitespace by one space."""
     if hasattr(value, "__html__"):
         value = value.__html__()
-    return Markup(text_type(value)).striptags()
+    return Markup(str(value)).striptags()
 
 
 def do_slice(value, slices, fill_with=None):
@@ -995,7 +991,7 @@ def do_sum(environment, iterable, attribute=None, start=0):
        attributes.  Also the `start` parameter was moved on to the right.
     """
     if attribute is not None:
-        iterable = imap(make_attrgetter(environment, attribute), iterable)
+        iterable = map(make_attrgetter(environment, attribute), iterable)
     return sum(iterable, start)
 
 
@@ -1015,14 +1011,14 @@ def do_mark_safe(value):
 
 def do_mark_unsafe(value):
     """Mark a value as unsafe.  This is the reverse operation for :func:`safe`."""
-    return text_type(value)
+    return str(value)
 
 
 def do_reverse(value):
     """Reverse the object or return an iterator that iterates over it the other
     way round.
     """
-    if isinstance(value, string_types):
+    if isinstance(value, str):
         return value[::-1]
     try:
         return reversed(value)
@@ -1355,7 +1351,7 @@ FILTERS = {
     "selectattr": do_selectattr,
     "slice": do_slice,
     "sort": do_sort,
-    "string": soft_unicode,
+    "string": soft_str,
     "striptags": do_striptags,
     "sum": do_sum,
     "title": do_title,
index 9a0d838017c938fa7a2c4279c4e8edc125608aaf..7889a2b53dd54eb45d36b4084ea8c3266b863eae 100644 (file)
@@ -1,4 +1,3 @@
-from ._compat import iteritems
 from .visitor import NodeVisitor
 
 VAR_LOAD_PARAMETER = "param"
@@ -114,7 +113,7 @@ class Symbols(object):
             self.loads.update(sym.loads)
             self.stores.update(sym.stores)
 
-        for name, branch_count in iteritems(stores):
+        for name, branch_count in stores.items():
             if branch_count == len(branch_symbols):
                 continue
             target = self.find_ref(name)
@@ -141,7 +140,7 @@ class Symbols(object):
         rv = set()
         node = self
         while node is not None:
-            for target, (instr, _) in iteritems(self.loads):
+            for target, (instr, _) in self.loads.items():
                 if instr == VAR_LOAD_PARAMETER:
                     rv.add(target)
             node = node.parent
index 8e73be820ee2510eea0a54093eda922335c5de79..0f32335c57baf69708c053b8bcb0c36b7b9d9432 100644 (file)
@@ -8,11 +8,8 @@ import re
 from ast import literal_eval
 from collections import deque
 from operator import itemgetter
+from sys import intern
 
-from ._compat import implements_iterator
-from ._compat import intern
-from ._compat import iteritems
-from ._compat import text_type
 from .exceptions import TemplateSyntaxError
 from .utils import LRUCache
 
@@ -21,7 +18,7 @@ from .utils import LRUCache
 _lexer_cache = LRUCache(50)
 
 # static regular expressions
-whitespace_re = re.compile(r"\s+", re.U)
+whitespace_re = re.compile(r"\s+")
 newline_re = re.compile(r"(\r\n|\r|\n)")
 string_re = re.compile(
     r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S
@@ -136,7 +133,7 @@ operators = {
     ";": TOKEN_SEMICOLON,
 }
 
-reverse_operators = dict([(v, k) for k, v in iteritems(operators)])
+reverse_operators = dict([(v, k) for k, v in operators.items()])
 assert len(operators) == len(reverse_operators), "operators dropped"
 operator_re = re.compile(
     "(%s)" % "|".join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))
@@ -296,7 +293,6 @@ class Token(tuple):
         return "Token(%r, %r, %r)" % (self.lineno, self.type, self.value)
 
 
-@implements_iterator
 class TokenStreamIterator(object):
     """The iterator for tokenstreams.  Iterate over the stream
     until the eof token is reached.
@@ -317,7 +313,6 @@ class TokenStreamIterator(object):
         return token
 
 
-@implements_iterator
 class TokenStream(object):
     """A token stream is an iterable that yields :class:`Token`\\s.  The
     parser however does not iterate over it but calls :meth:`next` to go
@@ -665,7 +660,6 @@ class Lexer(object):
         """This method tokenizes the text and returns the tokens in a
         generator.  Use this method if you just want to tokenize a template.
         """
-        source = text_type(source)
         lines = source.splitlines()
         if self.keep_trailing_newline and source:
             for newline in ("\r\n", "\r", "\n"):
@@ -744,7 +738,7 @@ class Lexer(object):
                         # yield for the current token the first named
                         # group that matched
                         elif token == "#bygroup":
-                            for key, value in iteritems(m.groupdict()):
+                            for key, value in m.groupdict().items():
                                 if value is not None:
                                     yield lineno, key, value
                                     lineno += value.count("\n")
@@ -804,7 +798,7 @@ class Lexer(object):
                         stack.pop()
                     # resolve the new state by group checking
                     elif new_state == "#bygroup":
-                        for key, value in iteritems(m.groupdict()):
+                        for key, value in m.groupdict().items():
                             if value is not None:
                                 stack.append(key)
                                 break
index 0a5538ad9429746bdf00d7add28220b3cc7dbee8..e48155dc00654ca6c7e5014ed076f50f03ed8d5d 100644 (file)
@@ -6,15 +6,11 @@ import os
 import pkgutil
 import sys
 import weakref
+from collections import abc
 from hashlib import sha1
 from importlib import import_module
-from os import path
 from types import ModuleType
 
-from ._compat import abc
-from ._compat import fspath
-from ._compat import iteritems
-from ._compat import string_types
 from .exceptions import TemplateNotFound
 from .utils import internalcode
 from .utils import open_if_exists
@@ -27,9 +23,9 @@ def split_template_path(template):
     pieces = []
     for piece in template.split("/"):
         if (
-            path.sep in piece
-            or (path.altsep and path.altsep in piece)
-            or piece == path.pardir
+            os.path.sep in piece
+            or (os.path.altsep and os.path.altsep in piece)
+            or piece == os.path.pardir
         ):
             raise TemplateNotFound(template)
         elif piece and piece != ".":
@@ -163,22 +159,17 @@ class FileSystemLoader(BaseLoader):
     """
 
     def __init__(self, searchpath, encoding="utf-8", followlinks=False):
-        if not isinstance(searchpath, abc.Iterable) or isinstance(
-            searchpath, string_types
-        ):
+        if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
             searchpath = [searchpath]
 
-        # In Python 3.5, os.path.join doesn't support Path. This can be
-        # simplified to list(searchpath) when Python 3.5 is dropped.
-        self.searchpath = [fspath(p) for p in searchpath]
-
+        self.searchpath = list(searchpath)
         self.encoding = encoding
         self.followlinks = followlinks
 
     def get_source(self, environment, template):
         pieces = split_template_path(template)
         for searchpath in self.searchpath:
-            filename = path.join(searchpath, *pieces)
+            filename = os.path.join(searchpath, *pieces)
             f = open_if_exists(filename)
             if f is None:
                 continue
@@ -187,11 +178,11 @@ class FileSystemLoader(BaseLoader):
             finally:
                 f.close()
 
-            mtime = path.getmtime(filename)
+            mtime = os.path.getmtime(filename)
 
             def uptodate():
                 try:
-                    return path.getmtime(filename) == mtime
+                    return os.path.getmtime(filename) == mtime
                 except OSError:
                     return False
 
@@ -403,7 +394,7 @@ class FunctionLoader(BaseLoader):
         rv = self.load_func(template)
         if rv is None:
             raise TemplateNotFound(template)
-        elif isinstance(rv, string_types):
+        elif isinstance(rv, str):
             return rv, None, None
         return rv
 
@@ -456,7 +447,7 @@ class PrefixLoader(BaseLoader):
 
     def list_templates(self):
         result = []
-        for prefix, loader in iteritems(self.mapping):
+        for prefix, loader in self.mapping.items():
             for template in loader.list_templates():
                 result.append(prefix + self.delimiter + template)
         return result
@@ -529,10 +520,10 @@ class ModuleLoader(BaseLoader):
         # path given.
         mod = _TemplateModule(package_name)
 
-        if not isinstance(path, abc.Iterable) or isinstance(path, string_types):
+        if not isinstance(path, abc.Iterable) or isinstance(path, str):
             path = [path]
 
-        mod.__path__ = [fspath(p) for p in path]
+        mod.__path__ = [os.fspath(p) for p in path]
 
         sys.modules[package_name] = weakref.proxy(
             mod, lambda x: sys.modules.pop(package_name, None)
index 3795aace59da12815544dfcd5bb9973e196f7a43..d112cbed660079cae5a6c668ffddb1432a2daab8 100644 (file)
@@ -3,8 +3,6 @@
 interesting for introspection.
 """
 from . import nodes
-from ._compat import iteritems
-from ._compat import string_types
 from .compiler import CodeGenerator
 
 
@@ -21,7 +19,7 @@ class TrackingCodeGenerator(CodeGenerator):
     def enter_frame(self, frame):
         """Remember all undeclared identifiers."""
         CodeGenerator.enter_frame(self, frame)
-        for _, (action, param) in iteritems(frame.symbols.loads):
+        for _, (action, param) in frame.symbols.loads.items():
             if action == "resolve" and param not in self.environment.globals:
                 self.undeclared_identifiers.add(param)
 
@@ -35,7 +33,7 @@ def find_undeclared_variables(ast):
     >>> from jinja2 import Environment, meta
     >>> env = Environment()
     >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
-    >>> meta.find_undeclared_variables(ast) == set(['bar'])
+    >>> meta.find_undeclared_variables(ast) == {'bar'}
     True
 
     .. admonition:: Implementation
@@ -75,7 +73,7 @@ def find_referenced_templates(ast):
                     # something const, only yield the strings and ignore
                     # non-string consts that really just make no sense
                     if isinstance(template_name, nodes.Const):
-                        if isinstance(template_name.value, string_types):
+                        if isinstance(template_name.value, str):
                             yield template_name.value
                     # something dynamic in there
                     else:
@@ -85,7 +83,7 @@ def find_referenced_templates(ast):
                 yield None
             continue
         # constant is a basestring, direct template name
-        if isinstance(node.template.value, string_types):
+        if isinstance(node.template.value, str):
             yield node.template.value
         # a tuple or list (latter *should* not happen) made of consts,
         # yield the consts that are strings.  We could warn here for
@@ -94,7 +92,7 @@ def find_referenced_templates(ast):
             node.template.value, (tuple, list)
         ):
             for template_name in node.template.value:
-                if isinstance(template_name, string_types):
+                if isinstance(template_name, str):
                     yield template_name
         # something else we don't care about, we could warn here
         else:
index 9866c962dcff21bb2dbc47c1a4252ef52f773124..4f8106bc79e26a0d41b73dfffa62e010836d9a4c 100644 (file)
@@ -4,7 +4,6 @@ from itertools import chain
 from itertools import islice
 
 from . import nodes
-from ._compat import text_type
 from .compiler import CodeGenerator
 from .compiler import has_safe_repr
 from .environment import Environment
@@ -33,7 +32,7 @@ def native_concat(nodes, preserve_quotes=True):
     else:
         if isinstance(nodes, types.GeneratorType):
             nodes = chain(head, nodes)
-        raw = u"".join([text_type(v) for v in nodes])
+        raw = u"".join([str(v) for v in nodes])
 
     try:
         literal = literal_eval(raw)
@@ -52,7 +51,7 @@ def native_concat(nodes, preserve_quotes=True):
 
 class NativeCodeGenerator(CodeGenerator):
     """A code generator which renders Python types by not adding
-    ``to_string()`` around output nodes, and using :func:`native_concat`
+    ``str()`` around output nodes, and using :func:`native_concat`
     to convert complex strings back to Python types if possible.
     """
 
index c0b6d77e468a7af4230d356eb0e870ee854d9299..924b0aa020624ee8d325b38b7d51592ccd2a425b 100644 (file)
@@ -8,11 +8,6 @@ from collections import deque
 
 from markupsafe import Markup
 
-from ._compat import izip
-from ._compat import PY2
-from ._compat import text_type
-from ._compat import with_metaclass
-
 _binop_to_func = {
     "*": operator.mul,
     "/": operator.truediv,
@@ -49,9 +44,9 @@ class NodeType(type):
     def __new__(mcs, name, bases, d):
         for attr in "fields", "attributes":
             storage = []
-            storage.extend(getattr(bases[0], attr, ()))
+            storage.extend(getattr(bases[0] if bases else object, attr, ()))
             storage.extend(d.get(attr, ()))
-            assert len(bases) == 1, "multiple inheritance not allowed"
+            assert len(bases) <= 1, "multiple inheritance not allowed"
             assert len(storage) == len(set(storage)), "layout conflict"
             d[attr] = tuple(storage)
         d.setdefault("abstract", False)
@@ -91,7 +86,7 @@ def get_eval_context(node, ctx):
     return ctx
 
 
-class Node(with_metaclass(NodeType, object)):
+class Node(metaclass=NodeType):
     """Baseclass for all Jinja nodes.  There are a number of nodes available
     of different types.  There are four major types:
 
@@ -127,7 +122,7 @@ class Node(with_metaclass(NodeType, object)):
                         len(self.fields) != 1 and "s" or "",
                     )
                 )
-            for name, arg in izip(self.fields, fields):
+            for name, arg in zip(self.fields, fields):
                 setattr(self, name, arg)
         for attr in self.attributes:
             setattr(self, attr, attributes.pop(attr, None))
@@ -510,17 +505,7 @@ class Const(Literal):
     fields = ("value",)
 
     def as_const(self, eval_ctx=None):
-        rv = self.value
-        if (
-            PY2
-            and type(rv) is text_type
-            and self.environment.policies["compiler.ascii_str"]
-        ):
-            try:
-                rv = rv.encode("ascii")
-            except UnicodeError:
-                pass
-        return rv
+        return self.value
 
     @classmethod
     def from_untrusted(cls, value, lineno=None, environment=None):
@@ -796,7 +781,7 @@ class Concat(Expr):
 
     def as_const(self, eval_ctx=None):
         eval_ctx = get_eval_context(self, eval_ctx)
-        return "".join(text_type(x.as_const(eval_ctx)) for x in self.nodes)
+        return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)
 
 
 class Compare(Expr):
index d5881066f7461b01291067f19c356d39dc75d38b..ec0778a7080ff99d3ce274215d653be6f2f4ac7a 100644 (file)
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 """Parse tokens from the lexer into nodes for the compiler."""
 from . import nodes
-from ._compat import imap
 from .exceptions import TemplateAssertionError
 from .exceptions import TemplateSyntaxError
 from .lexer import describe_token
@@ -66,7 +65,7 @@ class Parser(object):
     def _fail_ut_eof(self, name, end_token_stack, lineno):
         expected = []
         for exprs in end_token_stack:
-            expected.extend(imap(describe_token_expr, exprs))
+            expected.extend(map(describe_token_expr, exprs))
         if end_token_stack:
             currently_looking = " or ".join(
                 "'%s'" % describe_token_expr(expr) for expr in end_token_stack[-1]
index 8df42c590e480d4f596d12553de9675e7150e0c7..462238b5493d03680417ebae95a816754c3f932d 100644 (file)
@@ -1,22 +1,14 @@
 # -*- coding: utf-8 -*-
 """The runtime functions and state used by compiled templates."""
 import sys
+from collections import abc
 from itertools import chain
 from types import MethodType
 
 from markupsafe import escape  # noqa: F401
 from markupsafe import Markup
-from markupsafe import soft_unicode
-
-from ._compat import abc
-from ._compat import imap
-from ._compat import implements_iterator
-from ._compat import implements_to_string
-from ._compat import iteritems
-from ._compat import PY2
-from ._compat import string_types
-from ._compat import text_type
-from ._compat import with_metaclass
+from markupsafe import soft_str
+
 from .exceptions import TemplateNotFound  # noqa: F401
 from .exceptions import TemplateRuntimeError  # noqa: F401
 from .exceptions import UndefinedError
@@ -39,18 +31,13 @@ exported = [
     "concat",
     "escape",
     "markup_join",
-    "unicode_join",
-    "to_string",
+    "str_join",
     "identity",
     "TemplateNotFound",
     "Namespace",
     "Undefined",
 ]
 
-#: the name of the function that is used to convert something into
-#: a string.  We can just use the text type here.
-to_string = text_type
-
 
 def identity(x):
     """Returns its argument. Useful for certain things in the
@@ -62,7 +49,7 @@ def identity(x):
 def markup_join(seq):
     """Concatenation that escapes if necessary and converts to string."""
     buf = []
-    iterator = imap(soft_unicode, seq)
+    iterator = map(soft_str, seq)
     for arg in iterator:
         buf.append(arg)
         if hasattr(arg, "__html__"):
@@ -70,9 +57,21 @@ def markup_join(seq):
     return concat(buf)
 
 
-def unicode_join(seq):
+def str_join(seq):
     """Simple args to string conversion and concatenation."""
-    return concat(imap(text_type, seq))
+    return concat(map(str, seq))
+
+
+def unicode_join(seq):
+    import warnings
+
+    warnings.warn(
+        "This template must be recompiled with at least Jinja 3.0, or"
+        " it will fail in 3.1.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    return str_join(seq)
 
 
 def new_context(
@@ -96,7 +95,7 @@ def new_context(
         # we don't want to modify the dict passed
         if shared:
             parent = dict(parent)
-        for key, value in iteritems(locals):
+        for key, value in locals.items():
             if value is not missing:
                 parent[key] = value
     return environment.context_class(environment, parent, template_name, blocks)
@@ -155,7 +154,8 @@ def resolve_or_missing(context, key, missing=missing):
     return missing
 
 
-class Context(with_metaclass(ContextMeta)):
+@abc.Mapping.register
+class Context(metaclass=ContextMeta):
     """The template context holds the variables of a template.  It stores the
     values passed to the template and also the names the template exports.
     Creating instances is neither supported nor useful as it's created
@@ -191,7 +191,7 @@ class Context(with_metaclass(ContextMeta)):
         # create the initial mapping of blocks.  Whenever template inheritance
         # takes place the runtime will update this mapping with the new blocks
         # from the template.
-        self.blocks = dict((k, [v]) for k, v in iteritems(blocks))
+        self.blocks = {k: [v] for k, v in blocks.items()}
 
         # In case we detect the fast resolve mode we can set up an alias
         # here that bypasses the legacy code logic.
@@ -304,7 +304,7 @@ class Context(with_metaclass(ContextMeta)):
             self.environment, self.name, {}, self.get_all(), True, None, locals
         )
         context.eval_ctx = self.eval_ctx
-        context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks))
+        context.blocks.update((k, list(v)) for k, v in self.blocks.items())
         return context
 
     def _all(meth):  # noqa: B902
@@ -318,12 +318,6 @@ class Context(with_metaclass(ContextMeta)):
     keys = _all("keys")
     values = _all("values")
     items = _all("items")
-
-    # not available on python 3
-    if PY2:
-        iterkeys = _all("iterkeys")
-        itervalues = _all("itervalues")
-        iteritems = _all("iteritems")
     del _all
 
     def __contains__(self, name):
@@ -346,9 +340,6 @@ class Context(with_metaclass(ContextMeta)):
         )
 
 
-abc.Mapping.register(Context)
-
-
 class BlockReference(object):
     """One block on a template reference."""
 
@@ -375,7 +366,6 @@ class BlockReference(object):
         return rv
 
 
-@implements_iterator
 class LoopContext:
     """A wrapper iterable for dynamic ``for`` loops, with information
     about the loop and iteration.
@@ -688,7 +678,6 @@ class Macro(object):
         )
 
 
-@implements_to_string
 class Undefined(object):
     """The default undefined type.  This undefined type can be printed and
     iterated over, but every other access will raise an :exc:`UndefinedError`:
@@ -728,7 +717,7 @@ class Undefined(object):
         if self._undefined_obj is missing:
             return "%r is undefined" % self._undefined_name
 
-        if not isinstance(self._undefined_name, string_types):
+        if not isinstance(self._undefined_name, str):
             return "%s has no element %r" % (
                 object_type_repr(self._undefined_obj),
                 self._undefined_name,
@@ -752,51 +741,16 @@ class Undefined(object):
             raise AttributeError(name)
         return self._fail_with_undefined_error()
 
-    __add__ = (
-        __radd__
-    ) = (
-        __mul__
-    ) = (
-        __rmul__
-    ) = (
-        __div__
-    ) = (
-        __rdiv__
-    ) = (
-        __truediv__
-    ) = (
-        __rtruediv__
-    ) = (
-        __floordiv__
-    ) = (
-        __rfloordiv__
-    ) = (
-        __mod__
-    ) = (
-        __rmod__
-    ) = (
-        __pos__
-    ) = (
-        __neg__
-    ) = (
-        __call__
-    ) = (
-        __getitem__
-    ) = (
-        __lt__
-    ) = (
-        __le__
-    ) = (
-        __gt__
-    ) = (
-        __ge__
-    ) = (
-        __int__
-    ) = (
-        __float__
-    ) = (
-        __complex__
-    ) = __pow__ = __rpow__ = __sub__ = __rsub__ = _fail_with_undefined_error
+    __add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error
+    __mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error
+    __truediv__ = __rtruediv__ = _fail_with_undefined_error
+    __floordiv__ = __rfloordiv__ = _fail_with_undefined_error
+    __mod__ = __rmod__ = _fail_with_undefined_error
+    __pos__ = __neg__ = _fail_with_undefined_error
+    __call__ = __getitem__ = _fail_with_undefined_error
+    __lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error
+    __int__ = __float__ = __complex__ = _fail_with_undefined_error
+    __pow__ = __rpow__ = _fail_with_undefined_error
 
     def __eq__(self, other):
         return type(self) is type(other)
@@ -817,11 +771,9 @@ class Undefined(object):
         if 0:
             yield None
 
-    def __nonzero__(self):
+    def __bool__(self):
         return False
 
-    __bool__ = __nonzero__
-
     def __repr__(self):
         return "Undefined"
 
@@ -858,7 +810,7 @@ def make_logging_undefined(logger=None, base=None):
         if undef._undefined_hint is None:
             if undef._undefined_obj is missing:
                 hint = "%s is undefined" % undef._undefined_name
-            elif not isinstance(undef._undefined_name, string_types):
+            elif not isinstance(undef._undefined_name, str):
                 hint = "%s has no element %s" % (
                     object_type_repr(undef._undefined_obj),
                     undef._undefined_name,
@@ -890,31 +842,14 @@ def make_logging_undefined(logger=None, base=None):
             _log_message(self)
             return rv
 
-        if PY2:
-
-            def __nonzero__(self):
-                rv = base.__nonzero__(self)
-                _log_message(self)
-                return rv
-
-            def __unicode__(self):
-                rv = base.__unicode__(self)
-                _log_message(self)
-                return rv
-
-        else:
-
-            def __bool__(self):
-                rv = base.__bool__(self)
-                _log_message(self)
-                return rv
+        def __bool__(self):
+            rv = base.__bool__(self)
+            _log_message(self)
+            return rv
 
     return LoggingUndefined
 
 
-# No @implements_to_string decorator here because __str__
-# is not overwritten from Undefined in this class.
-# This would cause a recursion error in Python 2.
 class ChainableUndefined(Undefined):
     """An undefined that is chainable, where both ``__getattr__`` and
     ``__getitem__`` return itself rather than raising an
@@ -942,7 +877,6 @@ class ChainableUndefined(Undefined):
     __getitem__ = __getattr__
 
 
-@implements_to_string
 class DebugUndefined(Undefined):
     """An undefined that returns the debug info when printed.
 
@@ -970,7 +904,6 @@ class DebugUndefined(Undefined):
         return u"{{ undefined value printed: %s }}" % self._undefined_hint
 
 
-@implements_to_string
 class StrictUndefined(Undefined):
     """An undefined that barks on print and iteration as well as boolean
     tests and all kinds of comparisons.  In other words: you can do nothing
@@ -992,17 +925,12 @@ class StrictUndefined(Undefined):
     """
 
     __slots__ = ()
-    __iter__ = (
-        __str__
-    ) = (
-        __len__
-    ) = (
-        __nonzero__
-    ) = __eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error
+    __iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error
+    __eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error
 
 
-# remove remaining slots attributes, after the metaclass did the magic they
-# are unneeded and irritating as they contain wrong data for the subclasses.
+# Remove slots attributes, after the metaclass is applied they are
+# unneeded and contain wrong data for subclasses.
 del (
     Undefined.__slots__,
     ChainableUndefined.__slots__,
index 4629143baad144f1049dcc6bb3582ede1cbf1d2a..639fcdb5afc37cb7cb0ade5aafecc3bcb5350c71 100644 (file)
@@ -4,38 +4,24 @@ Useful when the template itself comes from an untrusted source.
 """
 import operator
 import types
+from collections import abc
 from collections import deque
 from string import Formatter
 
 from markupsafe import EscapeFormatter
 from markupsafe import Markup
 
-from ._compat import abc
-from ._compat import PY2
-from ._compat import range_type
-from ._compat import string_types
 from .environment import Environment
 from .exceptions import SecurityError
 
 #: maximum number of items a range may produce
 MAX_RANGE = 100000
 
-#: attributes of function objects that are considered unsafe.
-if PY2:
-    UNSAFE_FUNCTION_ATTRIBUTES = {
-        "func_closure",
-        "func_code",
-        "func_dict",
-        "func_defaults",
-        "func_globals",
-    }
-else:
-    # On versions > python 2 the special attributes on functions are gone,
-    # but they remain on methods and generators for whatever reason.
-    UNSAFE_FUNCTION_ATTRIBUTES = set()
+#: Unsafe function attributes.
+UNSAFE_FUNCTION_ATTRIBUTES = set()
 
-#: unsafe method attributes.  function attributes are unsafe for methods too
-UNSAFE_METHOD_ATTRIBUTES = {"im_class", "im_func", "im_self"}
+#: Unsafe method attributes. Function attributes are unsafe for methods too.
+UNSAFE_METHOD_ATTRIBUTES = set()
 
 #: unsafe generator attributes.
 UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
@@ -46,36 +32,9 @@ UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
 #: unsafe attributes on async generators
 UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
 
-_mutable_set_types = (set,)
-_mutable_mapping_types = (dict,)
-_mutable_sequence_types = (list,)
-
-# on python 2.x we can register the user collection types
-try:
-    from UserDict import UserDict, DictMixin
-    from UserList import UserList
-
-    _mutable_mapping_types += (UserDict, DictMixin)
-    _mutable_set_types += (UserList,)
-except ImportError:
-    pass
-
-# if sets is still available, register the mutable set from there as well
-try:
-    from sets import Set
-
-    _mutable_set_types += (Set,)
-except ImportError:
-    pass
-
-#: register Python 2.6 abstract base classes
-_mutable_set_types += (abc.MutableSet,)
-_mutable_mapping_types += (abc.MutableMapping,)
-_mutable_sequence_types += (abc.MutableSequence,)
-
 _mutable_spec = (
     (
-        _mutable_set_types,
+        abc.MutableSet,
         frozenset(
             [
                 "add",
@@ -90,11 +49,11 @@ _mutable_spec = (
         ),
     ),
     (
-        _mutable_mapping_types,
+        abc.MutableMapping,
         frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
     ),
     (
-        _mutable_sequence_types,
+        abc.MutableSequence,
         frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]),
     ),
     (
@@ -153,7 +112,7 @@ def inspect_format_method(callable):
     ) or callable.__name__ not in ("format", "format_map"):
         return None
     obj = callable.__self__
-    if isinstance(obj, string_types):
+    if isinstance(obj, str):
         return obj
 
 
@@ -161,7 +120,7 @@ def safe_range(*args):
     """A range that can't generate ranges with a length of more than
     MAX_RANGE items.
     """
-    rng = range_type(*args)
+    rng = range(*args)
 
     if len(rng) > MAX_RANGE:
         raise OverflowError(
@@ -376,7 +335,7 @@ class SandboxedEnvironment(Environment):
         try:
             return obj[argument]
         except (TypeError, LookupError):
-            if isinstance(argument, string_types):
+            if isinstance(argument, str):
                 try:
                     attr = str(argument)
                 except Exception:
index fabd4ce51b6a049955d903762986974f59c625d0..6a24d9c0bac469a088d6ef1f27fec2d158981d12 100644 (file)
@@ -1,13 +1,10 @@
 # -*- coding: utf-8 -*-
 """Built-in template tests used with the ``is`` operator."""
-import decimal
 import operator
 import re
+from collections import abc
+from numbers import Number
 
-from ._compat import abc
-from ._compat import integer_types
-from ._compat import string_types
-from ._compat import text_type
 from .runtime import Undefined
 
 number_re = re.compile(r"^-?\d+(\.\d+)?$")
@@ -87,7 +84,7 @@ def test_integer(value):
 
     .. versionadded:: 2.11
     """
-    return isinstance(value, integer_types) and value is not True and value is not False
+    return isinstance(value, int) and value is not True and value is not False
 
 
 # NOTE: The existing 'number' test matches booleans and integers
@@ -101,17 +98,17 @@ def test_float(value):
 
 def test_lower(value):
     """Return true if the variable is lowercased."""
-    return text_type(value).islower()
+    return str(value).islower()
 
 
 def test_upper(value):
     """Return true if the variable is uppercased."""
-    return text_type(value).isupper()
+    return str(value).isupper()
 
 
 def test_string(value):
     """Return true if the object is a string."""
-    return isinstance(value, string_types)
+    return isinstance(value, str)
 
 
 def test_mapping(value):
@@ -124,7 +121,7 @@ def test_mapping(value):
 
 def test_number(value):
     """Return true if the variable is a number."""
-    return isinstance(value, integer_types + (float, complex, decimal.Decimal))
+    return isinstance(value, Number)
 
 
 def test_sequence(value):
index 7e1ad9117e77726277e2da0f7ead5bb58616e08f..e80f7ebb44eab2670023c51a8973b17182835511 100644 (file)
@@ -2,19 +2,16 @@
 import json
 import os
 import re
+from collections import abc
 from collections import deque
 from random import choice
 from random import randrange
 from threading import Lock
+from urllib.parse import quote_from_bytes
 
 from markupsafe import escape
 from markupsafe import Markup
 
-from ._compat import abc
-from ._compat import string_types
-from ._compat import text_type
-from ._compat import url_quote
-
 _word_split_re = re.compile(r"(\s+)")
 _punctuation_re = re.compile(
     "^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$"
@@ -205,8 +202,8 @@ def urlize(text, trim_url_limit=None, rel=None, target=None):
         and (x[:limit] + (len(x) >= limit and "..." or ""))
         or x
     )
-    words = _word_split_re.split(text_type(escape(text)))
-    rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or ""
+    words = _word_split_re.split(str(escape(text)))
+    rel_attr = rel and ' rel="%s"' % str(escape(rel)) or ""
     target_attr = target and ' target="%s"' % escape(target) or ""
 
     for i, word in enumerate(words):
@@ -299,7 +296,7 @@ def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
     return Markup(u"\n".join(u"<p>%s</p>" % escape(x) for x in result))
 
 
-def unicode_urlencode(obj, charset="utf-8", for_qs=False):
+def url_quote(obj, charset="utf-8", for_qs=False):
     """Quote a string for use in a URL using the given charset.
 
     This function is misnamed, it is a wrapper around
@@ -310,17 +307,14 @@ def unicode_urlencode(obj, charset="utf-8", for_qs=False):
     :param charset: Encode text to bytes using this charset.
     :param for_qs: Quote "/" and use "+" for spaces.
     """
-    if not isinstance(obj, string_types):
-        obj = text_type(obj)
+    if not isinstance(obj, bytes):
+        if not isinstance(obj, str):
+            obj = str(obj)
 
-    if isinstance(obj, text_type):
         obj = obj.encode(charset)
 
     safe = b"" if for_qs else b"/"
-    rv = url_quote(obj, safe)
-
-    if not isinstance(rv, text_type):
-        rv = rv.decode("utf-8")
+    rv = quote_from_bytes(obj, safe)
 
     if for_qs:
         rv = rv.replace("%20", "+")
@@ -328,6 +322,19 @@ def unicode_urlencode(obj, charset="utf-8", for_qs=False):
     return rv
 
 
+def unicode_urlencode(obj, charset="utf-8", for_qs=False):
+    import warnings
+
+    warnings.warn(
+        "'unicode_urlencode' has been renamed to 'url_quote'. The old"
+        " name will be removed in version 3.1.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    return url_quote(obj, charset=charset, for_qs=for_qs)
+
+
+@abc.MutableMapping.register
 class LRUCache(object):
     """A simple LRU Cache implementation."""
 
@@ -484,9 +491,6 @@ class LRUCache(object):
     __copy__ = copy
 
 
-abc.MutableMapping.register(LRUCache)
-
-
 def select_autoescape(
     enabled_extensions=("html", "htm", "xml"),
     disabled_extensions=(),
index 67d2cdb5259e01f15540bf99e89dd3e87e216ef6..b083b758a936728ae66942c859bbcdc6eb935d7c 100644 (file)
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 import re
+from io import BytesIO
 
 import pytest
 
@@ -7,9 +8,6 @@ from jinja2 import contextfunction
 from jinja2 import DictLoader
 from jinja2 import Environment
 from jinja2 import nodes
-from jinja2._compat import BytesIO
-from jinja2._compat import itervalues
-from jinja2._compat import text_type
 from jinja2.exceptions import TemplateAssertionError
 from jinja2.ext import Extension
 from jinja2.lexer import count_newlines
@@ -231,7 +229,7 @@ class TestExtensions(object):
         original = Environment(extensions=[ExampleExtension])
         overlay = original.overlay()
         for env in original, overlay:
-            for ext in itervalues(env.extensions):
+            for ext in env.extensions.values():
                 assert ext.environment is env
 
     def test_preprocessor_extension(self):
@@ -619,7 +617,7 @@ class TestAutoEscape(object):
         """
         tmpl = env.from_string(tmplsource)
         assert tmpl.render(val=True).split()[0] == "Markup"
-        assert tmpl.render(val=False).split()[0] == text_type.__name__
+        assert tmpl.render(val=False).split()[0] == "str"
 
         # looking at the source we should see <testing> there in raw
         # (and then escaped as well)
index 34b6f200deab22533911fb1ec800ff8770254792..c7c43e8b8438327c6b204a06df8bc1b361eddd4d 100644 (file)
@@ -1,14 +1,8 @@
-import sys
-
 import pytest
 
-from jinja2 import contextfilter
-from jinja2 import Environment
 from jinja2 import Template
-from jinja2._compat import text_type
 
 
-@pytest.mark.skipif(sys.version_info < (3, 5), reason="Requires 3.5 or later")
 def test_generator_stop():
     class X(object):
         def __getattr__(self, name):
@@ -17,26 +11,3 @@ def test_generator_stop():
     t = Template("a{{ bad.bar() }}b")
     with pytest.raises(RuntimeError):
         t.render(bad=X())
-
-
-@pytest.mark.skipif(sys.version_info[0] > 2, reason="Feature only supported on 2.x")
-def test_ascii_str():
-    @contextfilter
-    def assert_func(context, value):
-        assert type(value) is context["expected_type"]
-
-    env = Environment()
-    env.filters["assert"] = assert_func
-
-    env.policies["compiler.ascii_str"] = False
-    t = env.from_string('{{ "foo"|assert }}')
-    t.render(expected_type=text_type)
-
-    env.policies["compiler.ascii_str"] = True
-    t = env.from_string('{{ "foo"|assert }}')
-    t.render(expected_type=str)
-
-    for val in True, False:
-        env.policies["compiler.ascii_str"] = val
-        t = env.from_string(u'{{ "\N{SNOWMAN}"|assert }}')
-        t.render(expected_type=text_type)
index 109f4441f8e05b3bc6ddd32873b81abea868a460..03db0392893b3a04a14ccf85464ec73610a06168 100644 (file)
@@ -6,27 +6,23 @@ import pytest
 
 from jinja2 import Environment
 from jinja2 import Markup
-from jinja2._compat import implements_to_string
-from jinja2._compat import text_type
 
 
-@implements_to_string
 class Magic(object):
     def __init__(self, value):
         self.value = value
 
     def __str__(self):
-        return text_type(self.value)
+        return str(self.value)
 
 
-@implements_to_string
 class Magic2(object):
     def __init__(self, value1, value2):
         self.value1 = value1
         self.value2 = value2
 
     def __str__(self):
-        return u"(%s,%s)" % (text_type(self.value1), text_type(self.value2))
+        return u"(%s,%s)" % (str(self.value1), str(self.value2))
 
 
 @pytest.mark.filter
@@ -281,7 +277,7 @@ class TestFilter(object):
     def test_string(self, env):
         x = [1, 2, 3, 4, 5]
         tmpl = env.from_string("""{{ obj|string }}""")
-        assert tmpl.render(obj=x) == text_type(x)
+        assert tmpl.render(obj=x) == str(x)
 
     def test_title(self, env):
         tmpl = env.from_string("""{{ "foo bar"|title }}""")
index 9da9380581112b0a14c558bf3b15a36fb657a7c9..75dd7552a247582d90d6edc04d398dc3a0f5419d 100644 (file)
@@ -6,9 +6,6 @@ from jinja2 import nodes
 from jinja2 import Template
 from jinja2 import TemplateSyntaxError
 from jinja2 import UndefinedError
-from jinja2._compat import iteritems
-from jinja2._compat import PY2
-from jinja2._compat import text_type
 from jinja2.lexer import Token
 from jinja2.lexer import TOKEN_BLOCK_BEGIN
 from jinja2.lexer import TOKEN_BLOCK_END
@@ -16,17 +13,6 @@ from jinja2.lexer import TOKEN_EOF
 from jinja2.lexer import TokenStream
 
 
-# how does a string look like in jinja syntax?
-if PY2:
-
-    def jinja_string_repr(string):
-        return repr(string)[1:]
-
-
-else:
-    jinja_string_repr = repr
-
-
 @pytest.mark.lexnparse
 @pytest.mark.tokenstream
 class TestTokenStream(object):
@@ -111,7 +97,7 @@ class TestLexer(object):
 
     def test_string_escapes(self, env):
         for char in u"\0", u"\u2668", u"\xe4", u"\t", u"\r", u"\n":
-            tmpl = env.from_string("{{ %s }}" % jinja_string_repr(char))
+            tmpl = env.from_string("{{ %s }}" % repr(char))
             assert tmpl.render() == char
         assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u"\u2668"
 
@@ -124,7 +110,7 @@ class TestLexer(object):
     def test_operators(self, env):
         from jinja2.lexer import operators
 
-        for test, expect in iteritems(operators):
+        for test, expect in operators.items():
             if test in "([{}])":
                 continue
             stream = env.lexer.tokenize("{{ %s }}" % test)
@@ -153,30 +139,30 @@ class TestLexer(object):
                 assert result == expect, (keep, template, result, expect)
 
     @pytest.mark.parametrize(
-        "name,valid2,valid3",
-        (
-            (u"foo", True, True),
-            (u"fรถรถ", False, True),
-            (u"ใ", False, True),
-            (u"_", True, True),
-            (u"1a", False, False),  # invalid ascii start
-            (u"a-", False, False),  # invalid ascii continue
-            (u"๐Ÿ", False, False),  # invalid unicode start
-            (u"a๐Ÿ", False, False),  # invalid unicode continue
+        ("name", "valid"),
+        [
+            (u"foo", True),
+            (u"fรถรถ", True),
+            (u"ใ", True),
+            (u"_", True),
+            (u"1a", False),  # invalid ascii start
+            (u"a-", False),  # invalid ascii continue
+            (u"๐Ÿ", False),  # invalid unicode start
+            (u"a๐Ÿ", False),  # invalid unicode continue
             # start characters not matched by \w
-            (u"\u1885", False, True),
-            (u"\u1886", False, True),
-            (u"\u2118", False, True),
-            (u"\u212e", False, True),
+            (u"\u1885", True),
+            (u"\u1886", True),
+            (u"\u2118", True),
+            (u"\u212e", True),
             # continue character not matched by \w
-            (u"\xb7", False, False),
-            (u"a\xb7", False, True),
-        ),
+            (u"\xb7", False),
+            (u"a\xb7", True),
+        ],
     )
-    def test_name(self, env, name, valid2, valid3):
+    def test_name(self, env, name, valid):
         t = u"{{ " + name + u" }}"
 
-        if (valid2 and PY2) or (valid3 and not PY2):
+        if valid:
             # valid for version being tested, shouldn't raise
             env.from_string(t)
         else:
@@ -528,7 +514,7 @@ class TestSyntax(object):
 
     def test_operator_precedence(self, env):
         tmpl = env.from_string("""{{ 2 * 3 + 4 % 2 + 1 - 2 }}""")
-        assert tmpl.render() == text_type(2 * 3 + 4 % 2 + 1 - 2)
+        assert tmpl.render() == "5"
 
     def test_implicit_subscribed_tuple(self, env):
         class Foo(object):
index ef51bb568d6be662cfa1576311e1f0d577ec8953..84bd175d21957a7db47ce9f0954fa3b12b1788a2 100644 (file)
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 import os
+import platform
 import shutil
 import sys
 import tempfile
@@ -11,8 +12,6 @@ import pytest
 from jinja2 import Environment
 from jinja2 import loaders
 from jinja2 import PackageLoader
-from jinja2._compat import PY2
-from jinja2._compat import PYPY
 from jinja2.exceptions import TemplateNotFound
 from jinja2.loaders import split_template_path
 
@@ -138,25 +137,19 @@ class TestFileSystemLoader(object):
         env = Environment(loader=filesystem_loader)
         self._test_common(env)
 
-    @pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
     def test_searchpath_as_pathlib(self):
         import pathlib
 
         searchpath = pathlib.Path(self.searchpath)
-
         filesystem_loader = loaders.FileSystemLoader(searchpath)
-
         env = Environment(loader=filesystem_loader)
         self._test_common(env)
 
-    @pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
     def test_searchpath_as_list_including_pathlib(self):
         import pathlib
 
         searchpath = pathlib.Path(self.searchpath)
-
         filesystem_loader = loaders.FileSystemLoader(["/tmp/templates", searchpath])
-
         env = Environment(loader=filesystem_loader)
         self._test_common(env)
 
@@ -265,17 +258,6 @@ class TestModuleLoader(object):
 
         assert name not in sys.modules
 
-    # This test only makes sense on non-pypy python 2
-    @pytest.mark.skipif(
-        not (PY2 and not PYPY), reason="This test only makes sense on non-pypy python 2"
-    )
-    def test_byte_compilation(self, prefix_loader):
-        log = self.compile_down(prefix_loader, py_compile=True)
-        assert 'Byte-compiled "a/test.html"' in log
-        self.mod_env.get_template("a/test.html")
-        mod = self.mod_env.loader.module.tmpl_3c4ddf650c1a73df961a6d3d2ce2752f1b8fd490
-        assert mod.__file__.endswith(".pyc")
-
     def test_choice_loader(self, prefix_loader):
         self.compile_down(prefix_loader)
         self.mod_env.loader = loaders.ChoiceLoader(
@@ -299,7 +281,6 @@ class TestModuleLoader(object):
         tmpl2 = self.mod_env.get_template("DICT/test.html")
         assert tmpl2.render() == "DICT_TEMPLATE"
 
-    @pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
     def test_path_as_pathlib(self, prefix_loader):
         self.compile_down(prefix_loader)
 
@@ -312,7 +293,6 @@ class TestModuleLoader(object):
 
         self._test_common()
 
-    @pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
     def test_supports_pathlib_in_list_of_paths(self, prefix_loader):
         self.compile_down(prefix_loader)
 
@@ -367,7 +347,7 @@ def test_package_zip_source(package_zip_loader, template, expect):
 
 
 @pytest.mark.xfail(
-    PYPY,
+    platform.python_implementation() == "PyPy",
     reason="PyPy's zipimporter doesn't have a _files attribute.",
     raises=TypeError,
 )
index 77d378d27d44ef95f1705d233a9ae1739d1b8c5e..947168ce521a213ead72a3258e778c7a8c764525 100644 (file)
@@ -1,6 +1,5 @@
 import pytest
 
-from jinja2._compat import text_type
 from jinja2.exceptions import UndefinedError
 from jinja2.nativetypes import NativeEnvironment
 from jinja2.nativetypes import NativeTemplate
@@ -53,7 +52,7 @@ def test_multi_expression_add(env):
 def test_loops(env):
     t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
     result = t.render(value=["a", "b", "c", "d"])
-    assert isinstance(result, text_type)
+    assert isinstance(result, str)
     assert result == "abcd"
 
 
@@ -111,7 +110,7 @@ def test_constant_dunder_to_string(env):
 def test_string_literal_var(env):
     t = env.from_string("[{{ 'all' }}]")
     result = t.render()
-    assert isinstance(result, text_type)
+    assert isinstance(result, str)
     assert result == "[all]"
 
 
index accc1f62a48cb2ccac7416216ba636583a4c61d6..993a922b7fdf989624a7081b90e926c82a8dd0d7 100644 (file)
@@ -1,6 +1,4 @@
 # -*- coding: utf-8 -*-
-import sys
-
 import pytest
 
 from jinja2 import DictLoader
@@ -10,7 +8,6 @@ from jinja2 import Template
 from jinja2 import TemplateAssertionError
 from jinja2 import TemplateNotFound
 from jinja2 import TemplateSyntaxError
-from jinja2._compat import text_type
 
 
 @pytest.mark.regression
@@ -134,7 +131,7 @@ class TestBug(object):
         """
         )
 
-        assert tmpl.render().split() == [text_type(x) for x in range(1, 11)] * 5
+        assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5
 
     def test_weird_inline_comment(self, env):
         env = Environment(line_statement_prefix="%")
@@ -308,13 +305,6 @@ class TestBug(object):
 
         assert output == expected
 
-    @pytest.mark.skipif(sys.version_info[0] > 2, reason="This only works on 2.x")
-    def test_old_style_attribute(self, env):
-        class Foo:
-            x = 42
-
-        assert env.getitem(Foo(), "x") == 42
-
     def test_block_set_with_extends(self):
         env = Environment(
             loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"})
index f092c96d95284775a58fab7d43913d0ca3369ae4..7c049fc177d9f0670b16acbb3967269a2252c376 100644 (file)
@@ -3,8 +3,6 @@ import pytest
 
 from jinja2 import Environment
 from jinja2 import escape
-from jinja2 import Markup
-from jinja2._compat import text_type
 from jinja2.exceptions import SecurityError
 from jinja2.exceptions import TemplateRuntimeError
 from jinja2.exceptions import TemplateSyntaxError
@@ -77,44 +75,6 @@ class TestSandbox(object):
             "{% for foo, bar.baz in seq %}...{% endfor %}",
         )
 
-    def test_markup_operations(self, env):
-        # adding two strings should escape the unsafe one
-        unsafe = '<script type="application/x-some-script">alert("foo");</script>'
-        safe = Markup("<em>username</em>")
-        assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe)
-
-        # string interpolations are safe to use too
-        assert Markup("<em>%s</em>") % "<bad user>" == "<em>&lt;bad user&gt;</em>"
-        assert (
-            Markup("<em>%(username)s</em>") % {"username": "<bad user>"}
-            == "<em>&lt;bad user&gt;</em>"
-        )
-
-        # an escaped object is markup too
-        assert type(Markup("foo") + "bar") is Markup
-
-        # and it implements __html__ by returning itself
-        x = Markup("foo")
-        assert x.__html__() is x
-
-        # it also knows how to treat __html__ objects
-        class Foo(object):
-            def __html__(self):
-                return "<em>awesome</em>"
-
-            def __unicode__(self):
-                return "awesome"
-
-        assert Markup(Foo()) == "<em>awesome</em>"
-        assert (
-            Markup("<strong>%s</strong>") % Foo() == "<strong><em>awesome</em></strong>"
-        )
-
-        # escaping and unescaping
-        assert escape("\"<>&'") == "&#34;&lt;&gt;&amp;&#39;"
-        assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
-        assert Markup("&lt;test&gt;").unescape() == "<test>"
-
     def test_template_data(self, env):
         env = Environment(autoescape=True)
         t = env.from_string(
@@ -124,7 +84,7 @@ class TestSandbox(object):
         )
         escaped_out = "<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>"
         assert t.render() == escaped_out
-        assert text_type(t.module) == escaped_out
+        assert str(t.module) == escaped_out
         assert escape(t.module) == escaped_out
         assert t.module.say_hello("<blink>foo</blink>") == escaped_out
         assert (
index 6ddd1a412cdd5d343230ef279b1872427941ec1d..9e75c6dcb3192eee8f5db4644df845922b037d40 100644 (file)
@@ -7,8 +7,6 @@ from copy import copy as shallow_copy
 import pytest
 from markupsafe import Markup
 
-from jinja2._compat import range_type
-from jinja2._compat import string_types
 from jinja2.utils import consume
 from jinja2.utils import generate_lorem_ipsum
 from jinja2.utils import LRUCache
@@ -165,26 +163,26 @@ class TestLoremIpsum(object):
 
     def test_lorem_ipsum_html(self):
         """Test that output of lorem_ipsum is a string_type when not html."""
-        assert isinstance(generate_lorem_ipsum(html=False), string_types)
+        assert isinstance(generate_lorem_ipsum(html=False), str)
 
     def test_lorem_ipsum_n(self):
         """Test that the n (number of lines) works as expected."""
         assert generate_lorem_ipsum(n=0, html=False) == u""
-        for n in range_type(1, 50):
+        for n in range(1, 50):
             assert generate_lorem_ipsum(n=n, html=False).count("\n") == (n - 1) * 2
 
     def test_lorem_ipsum_min(self):
         """Test that at least min words are in the output of each line"""
-        for _ in range_type(5):
+        for _ in range(5):
             m = random.randrange(20, 99)
-            for _ in range_type(10):
+            for _ in range(10):
                 assert generate_lorem_ipsum(n=1, min=m, html=False).count(" ") >= m - 1
 
     def test_lorem_ipsum_max(self):
         """Test that at least max words are in the output of each line"""
-        for _ in range_type(5):
+        for _ in range(5):
             m = random.randrange(21, 100)
-            for _ in range_type(10):
+            for _ in range(10):
                 assert generate_lorem_ipsum(n=1, max=m, html=False).count(" ") < m - 1