diff options
author | David Lord <davidism@gmail.com> | 2020-01-27 22:05:00 -0800 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2020-02-05 08:37:40 -0800 |
commit | 148a19138c197233cbd3a2cb1f543d468479f1bc (patch) | |
tree | 6b71f2f40a0931fe09f80efd73b7e5a18a00a123 | |
parent | b0015c72d5acbf93b9d99a1ce6167889338db85b (diff) | |
download | jinja-148a19138c197233cbd3a2cb1f543d468479f1bc.tar.gz |
remove _compat module
-rw-r--r-- | src/jinja2/_compat.py | 132 | ||||
-rw-r--r-- | src/jinja2/bccache.py | 24 | ||||
-rw-r--r-- | src/jinja2/compiler.py | 110 | ||||
-rw-r--r-- | src/jinja2/debug.py | 12 | ||||
-rw-r--r-- | src/jinja2/defaults.py | 3 | ||||
-rw-r--r-- | src/jinja2/environment.py | 37 | ||||
-rw-r--r-- | src/jinja2/exceptions.py | 44 | ||||
-rw-r--r-- | src/jinja2/ext.py | 11 | ||||
-rw-r--r-- | src/jinja2/filters.py | 74 | ||||
-rw-r--r-- | src/jinja2/idtracking.py | 5 | ||||
-rw-r--r-- | src/jinja2/lexer.py | 16 | ||||
-rw-r--r-- | src/jinja2/loaders.py | 35 | ||||
-rw-r--r-- | src/jinja2/meta.py | 12 | ||||
-rw-r--r-- | src/jinja2/nativetypes.py | 5 | ||||
-rw-r--r-- | src/jinja2/nodes.py | 27 | ||||
-rw-r--r-- | src/jinja2/parser.py | 3 | ||||
-rw-r--r-- | src/jinja2/runtime.py | 162 | ||||
-rw-r--r-- | src/jinja2/sandbox.py | 63 | ||||
-rw-r--r-- | src/jinja2/tests.py | 17 | ||||
-rw-r--r-- | src/jinja2/utils.py | 40 | ||||
-rw-r--r-- | tests/test_ext.py | 8 | ||||
-rw-r--r-- | tests/test_features.py | 29 | ||||
-rw-r--r-- | tests/test_filters.py | 10 | ||||
-rw-r--r-- | tests/test_lexnparse.py | 58 | ||||
-rw-r--r-- | tests/test_loader.py | 24 | ||||
-rw-r--r-- | tests/test_nativetypes.py | 5 | ||||
-rw-r--r-- | tests/test_regression.py | 12 | ||||
-rw-r--r-- | tests/test_security.py | 42 | ||||
-rw-r--r-- | tests/test_utils.py | 14 |
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><bad user></em>" - assert ( - Markup("<em>%(username)s</em>") % {"username": "<bad user>"} - == "<em><bad user></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("\"<>&'") == ""<>&'" - assert Markup("<em>Foo & Bar</em>").striptags() == "Foo & Bar" - assert Markup("<test>").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 <blink>foo</blink>!</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 |