aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2020-01-27 22:05:00 -0800
committerDavid Lord <davidism@gmail.com>2020-02-05 08:37:40 -0800
commit148a19138c197233cbd3a2cb1f543d468479f1bc (patch)
tree6b71f2f40a0931fe09f80efd73b7e5a18a00a123
parentb0015c72d5acbf93b9d99a1ce6167889338db85b (diff)
downloadjinja-148a19138c197233cbd3a2cb1f543d468479f1bc.tar.gz
remove _compat module
-rw-r--r--src/jinja2/_compat.py132
-rw-r--r--src/jinja2/bccache.py24
-rw-r--r--src/jinja2/compiler.py110
-rw-r--r--src/jinja2/debug.py12
-rw-r--r--src/jinja2/defaults.py3
-rw-r--r--src/jinja2/environment.py37
-rw-r--r--src/jinja2/exceptions.py44
-rw-r--r--src/jinja2/ext.py11
-rw-r--r--src/jinja2/filters.py74
-rw-r--r--src/jinja2/idtracking.py5
-rw-r--r--src/jinja2/lexer.py16
-rw-r--r--src/jinja2/loaders.py35
-rw-r--r--src/jinja2/meta.py12
-rw-r--r--src/jinja2/nativetypes.py5
-rw-r--r--src/jinja2/nodes.py27
-rw-r--r--src/jinja2/parser.py3
-rw-r--r--src/jinja2/runtime.py162
-rw-r--r--src/jinja2/sandbox.py63
-rw-r--r--src/jinja2/tests.py17
-rw-r--r--src/jinja2/utils.py40
-rw-r--r--tests/test_ext.py8
-rw-r--r--tests/test_features.py29
-rw-r--r--tests/test_filters.py10
-rw-r--r--tests/test_lexnparse.py58
-rw-r--r--tests/test_loader.py24
-rw-r--r--tests/test_nativetypes.py5
-rw-r--r--tests/test_regression.py12
-rw-r--r--tests/test_security.py42
-rw-r--r--tests/test_utils.py14
29 files changed, 266 insertions, 768 deletions
diff --git a/src/jinja2/_compat.py b/src/jinja2/_compat.py
deleted file mode 100644
index 1f044954..00000000
--- a/src/jinja2/_compat.py
+++ /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
diff --git a/src/jinja2/bccache.py b/src/jinja2/bccache.py
index ff4d606f..b328b3b0 100644
--- a/src/jinja2/bccache.py
+++ b/src/jinja2/bccache.py
@@ -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
diff --git a/src/jinja2/compiler.py b/src/jinja2/compiler.py
index 63297b42..8965b320 100644
--- a/src/jinja2/compiler.py
+++ b/src/jinja2/compiler.py
@@ -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)
diff --git a/src/jinja2/debug.py b/src/jinja2/debug.py
index d2c5a06b..2854a9f7 100644
--- a/src/jinja2/debug.py
+++ b/src/jinja2/debug.py
@@ -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
diff --git a/src/jinja2/defaults.py b/src/jinja2/defaults.py
index 8e0e7d77..72a93578 100644
--- a/src/jinja2/defaults.py
+++ b/src/jinja2/defaults.py
@@ -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,
diff --git a/src/jinja2/environment.py b/src/jinja2/environment.py
index 63ac0c8b..da7a09f3 100644
--- a/src/jinja2/environment.py
+++ b/src/jinja2/environment.py
@@ -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")
diff --git a/src/jinja2/exceptions.py b/src/jinja2/exceptions.py
index ac62c3b4..8d5e89d7 100644
--- a/src/jinja2/exceptions.py
+++ b/src/jinja2/exceptions.py
@@ -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."""
diff --git a/src/jinja2/ext.py b/src/jinja2/ext.py
index 99ecb34f..fd36e2d4 100644
--- a/src/jinja2/ext.py
+++ b/src/jinja2/ext.py
@@ -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)
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index 963c03ab..9000083d 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -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,
diff --git a/src/jinja2/idtracking.py b/src/jinja2/idtracking.py
index 9a0d8380..7889a2b5 100644
--- a/src/jinja2/idtracking.py
+++ b/src/jinja2/idtracking.py
@@ -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
diff --git a/src/jinja2/lexer.py b/src/jinja2/lexer.py
index 8e73be82..0f32335c 100644
--- a/src/jinja2/lexer.py
+++ b/src/jinja2/lexer.py
@@ -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
diff --git a/src/jinja2/loaders.py b/src/jinja2/loaders.py
index 0a5538ad..e48155dc 100644
--- a/src/jinja2/loaders.py
+++ b/src/jinja2/loaders.py
@@ -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)
diff --git a/src/jinja2/meta.py b/src/jinja2/meta.py
index 3795aace..d112cbed 100644
--- a/src/jinja2/meta.py
+++ b/src/jinja2/meta.py
@@ -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:
diff --git a/src/jinja2/nativetypes.py b/src/jinja2/nativetypes.py
index 9866c962..4f8106bc 100644
--- a/src/jinja2/nativetypes.py
+++ b/src/jinja2/nativetypes.py
@@ -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.
"""
diff --git a/src/jinja2/nodes.py b/src/jinja2/nodes.py
index c0b6d77e..924b0aa0 100644
--- a/src/jinja2/nodes.py
+++ b/src/jinja2/nodes.py
@@ -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):
diff --git a/src/jinja2/parser.py b/src/jinja2/parser.py
index d5881066..ec0778a7 100644
--- a/src/jinja2/parser.py
+++ b/src/jinja2/parser.py
@@ -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]
diff --git a/src/jinja2/runtime.py b/src/jinja2/runtime.py
index 8df42c59..462238b5 100644
--- a/src/jinja2/runtime.py
+++ b/src/jinja2/runtime.py
@@ -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__,
diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py
index 4629143b..639fcdb5 100644
--- a/src/jinja2/sandbox.py
+++ b/src/jinja2/sandbox.py
@@ -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:
diff --git a/src/jinja2/tests.py b/src/jinja2/tests.py
index fabd4ce5..6a24d9c0 100644
--- a/src/jinja2/tests.py
+++ b/src/jinja2/tests.py
@@ -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):
diff --git a/src/jinja2/utils.py b/src/jinja2/utils.py
index 7e1ad911..e80f7ebb 100644
--- a/src/jinja2/utils.py
+++ b/src/jinja2/utils.py
@@ -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=(),
diff --git a/tests/test_ext.py b/tests/test_ext.py
index 67d2cdb5..b083b758 100644
--- a/tests/test_ext.py
+++ b/tests/test_ext.py
@@ -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)
diff --git a/tests/test_features.py b/tests/test_features.py
index 34b6f200..c7c43e8b 100644
--- a/tests/test_features.py
+++ b/tests/test_features.py
@@ -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)
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 109f4441..03db0392 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -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 }}""")
diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py
index 9da93805..75dd7552 100644
--- a/tests/test_lexnparse.py
+++ b/tests/test_lexnparse.py
@@ -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):
diff --git a/tests/test_loader.py b/tests/test_loader.py
index ef51bb56..84bd175d 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -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,
)
diff --git a/tests/test_nativetypes.py b/tests/test_nativetypes.py
index 77d378d2..947168ce 100644
--- a/tests/test_nativetypes.py
+++ b/tests/test_nativetypes.py
@@ -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]"
diff --git a/tests/test_regression.py b/tests/test_regression.py
index accc1f62..993a922b 100644
--- a/tests/test_regression.py
+++ b/tests/test_regression.py
@@ -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 %}"})
diff --git a/tests/test_security.py b/tests/test_security.py
index f092c96d..7c049fc1 100644
--- a/tests/test_security.py
+++ b/tests/test_security.py
@@ -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 (
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 6ddd1a41..9e75c6dc 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -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