summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2020-01-28 20:16:59 -0800
committerDavid Lord <davidism@gmail.com>2020-01-28 23:09:56 -0800
commit35a97b228e28a51602ebf1515afb32eb7da40982 (patch)
treeabde9b1d55c0ef3b122e9748da477f2c6586ba6b
parentd6dba5033d8a0ae746e7802bc691dbc0bb53813e (diff)
downloadjinja2-35a97b228e28a51602ebf1515afb32eb7da40982.tar.gz
apply pyupgrade and f-strings
-rw-r--r--docs/api.rst8
-rw-r--r--docs/conf.py6
-rw-r--r--docs/examples/cache_extension.py2
-rw-r--r--docs/examples/inline_gettext_extension.py3
-rw-r--r--examples/basic/cycle.py2
-rw-r--r--examples/basic/debugger.py2
-rw-r--r--examples/basic/inheritance.py2
-rw-r--r--examples/basic/test.py8
-rw-r--r--examples/basic/test_filter_and_linestatements.py2
-rw-r--r--examples/basic/test_loop_filter.py2
-rw-r--r--examples/basic/translate.py4
-rwxr-xr-xscripts/generate_identifier_pattern.py8
-rw-r--r--src/jinja2/asyncfilters.py2
-rw-r--r--src/jinja2/asyncsupport.py1
-rw-r--r--src/jinja2/bccache.py11
-rw-r--r--src/jinja2/compiler.py336
-rw-r--r--src/jinja2/constants.py3
-rw-r--r--src/jinja2/debug.py2
-rw-r--r--src/jinja2/defaults.py1
-rw-r--r--src/jinja2/environment.py47
-rw-r--r--src/jinja2/exceptions.py10
-rw-r--r--src/jinja2/ext.py17
-rw-r--r--src/jinja2/filters.py51
-rw-r--r--src/jinja2/idtracking.py10
-rw-r--r--src/jinja2/lexer.py120
-rw-r--r--src/jinja2/loaders.py13
-rw-r--r--src/jinja2/meta.py1
-rw-r--r--src/jinja2/nativetypes.py5
-rw-r--r--src/jinja2/nodes.py46
-rw-r--r--src/jinja2/optimizer.py3
-rw-r--r--src/jinja2/parser.py34
-rw-r--r--src/jinja2/runtime.py115
-rw-r--r--src/jinja2/sandbox.py17
-rw-r--r--src/jinja2/tests.py1
-rw-r--r--src/jinja2/utils.py84
-rw-r--r--src/jinja2/visitor.py6
-rw-r--r--tests/conftest.py1
-rw-r--r--tests/test_api.py55
-rw-r--r--tests/test_async.py9
-rw-r--r--tests/test_bytecode_cache.py7
-rw-r--r--tests/test_core_tags.py29
-rw-r--r--tests/test_debug.py10
-rw-r--r--tests/test_ext.py91
-rw-r--r--tests/test_features.py3
-rw-r--r--tests/test_filters.py42
-rw-r--r--tests/test_idtracking.py38
-rw-r--r--tests/test_imports.py16
-rw-r--r--tests/test_inheritance.py9
-rw-r--r--tests/test_lexnparse.py95
-rw-r--r--tests/test_loader.py15
-rw-r--r--tests/test_regression.py25
-rw-r--r--tests/test_security.py19
-rw-r--r--tests/test_tests.py7
-rw-r--r--tests/test_utils.py17
54 files changed, 641 insertions, 832 deletions
diff --git a/docs/api.rst b/docs/api.rst
index 501a2c6..075bcc6 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -675,12 +675,14 @@ enabled::
import re
from jinja2 import evalcontextfilter, Markup, escape
- _paragraph_re = re.compile(r'(?:\r\n|\r(?!\n)|\n){2,}')
+ _paragraph_re = re.compile(r"(?:\r\n|\r(?!\n)|\n){2,}")
@evalcontextfilter
def nl2br(eval_ctx, value):
- result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', Markup('<br>\n'))
- for p in _paragraph_re.split(escape(value)))
+ result = "\n\n".join(
+ f"<p>{p.replace('\n', Markup('<br>\n'))}</p>"
+ for p in _paragraph_re.split(escape(value))
+ )
if eval_ctx.autoescape:
result = Markup(result)
return result
diff --git a/docs/conf.py b/docs/conf.py
index 01e530d..783bae2 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -42,11 +42,9 @@ singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
html_static_path = ["_static"]
html_favicon = "_static/jinja-logo-sidebar.png"
html_logo = "_static/jinja-logo-sidebar.png"
-html_title = "Jinja Documentation ({})".format(version)
+html_title = f"Jinja Documentation ({version})"
html_show_sourcelink = False
# LaTeX ----------------------------------------------------------------
-latex_documents = [
- (master_doc, "Jinja-{}.tex".format(version), html_title, author, "manual")
-]
+latex_documents = [(master_doc, f"Jinja-{version}.tex", html_title, author, "manual")]
diff --git a/docs/examples/cache_extension.py b/docs/examples/cache_extension.py
index 387cd46..46af67c 100644
--- a/docs/examples/cache_extension.py
+++ b/docs/examples/cache_extension.py
@@ -7,7 +7,7 @@ class FragmentCacheExtension(Extension):
tags = {"cache"}
def __init__(self, environment):
- super(FragmentCacheExtension, self).__init__(environment)
+ super().__init__(environment)
# add the defaults to the environment
environment.extend(fragment_cache_prefix="", fragment_cache=None)
diff --git a/docs/examples/inline_gettext_extension.py b/docs/examples/inline_gettext_extension.py
index 47bc9cc..d75119c 100644
--- a/docs/examples/inline_gettext_extension.py
+++ b/docs/examples/inline_gettext_extension.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import re
from jinja2.exceptions import TemplateSyntaxError
@@ -54,7 +53,7 @@ class InlineGettext(Extension):
else:
if gtok == "(" or paren_stack > 1:
yield Token(lineno, "data", gtok)
- paren_stack += gtok == ")" and -1 or 1
+ paren_stack += -1 if gtok == ")" else 1
if not paren_stack:
yield Token(lineno, "block_begin", None)
yield Token(lineno, "name", "endtrans")
diff --git a/examples/basic/cycle.py b/examples/basic/cycle.py
index 25dcb0b..1f97e37 100644
--- a/examples/basic/cycle.py
+++ b/examples/basic/cycle.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
from jinja2 import Environment
env = Environment(
diff --git a/examples/basic/debugger.py b/examples/basic/debugger.py
index d3c1a60..f6a9627 100644
--- a/examples/basic/debugger.py
+++ b/examples/basic/debugger.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
from jinja2 import Environment
from jinja2.loaders import FileSystemLoader
diff --git a/examples/basic/inheritance.py b/examples/basic/inheritance.py
index 4a881bf..6d928df 100644
--- a/examples/basic/inheritance.py
+++ b/examples/basic/inheritance.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
from jinja2 import Environment
from jinja2.loaders import DictLoader
diff --git a/examples/basic/test.py b/examples/basic/test.py
index 80b9d1f..d34b0ff 100644
--- a/examples/basic/test.py
+++ b/examples/basic/test.py
@@ -1,12 +1,10 @@
-from __future__ import print_function
-
from jinja2 import Environment
from jinja2.loaders import DictLoader
env = Environment(
loader=DictLoader(
{
- "child.html": u"""\
+ "child.html": """\
{% extends master_layout or 'master.html' %}
{% include helpers = 'helpers.html' %}
{% macro get_the_answer() %}42{% endmacro %}
@@ -16,12 +14,12 @@ env = Environment(
{{ helpers.conspirate() }}
{% endblock %}
""",
- "master.html": u"""\
+ "master.html": """\
<!doctype html>
<title>{{ title }}</title>
{% block body %}{% endblock %}
""",
- "helpers.html": u"""\
+ "helpers.html": """\
{% macro conspirate() %}23{% endmacro %}
""",
}
diff --git a/examples/basic/test_filter_and_linestatements.py b/examples/basic/test_filter_and_linestatements.py
index 673b67e..9bbcbca 100644
--- a/examples/basic/test_filter_and_linestatements.py
+++ b/examples/basic/test_filter_and_linestatements.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
from jinja2 import Environment
env = Environment(
diff --git a/examples/basic/test_loop_filter.py b/examples/basic/test_loop_filter.py
index 39be08d..6bd89fd 100644
--- a/examples/basic/test_loop_filter.py
+++ b/examples/basic/test_loop_filter.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
from jinja2 import Environment
tmpl = Environment().from_string(
diff --git a/examples/basic/translate.py b/examples/basic/translate.py
index 71547f4..e659681 100644
--- a/examples/basic/translate.py
+++ b/examples/basic/translate.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
from jinja2 import Environment
env = Environment(extensions=["jinja2.ext.i18n"])
@@ -7,7 +5,7 @@ env.globals["gettext"] = {"Hello %(user)s!": "Hallo %(user)s!"}.__getitem__
env.globals["ngettext"] = lambda s, p, n: {
"%(count)s user": "%(count)d Benutzer",
"%(count)s users": "%(count)d Benutzer",
-}[n == 1 and s or p]
+}[s if n == 1 else p]
print(
env.from_string(
"""\
diff --git a/scripts/generate_identifier_pattern.py b/scripts/generate_identifier_pattern.py
index 5813199..6b47953 100755
--- a/scripts/generate_identifier_pattern.py
+++ b/scripts/generate_identifier_pattern.py
@@ -1,12 +1,8 @@
-#!/usr/bin/env python3
import itertools
import os
import re
import sys
-if sys.version_info[0] < 3:
- raise RuntimeError("This needs to run on Python 3.")
-
def get_characters():
"""Find every Unicode character that is valid in a Python `identifier`_ but
@@ -52,7 +48,7 @@ def build_pattern(ranges):
out.append(a)
out.append(b)
else:
- out.append("{}-{}".format(a, b))
+ out.append(f"{a}-{b}")
return "".join(out)
@@ -70,7 +66,7 @@ def main():
f.write("import re\n\n")
f.write("# generated by scripts/generate_identifier_pattern.py\n")
f.write("pattern = re.compile(\n")
- f.write(' r"[\\w{}]+" # noqa: B950\n'.format(pattern))
+ f.write(f' r"[\\w{pattern}]+" # noqa: B950\n')
f.write(")\n")
diff --git a/src/jinja2/asyncfilters.py b/src/jinja2/asyncfilters.py
index a03e00b..18eecb7 100644
--- a/src/jinja2/asyncfilters.py
+++ b/src/jinja2/asyncfilters.py
@@ -85,7 +85,7 @@ async def do_groupby(environment, value, attribute):
@asyncfiltervariant(filters.do_join)
-async def do_join(eval_ctx, value, d=u"", attribute=None):
+async def do_join(eval_ctx, value, d="", attribute=None):
return filters.do_join(eval_ctx, await auto_to_seq(value), d, attribute)
diff --git a/src/jinja2/asyncsupport.py b/src/jinja2/asyncsupport.py
index d7d6259..3aef7ad 100644
--- a/src/jinja2/asyncsupport.py
+++ b/src/jinja2/asyncsupport.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""The code for async support. Importing this patches Jinja."""
import asyncio
import inspect
diff --git a/src/jinja2/bccache.py b/src/jinja2/bccache.py
index b328b3b..70b99c5 100644
--- a/src/jinja2/bccache.py
+++ b/src/jinja2/bccache.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""The optional bytecode cache system. This is useful if you have very
complex template situations and the compilation of all those templates
slows down your application too much.
@@ -30,7 +29,7 @@ bc_magic = (
)
-class Bucket(object):
+class Bucket:
"""Buckets are used to store the bytecode for one template. It's created
and initialized by the bytecode cache and passed to the loading functions.
@@ -87,7 +86,7 @@ class Bucket(object):
return out.getvalue()
-class BytecodeCache(object):
+class BytecodeCache:
"""To implement your own bytecode cache you have to subclass this class
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
these methods are passed a :class:`~jinja2.bccache.Bucket`.
@@ -205,7 +204,7 @@ class FileSystemBytecodeCache(BytecodeCache):
if not hasattr(os, "getuid"):
_unsafe_dir()
- dirname = "_jinja2-cache-%d" % os.getuid()
+ dirname = f"_jinja2-cache-{os.getuid()}"
actual_dir = os.path.join(tmpdir, dirname)
try:
@@ -237,7 +236,7 @@ class FileSystemBytecodeCache(BytecodeCache):
return actual_dir
def _get_cache_filename(self, bucket):
- return os.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")
@@ -260,7 +259,7 @@ class FileSystemBytecodeCache(BytecodeCache):
# normally.
from os import remove
- files = fnmatch.filter(os.listdir(self.directory), self.pattern % "*")
+ files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
for filename in files:
try:
remove(os.path.join(self.directory, filename))
diff --git a/src/jinja2/compiler.py b/src/jinja2/compiler.py
index 0838adc..a784879 100644
--- a/src/jinja2/compiler.py
+++ b/src/jinja2/compiler.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Compiles nodes from the parser into Python code."""
from collections import namedtuple
from functools import update_wrapper
@@ -89,7 +88,7 @@ def find_undeclared(nodes, names):
return visitor.undeclared
-class MacroRef(object):
+class MacroRef:
def __init__(self, node):
self.node = node
self.accesses_caller = False
@@ -97,12 +96,12 @@ class MacroRef(object):
self.accesses_varargs = False
-class Frame(object):
+class Frame:
"""Holds compile time information for us."""
def __init__(self, eval_ctx, parent=None, level=None):
self.eval_ctx = eval_ctx
- self.symbols = Symbols(parent and parent.symbols or None, level=level)
+ self.symbols = Symbols(parent.symbols if parent else None, level=level)
# a toplevel frame is the root + soft frames such as if conditions.
self.toplevel = False
@@ -123,7 +122,7 @@ class Frame(object):
self.buffer = None
# the name of the block we're in, otherwise None.
- self.block = parent and parent.block or None
+ self.block = parent.block if parent else None
# the parent of this frame
self.parent = parent
@@ -286,12 +285,12 @@ class CodeGenerator(NodeVisitor):
def temporary_identifier(self):
"""Get a new unique identifier."""
self._last_identifier += 1
- return "t_%d" % self._last_identifier
+ return f"t_{self._last_identifier}"
def buffer(self, frame):
"""Enable buffering for the frame from that point onwards."""
frame.buffer = self.temporary_identifier()
- self.writeline("%s = []" % frame.buffer)
+ self.writeline(f"{frame.buffer} = []")
def return_buffer_contents(self, frame, force_unescaped=False):
"""Return the buffer contents of the frame."""
@@ -299,17 +298,17 @@ class CodeGenerator(NodeVisitor):
if frame.eval_ctx.volatile:
self.writeline("if context.eval_ctx.autoescape:")
self.indent()
- self.writeline("return Markup(concat(%s))" % frame.buffer)
+ self.writeline(f"return Markup(concat({frame.buffer}))")
self.outdent()
self.writeline("else:")
self.indent()
- self.writeline("return concat(%s)" % frame.buffer)
+ self.writeline(f"return concat({frame.buffer})")
self.outdent()
return
elif frame.eval_ctx.autoescape:
- self.writeline("return Markup(concat(%s))" % frame.buffer)
+ self.writeline(f"return Markup(concat({frame.buffer}))")
return
- self.writeline("return concat(%s)" % frame.buffer)
+ self.writeline(f"return concat({frame.buffer})")
def indent(self):
"""Indent by one."""
@@ -324,7 +323,7 @@ class CodeGenerator(NodeVisitor):
if frame.buffer is None:
self.writeline("yield ", node)
else:
- self.writeline("%s.append(" % frame.buffer, node)
+ self.writeline(f"{frame.buffer}.append(", node)
def end_write(self, frame):
"""End the writing process started by `start_write`."""
@@ -399,7 +398,7 @@ class CodeGenerator(NodeVisitor):
self.visit(kwarg, frame)
if extra_kwargs is not None:
for key, value in extra_kwargs.items():
- self.write(", %s=%s" % (key, value))
+ self.write(f", {key}={value}")
if node.dyn_args:
self.write(", *")
self.visit(node.dyn_args, frame)
@@ -410,12 +409,12 @@ class CodeGenerator(NodeVisitor):
else:
self.write(", **{")
for kwarg in node.kwargs:
- self.write("%r: " % kwarg.key)
+ self.write(f"{kwarg.key!r}: ")
self.visit(kwarg.value, frame)
self.write(", ")
if extra_kwargs is not None:
for key, value in extra_kwargs.items():
- self.write("%r: %s, " % (key, value))
+ self.write(f"{key!r}: {value}, ")
if node.dyn_kwargs is not None:
self.write("}, **")
self.visit(node.dyn_kwargs, frame)
@@ -437,9 +436,7 @@ class CodeGenerator(NodeVisitor):
for name in getattr(visitor, dependency):
if name not in mapping:
mapping[name] = self.temporary_identifier()
- self.writeline(
- "%s = environment.%s[%r]" % (mapping[name], dependency, name)
- )
+ self.writeline(f"{mapping[name]} = environment.{dependency}[{name!r}]")
def enter_frame(self, frame):
undefs = []
@@ -447,15 +444,15 @@ class CodeGenerator(NodeVisitor):
if action == VAR_LOAD_PARAMETER:
pass
elif action == VAR_LOAD_RESOLVE:
- self.writeline("%s = %s(%r)" % (target, self.get_resolve_func(), param))
+ self.writeline(f"{target} = {self.get_resolve_func()}({param!r})")
elif action == VAR_LOAD_ALIAS:
- self.writeline("%s = %s" % (target, param))
+ self.writeline(f"{target} = {param}")
elif action == VAR_LOAD_UNDEFINED:
undefs.append(target)
else:
raise NotImplementedError("unknown load instruction")
if undefs:
- self.writeline("%s = missing" % " = ".join(undefs))
+ self.writeline(f"{' = '.join(undefs)} = missing")
def leave_frame(self, frame, with_python_scope=False):
if not with_python_scope:
@@ -463,12 +460,12 @@ class CodeGenerator(NodeVisitor):
for target in frame.symbols.loads:
undefs.append(target)
if undefs:
- self.writeline("%s = missing" % " = ".join(undefs))
+ self.writeline(f"{' = '.join(undefs)} = missing")
def func(self, name):
if self.environment.is_async:
- return "async def %s" % name
- return "def %s" % name
+ return f"async def {name}"
+ return f"def {name}"
def macro_body(self, node, frame):
"""Dump the function def of a macro or call block."""
@@ -518,7 +515,7 @@ class CodeGenerator(NodeVisitor):
# macros are delayed, they never require output checks
frame.require_output_check = False
frame.symbols.analyze_node(node)
- self.writeline("%s(%s):" % (self.func("macro"), ", ".join(args)), node)
+ self.writeline(f"{self.func('macro')}({', '.join(args)}):", node)
self.indent()
self.buffer(frame)
@@ -527,17 +524,17 @@ class CodeGenerator(NodeVisitor):
self.push_parameter_definitions(frame)
for idx, arg in enumerate(node.args):
ref = frame.symbols.ref(arg.name)
- self.writeline("if %s is missing:" % ref)
+ self.writeline(f"if {ref} is missing:")
self.indent()
try:
default = node.defaults[idx - len(node.args)]
except IndexError:
self.writeline(
- "%s = undefined(%r, name=%r)"
- % (ref, "parameter %r was not provided" % arg.name, arg.name)
+ f'{ref} = undefined("parameter {arg.name!r} was not provided",'
+ f" name={arg.name!r})"
)
else:
- self.writeline("%s = " % ref)
+ self.writeline(f"{ref} = ")
self.visit(default, frame)
self.mark_parameter_stored(ref)
self.outdent()
@@ -557,29 +554,24 @@ class CodeGenerator(NodeVisitor):
if len(macro_ref.node.args) == 1:
arg_tuple += ","
self.write(
- "Macro(environment, macro, %r, (%s), %r, %r, %r, "
- "context.eval_ctx.autoescape)"
- % (
- name,
- arg_tuple,
- macro_ref.accesses_kwargs,
- macro_ref.accesses_varargs,
- macro_ref.accesses_caller,
- )
+ f"Macro(environment, macro, {name!r}, ({arg_tuple}),"
+ f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r},"
+ f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)"
)
def position(self, node):
"""Return a human readable position for the node."""
- rv = "line %d" % node.lineno
+ rv = f"line {node.lineno}"
if self.name is not None:
- rv += " in " + repr(self.name)
+ rv = f"{rv} in {self.name!r}"
return rv
def dump_local_context(self, frame):
- return "{%s}" % ", ".join(
- "%r: %s" % (name, target)
+ items_kv = ", ".join(
+ f"{name!r}: {target}"
for name, target in frame.symbols.dump_stores().items()
)
+ return f"{{{items_kv}}}"
def write_commons(self):
"""Writes a common preamble that is used by root and block functions.
@@ -626,13 +618,10 @@ class CodeGenerator(NodeVisitor):
target = self._context_reference_stack[-1]
if target == "context":
return "resolve"
- return "%s.resolve" % target
+ return f"{target}.resolve"
def derive_context(self, frame):
- return "%s.derived(%s)" % (
- self.get_context_ref(),
- self.dump_local_context(frame),
- )
+ return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})"
def parameter_is_undeclared(self, target):
"""Checks if a given target is an undeclared parameter."""
@@ -655,23 +644,21 @@ class CodeGenerator(NodeVisitor):
if len(vars) == 1:
name = next(iter(vars))
ref = frame.symbols.ref(name)
- self.writeline("context.vars[%r] = %s" % (name, ref))
+ self.writeline(f"context.vars[{name!r}] = {ref}")
else:
self.writeline("context.vars.update({")
for idx, name in enumerate(vars):
if idx:
self.write(", ")
ref = frame.symbols.ref(name)
- self.write("%r: %s" % (name, ref))
+ self.write(f"{name!r}: {ref}")
self.write("})")
if public_names:
if len(public_names) == 1:
- self.writeline("context.exported_vars.add(%r)" % public_names[0])
+ self.writeline(f"context.exported_vars.add({public_names[0]!r})")
else:
- self.writeline(
- "context.exported_vars.update((%s))"
- % ", ".join(map(repr, public_names))
- )
+ names_str = ", ".join(map(repr, public_names))
+ self.writeline(f"context.exported_vars.update(({names_str}))")
# -- Statement Visitors
@@ -692,7 +679,7 @@ class CodeGenerator(NodeVisitor):
# if we want a deferred initialization we cannot move the
# environment into a local name
- envenv = not self.defer_init and ", environment=environment" or ""
+ envenv = "" if self.defer_init else ", environment=environment"
# do we have an extends tag at all? If not, we can save some
# overhead by just not processing any inheritance code.
@@ -701,7 +688,7 @@ class CodeGenerator(NodeVisitor):
# find all blocks
for block in node.find_all(nodes.Block):
if block.name in self.blocks:
- self.fail("block %r defined twice" % block.name, block.lineno)
+ self.fail(f"block {block.name!r} defined twice", block.lineno)
self.blocks[block.name] = block
# find all imports and import them
@@ -711,16 +698,16 @@ class CodeGenerator(NodeVisitor):
self.import_aliases[imp] = alias = self.temporary_identifier()
if "." in imp:
module, obj = imp.rsplit(".", 1)
- self.writeline("from %s import %s as %s" % (module, obj, alias))
+ self.writeline(f"from {module} import {obj} as {alias}")
else:
- self.writeline("import %s as %s" % (imp, alias))
+ self.writeline(f"import {imp} as {alias}")
# add the load name
- self.writeline("name = %r" % self.name)
+ self.writeline(f"name = {self.name!r}")
# generate the root render function.
self.writeline(
- "%s(context, missing=missing%s):" % (self.func("root"), envenv), extra=1
+ f"{self.func('root')}(context, missing=missing{envenv}):", extra=1
)
self.indent()
self.write_commons()
@@ -729,7 +716,7 @@ class CodeGenerator(NodeVisitor):
frame = Frame(eval_ctx)
if "self" in find_undeclared(node.body, ("self",)):
ref = frame.symbols.declare_parameter("self")
- self.writeline("%s = TemplateReference(context)" % ref)
+ self.writeline(f"{ref} = TemplateReference(context)")
frame.symbols.analyze_node(node)
frame.toplevel = frame.rootlevel = True
frame.require_output_check = have_extends and not self.has_known_extends
@@ -750,10 +737,9 @@ class CodeGenerator(NodeVisitor):
if not self.environment.is_async:
self.writeline("yield from parent_template.root_render_func(context)")
else:
+ loop = "async for" if self.environment.is_async else "for"
self.writeline(
- "%sfor event in parent_template."
- "root_render_func(context):"
- % (self.environment.is_async and "async " or "")
+ f"{loop} event in parent_template.root_render_func(context):"
)
self.indent()
self.writeline("yield event")
@@ -763,8 +749,7 @@ class CodeGenerator(NodeVisitor):
# at this point we now have the blocks collected and can visit them too.
for name, block in self.blocks.items():
self.writeline(
- "%s(context, missing=missing%s):"
- % (self.func("block_" + name), envenv),
+ f"{self.func('block_' + name)}(context, missing=missing{envenv}):",
block,
1,
)
@@ -777,10 +762,10 @@ class CodeGenerator(NodeVisitor):
undeclared = find_undeclared(block.body, ("self", "super"))
if "self" in undeclared:
ref = block_frame.symbols.declare_parameter("self")
- self.writeline("%s = TemplateReference(context)" % ref)
+ self.writeline(f"{ref} = TemplateReference(context)")
if "super" in undeclared:
ref = block_frame.symbols.declare_parameter("super")
- self.writeline("%s = context.super(%r, block_%s)" % (ref, name, name))
+ self.writeline(f"{ref} = context.super({name!r}, block_{name})")
block_frame.symbols.analyze_node(block)
block_frame.block = name
self.enter_frame(block_frame)
@@ -789,15 +774,10 @@ class CodeGenerator(NodeVisitor):
self.leave_frame(block_frame, with_python_scope=True)
self.outdent()
- self.writeline(
- "blocks = {%s}" % ", ".join("%r: block_%s" % (x, x) for x in self.blocks),
- extra=1,
- )
-
- # add a function that returns the debug info
- self.writeline(
- "debug_info = %r" % "&".join("%s=%s" % x for x in self.debug_info)
- )
+ blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks)
+ self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1)
+ debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info)
+ self.writeline(f"debug_info = {debug_kv_str!r}")
def visit_Block(self, node, frame):
"""Call a block and register it for the template."""
@@ -819,13 +799,12 @@ class CodeGenerator(NodeVisitor):
if not self.environment.is_async and frame.buffer is None:
self.writeline(
- "yield from context.blocks[%r][0](%s)" % (node.name, context), node
+ f"yield from context.blocks[{node.name!r}][0]({context})", node
)
else:
- loop = self.environment.is_async and "async for" or "for"
+ loop = "async for" if self.environment.is_async else "for"
self.writeline(
- "%s event in context.blocks[%r][0](%s):" % (loop, node.name, context),
- node,
+ f"{loop} event in context.blocks[{node.name!r}][0]({context}):", node
)
self.indent()
self.simple_write("event", frame)
@@ -850,7 +829,7 @@ class CodeGenerator(NodeVisitor):
if not self.has_known_extends:
self.writeline("if parent_template is not None:")
self.indent()
- self.writeline("raise TemplateRuntimeError(%r)" % "extended multiple times")
+ self.writeline('raise TemplateRuntimeError("extended multiple times")')
# if we have a known extends already we don't need that code here
# as we know that the template execution will end here.
@@ -861,7 +840,7 @@ class CodeGenerator(NodeVisitor):
self.writeline("parent_template = environment.get_template(", node)
self.visit(node.template, frame)
- self.write(", %r)" % self.name)
+ self.write(f", {self.name!r})")
self.writeline("for name, parent_block in parent_template.blocks.items():")
self.indent()
self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
@@ -891,9 +870,9 @@ class CodeGenerator(NodeVisitor):
elif isinstance(node.template, (nodes.Tuple, nodes.List)):
func_name = "select_template"
- self.writeline("template = environment.%s(" % func_name, node)
+ self.writeline(f"template = environment.{func_name}(", node)
self.visit(node.template, frame)
- self.write(", %r)" % self.name)
+ self.write(f", {self.name!r})")
if node.ignore_missing:
self.outdent()
self.writeline("except TemplateNotFound:")
@@ -905,16 +884,15 @@ class CodeGenerator(NodeVisitor):
skip_event_yield = False
if node.with_context:
- loop = self.environment.is_async and "async for" or "for"
+ loop = "async for" if self.environment.is_async else "for"
self.writeline(
- "%s event in template.root_render_func("
- "template.new_context(context.get_all(), True, "
- "%s)):" % (loop, self.dump_local_context(frame))
+ f"{loop} event in template.root_render_func("
+ "template.new_context(context.get_all(), True,"
+ f" {self.dump_local_context(frame)})):"
)
elif self.environment.is_async:
self.writeline(
- "for event in (await "
- "template._get_default_module_async())"
+ "for event in (await template._get_default_module_async())"
"._body_stream:"
)
else:
@@ -931,45 +909,37 @@ class CodeGenerator(NodeVisitor):
def visit_Import(self, node, frame):
"""Visit regular imports."""
- self.writeline("%s = " % frame.symbols.ref(node.target), node)
+ self.writeline(f"{frame.symbols.ref(node.target)} = ", node)
if frame.toplevel:
- self.write("context.vars[%r] = " % node.target)
+ self.write(f"context.vars[{node.target!r}] = ")
if self.environment.is_async:
self.write("await ")
self.write("environment.get_template(")
self.visit(node.template, frame)
- self.write(", %r)." % self.name)
+ self.write(f", {self.name!r}).")
if node.with_context:
+ func = "make_module" + ("_async" if self.environment.is_async else "")
self.write(
- "make_module%s(context.get_all(), True, %s)"
- % (
- self.environment.is_async and "_async" or "",
- self.dump_local_context(frame),
- )
+ f"{func}(context.get_all(), True, {self.dump_local_context(frame)})"
)
elif self.environment.is_async:
self.write("_get_default_module_async()")
else:
self.write("_get_default_module()")
if frame.toplevel and not node.target.startswith("_"):
- self.writeline("context.exported_vars.discard(%r)" % node.target)
+ self.writeline(f"context.exported_vars.discard({node.target!r})")
def visit_FromImport(self, node, frame):
"""Visit named imports."""
self.newline(node)
- self.write(
- "included_template = %senvironment.get_template("
- % (self.environment.is_async and "await " or "")
- )
+ prefix = "await " if self.environment.is_async else ""
+ self.write(f"included_template = {prefix}environment.get_template(")
self.visit(node.template, frame)
- self.write(", %r)." % self.name)
+ self.write(f", {self.name!r}).")
if node.with_context:
+ func = "make_module" + ("_async" if self.environment.is_async else "")
self.write(
- "make_module%s(context.get_all(), True, %s)"
- % (
- self.environment.is_async and "_async" or "",
- self.dump_local_context(frame),
- )
+ f"{func}(context.get_all(), True, {self.dump_local_context(frame)})"
)
elif self.environment.is_async:
self.write("_get_default_module_async()")
@@ -984,22 +954,18 @@ class CodeGenerator(NodeVisitor):
else:
alias = name
self.writeline(
- "%s = getattr(included_template, "
- "%r, missing)" % (frame.symbols.ref(alias), name)
+ f"{frame.symbols.ref(alias)} ="
+ f" getattr(included_template, {name!r}, missing)"
)
- self.writeline("if %s is missing:" % frame.symbols.ref(alias))
+ self.writeline(f"if {frame.symbols.ref(alias)} is missing:")
self.indent()
+ message = (
+ "the template {included_template.__name__!r}"
+ f" (imported on {self.position(node)})"
+ f" does not export the requested name {name!r}"
+ )
self.writeline(
- "%s = undefined(%r %% "
- "included_template.__name__, "
- "name=%r)"
- % (
- frame.symbols.ref(alias),
- "the template %%r (imported on %s) does "
- "not export the requested name %s"
- % (self.position(node), repr(name)),
- name,
- )
+ f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})"
)
self.outdent()
if frame.toplevel:
@@ -1010,23 +976,19 @@ class CodeGenerator(NodeVisitor):
if var_names:
if len(var_names) == 1:
name = var_names[0]
- self.writeline(
- "context.vars[%r] = %s" % (name, frame.symbols.ref(name))
- )
+ self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}")
else:
- self.writeline(
- "context.vars.update({%s})"
- % ", ".join(
- "%r: %s" % (name, frame.symbols.ref(name)) for name in var_names
- )
+ names_kv = ", ".join(
+ f"{name!r}: {frame.symbols.ref(name)}" for name in var_names
)
+ self.writeline(f"context.vars.update({{{names_kv}}})")
if discarded_names:
if len(discarded_names) == 1:
- self.writeline("context.exported_vars.discard(%r)" % discarded_names[0])
+ self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})")
else:
+ names_str = ", ".join(map(repr, discarded_names))
self.writeline(
- "context.exported_vars.difference_"
- "update((%s))" % ", ".join(map(repr, discarded_names))
+ f"context.exported_vars.difference_update(({names_str}))"
)
def visit_For(self, node, frame):
@@ -1052,13 +1014,13 @@ class CodeGenerator(NodeVisitor):
if node.test:
loop_filter_func = self.temporary_identifier()
test_frame.symbols.analyze_node(node, for_branch="test")
- self.writeline("%s(fiter):" % self.func(loop_filter_func), node.test)
+ self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test)
self.indent()
self.enter_frame(test_frame)
- self.writeline(self.environment.is_async and "async for " or "for ")
+ self.writeline("async for " if self.environment.is_async else "for ")
self.visit(node.target, loop_frame)
self.write(" in ")
- self.write(self.environment.is_async and "auto_aiter(fiter)" or "fiter")
+ self.write("auto_aiter(fiter)" if self.environment.is_async else "fiter")
self.write(":")
self.indent()
self.writeline("if ", node.test)
@@ -1075,7 +1037,7 @@ class CodeGenerator(NodeVisitor):
# variable is a special one we have to enforce aliasing for it.
if node.recursive:
self.writeline(
- "%s(reciter, loop_render_func, depth=0):" % self.func("loop"), node
+ f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node
)
self.indent()
self.buffer(loop_frame)
@@ -1086,7 +1048,7 @@ class CodeGenerator(NodeVisitor):
# make sure the loop variable is a special one and raise a template
# assertion error if a loop tries to write to loop
if extended_loop:
- self.writeline("%s = missing" % loop_ref)
+ self.writeline(f"{loop_ref} = missing")
for name in node.find_all(nodes.Name):
if name.ctx == "store" and name.name == "loop":
@@ -1097,20 +1059,18 @@ class CodeGenerator(NodeVisitor):
if node.else_:
iteration_indicator = self.temporary_identifier()
- self.writeline("%s = 1" % iteration_indicator)
+ self.writeline(f"{iteration_indicator} = 1")
- self.writeline(self.environment.is_async and "async for " or "for ", node)
+ self.writeline("async for " if self.environment.is_async else "for ", node)
self.visit(node.target, loop_frame)
if extended_loop:
- if self.environment.is_async:
- self.write(", %s in AsyncLoopContext(" % loop_ref)
- else:
- self.write(", %s in LoopContext(" % loop_ref)
+ prefix = "Async" if self.environment.is_async else ""
+ self.write(f", {loop_ref} in {prefix}LoopContext(")
else:
self.write(" in ")
if node.test:
- self.write("%s(" % loop_filter_func)
+ self.write(f"{loop_filter_func}(")
if node.recursive:
self.write("reciter")
else:
@@ -1125,21 +1085,21 @@ class CodeGenerator(NodeVisitor):
if node.recursive:
self.write(", undefined, loop_render_func, depth):")
else:
- self.write(extended_loop and ", undefined):" or ":")
+ self.write(", undefined):" if extended_loop else ":")
self.indent()
self.enter_frame(loop_frame)
self.blockvisit(node.body, loop_frame)
if node.else_:
- self.writeline("%s = 0" % iteration_indicator)
+ self.writeline(f"{iteration_indicator} = 0")
self.outdent()
self.leave_frame(
loop_frame, with_python_scope=node.recursive and not node.else_
)
if node.else_:
- self.writeline("if %s:" % iteration_indicator)
+ self.writeline(f"if {iteration_indicator}:")
self.indent()
self.enter_frame(else_frame)
self.blockvisit(node.else_, else_frame)
@@ -1189,9 +1149,9 @@ class CodeGenerator(NodeVisitor):
self.newline()
if frame.toplevel:
if not node.name.startswith("_"):
- self.write("context.exported_vars.add(%r)" % node.name)
- self.writeline("context.vars[%r] = " % node.name)
- self.write("%s = " % frame.symbols.ref(node.name))
+ self.write(f"context.exported_vars.add({node.name!r})")
+ self.writeline(f"context.vars[{node.name!r}] = ")
+ self.write(f"{frame.symbols.ref(node.name)} = ")
self.macro_def(macro_ref, macro_frame)
def visit_CallBlock(self, node, frame):
@@ -1369,9 +1329,9 @@ class CodeGenerator(NodeVisitor):
if frame.buffer is not None:
if len(body) == 1:
- self.writeline("%s.append(" % frame.buffer)
+ self.writeline(f"{frame.buffer}.append(")
else:
- self.writeline("%s.extend((" % frame.buffer)
+ self.writeline(f"{frame.buffer}.extend((")
self.indent()
@@ -1430,7 +1390,7 @@ class CodeGenerator(NodeVisitor):
if node.filter is not None:
self.visit_Filter(node.filter, block_frame)
else:
- self.write("concat(%s)" % block_frame.buffer)
+ self.write(f"concat({block_frame.buffer})")
self.write(")")
self.pop_assign_tracking(frame)
self.leave_frame(block_frame)
@@ -1454,8 +1414,7 @@ class CodeGenerator(NodeVisitor):
and not self.parameter_is_undeclared(ref)
):
self.write(
- "(undefined(name=%r) if %s is missing else %s)"
- % (node.name, ref, ref)
+ f"(undefined(name={node.name!r}) if {ref} is missing else {ref})"
)
return
@@ -1466,14 +1425,14 @@ class CodeGenerator(NodeVisitor):
# `foo.bar` notation they will be parsed as a normal attribute access
# when used anywhere but in a `set` context
ref = frame.symbols.ref(node.name)
- self.writeline("if not isinstance(%s, Namespace):" % ref)
+ self.writeline(f"if not isinstance({ref}, Namespace):")
self.indent()
self.writeline(
- "raise TemplateRuntimeError(%r)"
- % "cannot assign attribute on non-namespace object"
+ "raise TemplateRuntimeError"
+ '("cannot assign attribute on non-namespace object")'
)
self.outdent()
- self.writeline("%s[%r]" % (ref, node.attr))
+ self.writeline(f"{ref}[{node.attr!r}]")
def visit_Const(self, node, frame):
val = node.as_const(frame.eval_ctx)
@@ -1487,7 +1446,7 @@ class CodeGenerator(NodeVisitor):
self.write(repr(node.as_const(frame.eval_ctx)))
except nodes.Impossible:
self.write(
- "(Markup if context.eval_ctx.autoescape else identity)(%r)" % node.data
+ f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})"
)
def visit_Tuple(self, node, frame):
@@ -1497,7 +1456,7 @@ class CodeGenerator(NodeVisitor):
if idx:
self.write(", ")
self.visit(item, frame)
- self.write(idx == 0 and ",)" or ")")
+ self.write(",)" if idx == 0 else ")")
def visit_List(self, node, frame):
self.write("[")
@@ -1524,14 +1483,14 @@ class CodeGenerator(NodeVisitor):
self.environment.sandboxed
and operator in self.environment.intercepted_binops
):
- self.write("environment.call_binop(context, %r, " % operator)
+ self.write(f"environment.call_binop(context, {operator!r}, ")
self.visit(node.left, frame)
self.write(", ")
self.visit(node.right, frame)
else:
self.write("(")
self.visit(node.left, frame)
- self.write(" %s " % operator)
+ self.write(f" {operator} ")
self.visit(node.right, frame)
self.write(")")
@@ -1544,7 +1503,7 @@ class CodeGenerator(NodeVisitor):
self.environment.sandboxed
and operator in self.environment.intercepted_unops
):
- self.write("environment.call_unop(context, %r, " % operator)
+ self.write(f"environment.call_unop(context, {operator!r}, ")
self.visit(node.node, frame)
else:
self.write("(" + operator)
@@ -1570,12 +1529,12 @@ 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 str_join)"
+ func_name = "(markup_join if context.eval_ctx.volatile else str_join)"
elif frame.eval_ctx.autoescape:
func_name = "markup_join"
else:
func_name = "str_join"
- self.write("%s((" % func_name)
+ self.write(f"{func_name}((")
for arg in node.nodes:
self.visit(arg, frame)
self.write(", ")
@@ -1590,7 +1549,7 @@ class CodeGenerator(NodeVisitor):
self.write(")")
def visit_Operand(self, node, frame):
- self.write(" %s " % operators[node.op])
+ self.write(f" {operators[node.op]} ")
self.visit(node.expr, frame)
@optimizeconst
@@ -1600,7 +1559,7 @@ class CodeGenerator(NodeVisitor):
self.write("environment.getattr(")
self.visit(node.node, frame)
- self.write(", %r)" % node.attr)
+ self.write(f", {node.attr!r})")
if self.environment.is_async:
self.write(")")
@@ -1643,7 +1602,7 @@ class CodeGenerator(NodeVisitor):
self.write(self.filters[node.name] + "(")
func = self.environment.filters.get(node.name)
if func is None:
- self.fail("no filter named %r" % node.name, node.lineno)
+ self.fail(f"no filter named {node.name!r}", node.lineno)
if getattr(func, "contextfilter", False):
self.write("context, ")
elif getattr(func, "evalcontextfilter", False):
@@ -1657,13 +1616,13 @@ class CodeGenerator(NodeVisitor):
self.visit(node.node, frame)
elif frame.eval_ctx.volatile:
self.write(
- "(context.eval_ctx.autoescape and"
- " Markup(concat(%s)) or concat(%s))" % (frame.buffer, frame.buffer)
+ f"(Markup(concat({frame.buffer}))"
+ f" if context.eval_ctx.autoescape else concat({frame.buffer}))"
)
elif frame.eval_ctx.autoescape:
- self.write("Markup(concat(%s))" % frame.buffer)
+ self.write(f"Markup(concat({frame.buffer}))")
else:
- self.write("concat(%s)" % frame.buffer)
+ self.write(f"concat({frame.buffer})")
self.signature(node, frame)
self.write(")")
if self.environment.is_async:
@@ -1673,7 +1632,7 @@ class CodeGenerator(NodeVisitor):
def visit_Test(self, node, frame):
self.write(self.tests[node.name] + "(")
if node.name not in self.environment.tests:
- self.fail("no test named %r" % node.name, node.lineno)
+ self.fail(f"no test named {node.name!r}", node.lineno)
self.visit(node.node, frame)
self.signature(node, frame)
self.write(")")
@@ -1684,12 +1643,9 @@ class CodeGenerator(NodeVisitor):
if node.expr2 is not None:
return self.visit(node.expr2, frame)
self.write(
- "cond_expr_undefined(%r)"
- % (
- "the inline if-"
- "expression on %s evaluated to false and "
- "no else section was defined." % self.position(node)
- )
+ f'cond_expr_undefined("the inline if-expression on'
+ f" {self.position(node)} evaluated to false and no else"
+ f' section was defined.")'
)
self.write("(")
@@ -1709,7 +1665,7 @@ class CodeGenerator(NodeVisitor):
else:
self.write("context.call(")
self.visit(node.node, frame)
- extra_kwargs = forward_caller and {"caller": "caller"} or None
+ extra_kwargs = {"caller": "caller"} if forward_caller else None
self.signature(node, frame, extra_kwargs)
self.write(")")
if self.environment.is_async:
@@ -1727,7 +1683,7 @@ class CodeGenerator(NodeVisitor):
self.write(")")
def visit_MarkSafeIfAutoescape(self, node, frame):
- self.write("(context.eval_ctx.autoescape and Markup or identity)(")
+ self.write("(Markup if context.eval_ctx.autoescape else identity)(")
self.visit(node.expr, frame)
self.write(")")
@@ -1735,7 +1691,7 @@ class CodeGenerator(NodeVisitor):
self.write("environment." + node.name)
def visit_ExtensionAttribute(self, node, frame):
- self.write("environment.extensions[%r].%s" % (node.identifier, node.name))
+ self.write(f"environment.extensions[{node.identifier!r}].{node.name}")
def visit_ImportedName(self, node, frame):
self.write(self.import_aliases[node.importname])
@@ -1764,8 +1720,8 @@ class CodeGenerator(NodeVisitor):
def visit_OverlayScope(self, node, frame):
ctx = self.temporary_identifier()
- self.writeline("%s = %s" % (ctx, self.derive_context(frame)))
- self.writeline("%s.vars = " % ctx)
+ self.writeline(f"{ctx} = {self.derive_context(frame)}")
+ self.writeline(f"{ctx}.vars = ")
self.visit(node.context, frame)
self.push_context_reference(ctx)
@@ -1778,7 +1734,7 @@ class CodeGenerator(NodeVisitor):
def visit_EvalContextModifier(self, node, frame):
for keyword in node.options:
- self.writeline("context.eval_ctx.%s = " % keyword.key)
+ self.writeline(f"context.eval_ctx.{keyword.key} = ")
self.visit(keyword.value, frame)
try:
val = keyword.value.as_const(frame.eval_ctx)
@@ -1790,9 +1746,9 @@ class CodeGenerator(NodeVisitor):
def visit_ScopedEvalContextModifier(self, node, frame):
old_ctx_name = self.temporary_identifier()
saved_ctx = frame.eval_ctx.save()
- self.writeline("%s = context.eval_ctx.save()" % old_ctx_name)
+ self.writeline(f"{old_ctx_name} = context.eval_ctx.save()")
self.visit_EvalContextModifier(node, frame)
for child in node.body:
self.visit(child, frame)
frame.eval_ctx.revert(saved_ctx)
- self.writeline("context.eval_ctx.revert(%s)" % old_ctx_name)
+ self.writeline(f"context.eval_ctx.revert({old_ctx_name})")
diff --git a/src/jinja2/constants.py b/src/jinja2/constants.py
index bf7f2ca..41a1c23 100644
--- a/src/jinja2/constants.py
+++ b/src/jinja2/constants.py
@@ -1,6 +1,5 @@
-# -*- coding: utf-8 -*-
#: list of lorem ipsum words used by the lipsum() helper function
-LOREM_IPSUM_WORDS = u"""\
+LOREM_IPSUM_WORDS = """\
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
auctor augue bibendum blandit class commodo condimentum congue consectetuer
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
diff --git a/src/jinja2/debug.py b/src/jinja2/debug.py
index a0efa29..46c24eb 100644
--- a/src/jinja2/debug.py
+++ b/src/jinja2/debug.py
@@ -106,7 +106,7 @@ def fake_traceback(exc_value, tb, filename, lineno):
if function == "root":
location = "top-level template code"
elif function.startswith("block_"):
- location = 'block "%s"' % function[6:]
+ location = f"block {function[6:]!r}"
# Collect arguments for the new code object. CodeType only
# accepts positional arguments, and arguments were inserted in
diff --git a/src/jinja2/defaults.py b/src/jinja2/defaults.py
index 72a9357..1f0b0ab 100644
--- a/src/jinja2/defaults.py
+++ b/src/jinja2/defaults.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401
from .tests import TESTS as DEFAULT_TESTS # noqa: F401
from .utils import Cycler
diff --git a/src/jinja2/environment.py b/src/jinja2/environment.py
index a477ae9..21333b4 100644
--- a/src/jinja2/environment.py
+++ b/src/jinja2/environment.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Classes for managing templates and their runtime and compile time
options.
"""
@@ -101,13 +100,14 @@ def load_extensions(environment, extensions):
return result
-def fail_for_missing_callable(string, name):
- msg = string % name
+def fail_for_missing_callable(thing, name):
+ msg = f"no {thing} named {name!r}"
+
if isinstance(name, Undefined):
try:
name._fail_with_undefined_error()
except Exception as e:
- msg = "%s (%s; did you forget to quote the callable name?)" % (msg, e)
+ msg = f"{msg} ({e}; did you forget to quote the callable name?)"
raise TemplateRuntimeError(msg)
@@ -121,15 +121,15 @@ def _environment_sanity_check(environment):
!= environment.variable_start_string
!= environment.comment_start_string
), "block, variable and comment start strings must be different"
- assert environment.newline_sequence in (
+ assert environment.newline_sequence in {
"\r",
"\r\n",
"\n",
- ), "newline_sequence set to unknown line ending string."
+ }, "newline_sequence set to unknown line ending string."
return environment
-class Environment(object):
+class Environment:
r"""The core component of Jinja is the `Environment`. It contains
important shared variables like configuration, filters, tests,
globals and others. Instances of this class may be modified if
@@ -479,7 +479,7 @@ class Environment(object):
"""
func = self.filters.get(name)
if func is None:
- fail_for_missing_callable("no filter named %r", name)
+ fail_for_missing_callable("filter", name)
args = [value] + list(args or ())
if getattr(func, "contextfilter", False):
if context is None:
@@ -505,7 +505,7 @@ class Environment(object):
"""
func = self.tests.get(name)
if func is None:
- fail_for_missing_callable("no test named %r", name)
+ fail_for_missing_callable("test", name)
return func(value, *(args or ()), **(kwargs or {}))
@internalcode
@@ -719,11 +719,11 @@ class Environment(object):
zip_file = ZipFile(
target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
)
- log_function('Compiling into Zip archive "%s"' % target)
+ log_function(f"Compiling into Zip archive {target!r}")
else:
if not os.path.isdir(target):
os.makedirs(target)
- log_function('Compiling into folder "%s"' % target)
+ log_function(f"Compiling into folder {target!r}")
try:
for name in self.list_templates(extensions, filter_func):
@@ -733,13 +733,13 @@ class Environment(object):
except TemplateSyntaxError as e:
if not ignore_errors:
raise
- log_function('Could not compile "%s": %s' % (name, e))
+ log_function(f'Could not compile "{name}": {e}')
continue
filename = ModuleLoader.get_module_filename(name)
write_file(filename, code)
- log_function('Compiled "%s" as %s' % (name, filename))
+ log_function(f'Compiled "{name}" as {filename}')
finally:
if zip:
zip_file.close()
@@ -859,7 +859,7 @@ class Environment(object):
if not names:
raise TemplatesNotFound(
- message=u"Tried to select from an empty list " u"of templates."
+ message="Tried to select from an empty list of templates."
)
globals = self.make_globals(globals)
for name in names:
@@ -902,7 +902,7 @@ class Environment(object):
return dict(self.globals, **d)
-class Template(object):
+class Template:
"""The central template object. This class represents a compiled template
and is used to evaluate it.
@@ -1074,8 +1074,7 @@ class Template(object):
"""
vars = dict(*args, **kwargs)
try:
- for event in self.root_render_func(self.new_context(vars)):
- yield event
+ yield from self.root_render_func(self.new_context(vars))
except Exception:
yield self.environment.handle_exception()
@@ -1168,13 +1167,13 @@ class Template(object):
def __repr__(self):
if self.name is None:
- name = "memory:%x" % id(self)
+ name = f"memory:{id(self):x}"
else:
name = repr(self.name)
- return "<%s %s>" % (self.__class__.__name__, name)
+ return f"<{self.__class__.__name__} {name}>"
-class TemplateModule(object):
+class TemplateModule:
"""Represents an imported template. All the exported names of the
template are available as attributes on this object. Additionally
converting it into a string renders the contents.
@@ -1202,13 +1201,13 @@ class TemplateModule(object):
def __repr__(self):
if self.__name__ is None:
- name = "memory:%x" % id(self)
+ name = f"memory:{id(self):x}"
else:
name = repr(self.__name__)
- return "<%s %s>" % (self.__class__.__name__, name)
+ return f"<{self.__class__.__name__} {name}>"
-class TemplateExpression(object):
+class TemplateExpression:
"""The :meth:`jinja2.Environment.compile_expression` method returns an
instance of this object. It encapsulates the expression-like access
to the template with an expression it wraps.
@@ -1227,7 +1226,7 @@ class TemplateExpression(object):
return rv
-class TemplateStream(object):
+class TemplateStream:
"""A template stream works pretty much like an ordinary python generator
but it can buffer multiple items to reduce the number of total iterations.
Per default the output is unbuffered which means that for every unbuffered
diff --git a/src/jinja2/exceptions.py b/src/jinja2/exceptions.py
index 8d5e89d..07cfba2 100644
--- a/src/jinja2/exceptions.py
+++ b/src/jinja2/exceptions.py
@@ -65,10 +65,10 @@ class TemplatesNotFound(TemplateNotFound):
else:
parts.append(name)
- message = u"none of the templates given were found: " + u", ".join(
+ message = "none of the templates given were found: " + ", ".join(
map(str, parts)
)
- TemplateNotFound.__init__(self, names and names[-1] or None, message)
+ TemplateNotFound.__init__(self, names[-1] if names else None, message)
self.templates = list(names)
@@ -92,10 +92,10 @@ class TemplateSyntaxError(TemplateError):
return self.message
# otherwise attach some stuff
- location = "line %d" % self.lineno
+ location = f"line {self.lineno}"
name = self.filename or self.name
if name:
- location = 'File "%s", %s' % (name, location)
+ location = f'File "{name}", {location}'
lines = [self.message, " " + location]
# if the source is set, add the line to the output
@@ -107,7 +107,7 @@ class TemplateSyntaxError(TemplateError):
if line:
lines.append(" " + line.strip())
- return u"\n".join(lines)
+ return "\n".join(lines)
def __reduce__(self):
# https://bugs.python.org/issue1692335 Exceptions that take
diff --git a/src/jinja2/ext.py b/src/jinja2/ext.py
index fd36e2d..7c36bb4 100644
--- a/src/jinja2/ext.py
+++ b/src/jinja2/ext.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Extension API for adding custom tags and behavior."""
import pprint
import re
@@ -40,7 +39,7 @@ class ExtensionRegistry(type):
def __new__(mcs, name, bases, d):
rv = type.__new__(mcs, name, bases, d)
- rv.identifier = rv.__module__ + "." + rv.__name__
+ rv.identifier = f"{rv.__module__}.{rv.__name__}"
return rv
@@ -203,7 +202,7 @@ class InternationalizationExtension(Extension):
def _install_null(self, newstyle=None):
self._install_callables(
- lambda x: x, lambda s, p, n: (n != 1 and (p,) or (s,))[0], newstyle
+ lambda x: x, lambda s, p, n: s if n == 1 else p, newstyle
)
def _install_callables(self, gettext, ngettext, newstyle=None):
@@ -246,7 +245,7 @@ class InternationalizationExtension(Extension):
name = parser.stream.expect("name")
if name.value in variables:
parser.fail(
- "translatable variable %r defined twice." % name.value,
+ f"translatable variable {name.value!r} defined twice.",
name.lineno,
exc=TemplateAssertionError,
)
@@ -294,7 +293,7 @@ class InternationalizationExtension(Extension):
name = parser.stream.expect("name")
if name.value not in variables:
parser.fail(
- "unknown variable %r for pluralization" % name.value,
+ f"unknown variable {name.value!r} for pluralization",
name.lineno,
exc=TemplateAssertionError,
)
@@ -353,7 +352,7 @@ class InternationalizationExtension(Extension):
next(parser.stream)
name = parser.stream.expect("name").value
referenced.append(name)
- buf.append("%%(%s)s" % name)
+ buf.append(f"%({name})s")
parser.stream.expect("variable_end")
elif parser.stream.current.type == "block_begin":
next(parser.stream)
@@ -436,7 +435,7 @@ class ExprStmtExtension(Extension):
that it doesn't print the return value.
"""
- tags = set(["do"])
+ tags = {"do"}
def parse(self, parser):
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
@@ -447,7 +446,7 @@ class ExprStmtExtension(Extension):
class LoopControlExtension(Extension):
"""Adds break and continue to the template engine."""
- tags = set(["break", "continue"])
+ tags = {"break", "continue"}
def parse(self, parser):
token = next(parser.stream)
@@ -575,7 +574,7 @@ def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, babel_style=True
yield node.lineno, node.node.name, strings
-class _CommentFinder(object):
+class _CommentFinder:
"""Helper class to find comments in a token stream. Can only
find comments for gettext calls forwards. Once the comment
from line 4 is found, a comment for line 1 will not return a
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index 0675f54..f39d389 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Built-in template filters used with the ``|`` operator."""
import math
import random
@@ -153,9 +152,8 @@ def do_urlencode(value):
else:
items = iter(value)
- return u"&".join(
- "%s=%s" % (url_quote(k, for_qs=True), url_quote(v, for_qs=True))
- for k, v in items
+ return "&".join(
+ f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items
)
@@ -224,13 +222,13 @@ def do_xmlattr(_eval_ctx, d, autospace=True):
As you can see it automatically prepends a space in front of the item
if the filter returned something unless the second parameter is false.
"""
- rv = u" ".join(
- u'%s="%s"' % (escape(key), escape(value))
+ rv = " ".join(
+ f'{escape(key)}="{escape(value)}"'
for key, value in d.items()
if value is not None and not isinstance(value, Undefined)
)
if autospace and rv:
- rv = u" " + rv
+ rv = " " + rv
if _eval_ctx.autoescape:
rv = Markup(rv)
return rv
@@ -415,7 +413,7 @@ def do_max(environment, value, case_sensitive=False, attribute=None):
return _min_or_max(environment, value, max, case_sensitive, attribute)
-def do_default(value, default_value=u"", boolean=False):
+def do_default(value, default_value="", boolean=False):
"""If the value is undefined it will return the passed default value,
otherwise the value of the variable:
@@ -444,7 +442,7 @@ def do_default(value, default_value=u"", boolean=False):
@evalcontextfilter
-def do_join(eval_ctx, value, d=u"", attribute=None):
+def do_join(eval_ctx, value, d="", attribute=None):
"""Return a string which is the concatenation of the strings in the
sequence. The separator between elements is an empty string per
default, you can define it with the optional parameter:
@@ -541,27 +539,27 @@ def do_filesizeformat(value, binary=False):
prefixes are used (Mebi, Gibi).
"""
bytes = float(value)
- base = binary and 1024 or 1000
+ base = 1024 if binary else 1000
prefixes = [
- (binary and "KiB" or "kB"),
- (binary and "MiB" or "MB"),
- (binary and "GiB" or "GB"),
- (binary and "TiB" or "TB"),
- (binary and "PiB" or "PB"),
- (binary and "EiB" or "EB"),
- (binary and "ZiB" or "ZB"),
- (binary and "YiB" or "YB"),
+ ("KiB" if binary else "kB"),
+ ("MiB" if binary else "MB"),
+ ("GiB" if binary else "GB"),
+ ("TiB" if binary else "TB"),
+ ("PiB" if binary else "PB"),
+ ("EiB" if binary else "EB"),
+ ("ZiB" if binary else "ZB"),
+ ("YiB" if binary else "YB"),
]
if bytes == 1:
return "1 Byte"
elif bytes < base:
- return "%d Bytes" % bytes
+ return f"{int(bytes)} Bytes"
else:
for i, prefix in enumerate(prefixes):
unit = base ** (i + 2)
if bytes < unit:
- return "%.1f %s" % ((base * bytes / unit), prefix)
- return "%.1f %s" % ((base * bytes / unit), prefix)
+ return f"{base * bytes / unit:.1f} {prefix}"
+ return f"{base * bytes / unit:.1f} {prefix}"
def do_pprint(value):
@@ -621,8 +619,8 @@ def do_indent(s, width=4, first=False, blank=False):
Rename the ``indentfirst`` argument to ``first``.
"""
- indention = u" " * width
- newline = u"\n"
+ indention = " " * width
+ newline = "\n"
if isinstance(s, Markup):
indention = Markup(indention)
@@ -674,8 +672,8 @@ def do_truncate(env, s, length=255, killwords=False, end="...", leeway=None):
"""
if leeway is None:
leeway = env.policies["truncate.leeway"]
- assert length >= len(end), "expected length >= %s, got %s" % (len(end), length)
- assert leeway >= 0, "expected leeway >= 0, got %s" % leeway
+ assert length >= len(end), f"expected length >= {len(end)}, got {length}"
+ assert leeway >= 0, f"expected leeway >= 0, got {leeway}"
if len(s) <= length + leeway:
return s
if killwords:
@@ -1245,14 +1243,13 @@ def do_tojson(eval_ctx, value, indent=None):
def prepare_map(args, kwargs):
context = args[0]
seq = args[1]
- default = None
if len(args) == 2 and "attribute" in kwargs:
attribute = kwargs.pop("attribute")
default = kwargs.pop("default", None)
if kwargs:
raise FilterArgumentError(
- "Unexpected keyword argument %r" % next(iter(kwargs))
+ f"Unexpected keyword argument {next(iter(kwargs))!r}"
)
func = make_attrgetter(context.environment, attribute, default=default)
else:
diff --git a/src/jinja2/idtracking.py b/src/jinja2/idtracking.py
index 7889a2b..78cad91 100644
--- a/src/jinja2/idtracking.py
+++ b/src/jinja2/idtracking.py
@@ -20,7 +20,7 @@ def symbols_for_node(node, parent_symbols=None):
return sym
-class Symbols(object):
+class Symbols:
def __init__(self, parent=None, level=None):
if level is None:
if parent is None:
@@ -38,7 +38,7 @@ class Symbols(object):
visitor.visit(node, **kwargs)
def _define_ref(self, name, load=None):
- ident = "l_%d_%s" % (self.level, name)
+ ident = f"l_{self.level}_{name}"
self.refs[name] = ident
if load is not None:
self.loads[ident] = load
@@ -60,8 +60,8 @@ class Symbols(object):
rv = self.find_ref(name)
if rv is None:
raise AssertionError(
- "Tried to resolve a name to a reference that "
- "was unknown to the frame (%r)" % name
+ "Tried to resolve a name to a reference that was"
+ f" unknown to the frame ({name!r})"
)
return rv
@@ -199,7 +199,7 @@ class RootVisitor(NodeVisitor):
def generic_visit(self, node, *args, **kwargs):
raise NotImplementedError(
- "Cannot find symbols for %r" % node.__class__.__name__
+ f"Cannot find symbols for {node.__class__.__name__!r}"
)
diff --git a/src/jinja2/lexer.py b/src/jinja2/lexer.py
index 4988f7e..e0b7a2e 100644
--- a/src/jinja2/lexer.py
+++ b/src/jinja2/lexer.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Implements a Jinja / Python combination lexer. The ``Lexer`` class
is used to do some preprocessing. It filters out invalid operators like
the bitshift operators we don't allow in templates. It separates
@@ -120,10 +119,10 @@ operators = {
";": TOKEN_SEMICOLON,
}
-reverse_operators = dict([(v, k) for k, v in operators.items()])
+reverse_operators = {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)))
+ f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})"
)
ignored_tokens = frozenset(
@@ -227,7 +226,7 @@ def compile_rules(environment):
return [x[1:] for x in sorted(rules, reverse=True)]
-class Failure(object):
+class Failure:
"""Class that raises a `TemplateSyntaxError` if called.
Used by the `Lexer` to specify known errors.
"""
@@ -277,10 +276,10 @@ class Token(tuple):
return False
def __repr__(self):
- return "Token(%r, %r, %r)" % (self.lineno, self.type, self.value)
+ return f"Token({self.lineno!r}, {self.type!r}, {self.value!r})"
-class TokenStreamIterator(object):
+class TokenStreamIterator:
"""The iterator for tokenstreams. Iterate over the stream
until the eof token is reached.
"""
@@ -300,7 +299,7 @@ class TokenStreamIterator(object):
return token
-class TokenStream(object):
+class TokenStream:
"""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
one token ahead. The current active token is stored as :attr:`current`.
@@ -385,13 +384,13 @@ class TokenStream(object):
expr = describe_token_expr(expr)
if self.current.type is TOKEN_EOF:
raise TemplateSyntaxError(
- "unexpected end of template, expected %r." % expr,
+ f"unexpected end of template, expected {expr!r}.",
self.current.lineno,
self.name,
self.filename,
)
raise TemplateSyntaxError(
- "expected token %r, got %r" % (expr, describe_token(self.current)),
+ f"expected token {expr!r}, got {describe_token(self.current)!r}",
self.current.lineno,
self.name,
self.filename,
@@ -435,10 +434,10 @@ class OptionalLStrip(tuple):
# Even though it looks like a no-op, creating instances fails
# without this.
def __new__(cls, *members, **kwargs):
- return super(OptionalLStrip, cls).__new__(cls, members)
+ return super().__new__(cls, members)
-class Lexer(object):
+class Lexer:
"""Class that implements a lexer for a given environment. Automatically
created by the environment class, usually you don't have to do that.
@@ -471,8 +470,13 @@ class Lexer(object):
# is required.
root_tag_rules = compile_rules(environment)
+ block_start_re = e(environment.block_start_string)
+ block_end_re = e(environment.block_end_string)
+ comment_end_re = e(environment.comment_end_string)
+ variable_end_re = e(environment.variable_end_string)
+
# block suffix if trimming is enabled
- block_suffix_re = environment.trim_blocks and "\\n?" or ""
+ block_suffix_re = "\\n?" if environment.trim_blocks else ""
# If lstrip is enabled, it should not be applied if there is any
# non-whitespace between the newline and block.
@@ -481,28 +485,20 @@ class Lexer(object):
self.newline_sequence = environment.newline_sequence
self.keep_trailing_newline = environment.keep_trailing_newline
+ root_raw_re = (
+ fr"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
+ fr"(?:\-{block_end_re}\s*|{block_end_re}))"
+ )
+ root_parts_re = "|".join(
+ [root_raw_re] + [fr"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules]
+ )
+
# global lexing rules
self.rules = {
"root": [
# directives
(
- c(
- "(.*?)(?:%s)"
- % "|".join(
- [
- r"(?P<raw_begin>%s(\-|\+|)\s*raw\s*(?:\-%s\s*|%s))"
- % (
- e(environment.block_start_string),
- e(environment.block_end_string),
- e(environment.block_end_string),
- )
- ]
- + [
- r"(?P<%s>%s(\-|\+|))" % (n, r)
- for n, r in root_tag_rules
- ]
- )
- ),
+ c(fr"(.*?)(?:{root_parts_re})"),
OptionalLStrip(TOKEN_DATA, "#bygroup"),
"#bygroup",
),
@@ -513,29 +509,18 @@ class Lexer(object):
TOKEN_COMMENT_BEGIN: [
(
c(
- r"(.*?)((?:\-%s\s*|%s)%s)"
- % (
- e(environment.comment_end_string),
- e(environment.comment_end_string),
- block_suffix_re,
- )
+ fr"(.*?)((?:\-{comment_end_re}\s*"
+ fr"|{comment_end_re}){block_suffix_re})"
),
(TOKEN_COMMENT, TOKEN_COMMENT_END),
"#pop",
),
- (c("(.)"), (Failure("Missing end of comment tag"),), None),
+ (c(r"(.)"), (Failure("Missing end of comment tag"),), None),
],
# blocks
TOKEN_BLOCK_BEGIN: [
(
- c(
- r"(?:\-%s\s*|%s)%s"
- % (
- e(environment.block_end_string),
- e(environment.block_end_string),
- block_suffix_re,
- )
- ),
+ c(fr"(?:\-{block_end_re}\s*|{block_end_re}){block_suffix_re}"),
TOKEN_BLOCK_END,
"#pop",
),
@@ -544,13 +529,7 @@ class Lexer(object):
# variables
TOKEN_VARIABLE_BEGIN: [
(
- c(
- r"\-%s\s*|%s"
- % (
- e(environment.variable_end_string),
- e(environment.variable_end_string),
- )
- ),
+ c(fr"\-{variable_end_re}\s*|{variable_end_re}"),
TOKEN_VARIABLE_END,
"#pop",
)
@@ -560,18 +539,13 @@ class Lexer(object):
TOKEN_RAW_BEGIN: [
(
c(
- r"(.*?)((?:%s(\-|\+|))\s*endraw\s*(?:\-%s\s*|%s%s))"
- % (
- e(environment.block_start_string),
- e(environment.block_end_string),
- e(environment.block_end_string),
- block_suffix_re,
- )
+ fr"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
+ fr"(?:\-{block_end_re}\s*|{block_end_re}{block_suffix_re}))"
),
OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END),
"#pop",
),
- (c("(.)"), (Failure("Missing end of raw directive"),), None),
+ (c(r"(.)"), (Failure("Missing end of raw directive"),), None),
],
# line statements
TOKEN_LINESTATEMENT_BEGIN: [
@@ -649,10 +623,8 @@ class Lexer(object):
"""
lines = source.splitlines()
if self.keep_trailing_newline and source:
- for newline in ("\r\n", "\r", "\n"):
- if source.endswith(newline):
- lines.append("")
- break
+ if source.endswith(("\r\n", "\r", "\n")):
+ lines.append("")
source = "\n".join(lines)
pos = 0
lineno = 1
@@ -732,9 +704,8 @@ class Lexer(object):
break
else:
raise RuntimeError(
- "%r wanted to resolve "
- "the token dynamically"
- " but no group matched" % regex
+ f"{regex!r} wanted to resolve the token dynamically"
+ " but no group matched"
)
# normal group
else:
@@ -757,13 +728,12 @@ class Lexer(object):
elif data in ("}", ")", "]"):
if not balancing_stack:
raise TemplateSyntaxError(
- "unexpected '%s'" % data, lineno, name, filename
+ f"unexpected '{data}'", lineno, name, filename
)
expected_op = balancing_stack.pop()
if expected_op != data:
raise TemplateSyntaxError(
- "unexpected '%s', "
- "expected '%s'" % (data, expected_op),
+ f"unexpected '{data}', expected '{expected_op}'",
lineno,
name,
filename,
@@ -791,9 +761,8 @@ class Lexer(object):
break
else:
raise RuntimeError(
- "%r wanted to resolve the "
- "new state dynamically but"
- " no group matched" % regex
+ f"{regex!r} wanted to resolve the new state dynamically"
+ f" but no group matched"
)
# direct state name given
else:
@@ -804,7 +773,7 @@ class Lexer(object):
# raise error
elif pos2 == pos:
raise RuntimeError(
- "%r yielded empty string without stack change" % regex
+ f"{regex!r} yielded empty string without stack change"
)
# publish new function and start again
pos = pos2
@@ -817,8 +786,5 @@ class Lexer(object):
return
# something went wrong
raise TemplateSyntaxError(
- "unexpected char %r at %d" % (source[pos], pos),
- lineno,
- name,
- filename,
+ f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename
)
diff --git a/src/jinja2/loaders.py b/src/jinja2/loaders.py
index e48155d..b02cc35 100644
--- a/src/jinja2/loaders.py
+++ b/src/jinja2/loaders.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""API and implementations for loading templates from different data
sources.
"""
@@ -33,7 +32,7 @@ def split_template_path(template):
return pieces
-class BaseLoader(object):
+class BaseLoader:
"""Baseclass for all loaders. Subclass this and override `get_source` to
implement a custom loading mechanism. The environment provides a
`get_template` method that calls the loader's `load` method to get the
@@ -86,7 +85,7 @@ class BaseLoader(object):
"""
if not self.has_source_access:
raise RuntimeError(
- "%s cannot provide access to the source" % self.__class__.__name__
+ f"{self.__class__.__name__} cannot provide access to the source"
)
raise TemplateNotFound(template)
@@ -277,8 +276,8 @@ class PackageLoader(BaseLoader):
if self._template_root is None:
raise ValueError(
- "The %r package was not installed in a way that"
- " PackageLoader understands." % package_name
+ f"The {package_name!r} package was not installed in a"
+ " way that PackageLoader understands."
)
def get_source(self, environment, template):
@@ -514,7 +513,7 @@ class ModuleLoader(BaseLoader):
has_source_access = False
def __init__(self, path):
- package_name = "_jinja2_module_templates_%x" % id(self)
+ package_name = f"_jinja2_module_templates_{id(self):x}"
# create a fake module that looks for the templates in the
# path given.
@@ -546,7 +545,7 @@ class ModuleLoader(BaseLoader):
@internalcode
def load(self, environment, name, globals=None):
key = self.get_template_key(name)
- module = "%s.%s" % (self.package_name, key)
+ module = f"{self.package_name}.{key}"
mod = getattr(self.module, module, None)
if mod is None:
try:
diff --git a/src/jinja2/meta.py b/src/jinja2/meta.py
index d112cbe..899e179 100644
--- a/src/jinja2/meta.py
+++ b/src/jinja2/meta.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Functions that expose information about templates that might be
interesting for introspection.
"""
diff --git a/src/jinja2/nativetypes.py b/src/jinja2/nativetypes.py
index 4f8106b..e0ad94d 100644
--- a/src/jinja2/nativetypes.py
+++ b/src/jinja2/nativetypes.py
@@ -32,7 +32,7 @@ def native_concat(nodes, preserve_quotes=True):
else:
if isinstance(nodes, types.GeneratorType):
nodes = chain(head, nodes)
- raw = u"".join([str(v) for v in nodes])
+ raw = "".join([str(v) for v in nodes])
try:
literal = literal_eval(raw)
@@ -44,7 +44,8 @@ def native_concat(nodes, preserve_quotes=True):
# Without this, "'{{ a }}', '{{ b }}'" results in "a, b", but should
# be ('a', 'b').
if preserve_quotes and isinstance(literal, str):
- return "{quote}{}{quote}".format(literal, quote=raw[0])
+ quote = raw[0]
+ return f"{quote}{literal}{quote}"
return literal
diff --git a/src/jinja2/nodes.py b/src/jinja2/nodes.py
index e2351bd..b91f6bf 100644
--- a/src/jinja2/nodes.py
+++ b/src/jinja2/nodes.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""AST nodes generated by the parser for the compiler. Also provides
some node tree helper functions used by the parser and compiler in order
to normalize nodes.
@@ -18,7 +17,11 @@ _binop_to_func = {
"-": operator.sub,
}
-_uaop_to_func = {"not": operator.not_, "+": operator.pos, "-": operator.neg}
+_uaop_to_func = {
+ "not": operator.not_,
+ "+": operator.pos,
+ "-": operator.neg,
+}
_cmpop_to_func = {
"eq": operator.eq,
@@ -53,7 +56,7 @@ class NodeType(type):
return type.__new__(mcs, name, bases, d)
-class EvalContext(object):
+class EvalContext:
"""Holds evaluation time information. Custom attributes can be attached
to it in extensions.
"""
@@ -78,9 +81,8 @@ def get_eval_context(node, ctx):
if ctx is None:
if node.environment is None:
raise RuntimeError(
- "if no eval context is passed, the "
- "node must have an attached "
- "environment."
+ "if no eval context is passed, the node must have an"
+ " attached environment."
)
return EvalContext(node.environment)
return ctx
@@ -113,21 +115,17 @@ class Node(metaclass=NodeType):
if fields:
if len(fields) != len(self.fields):
if not self.fields:
- raise TypeError("%r takes 0 arguments" % self.__class__.__name__)
+ raise TypeError(f"{self.__class__.__name__!r} takes 0 arguments")
raise TypeError(
- "%r takes 0 or %d argument%s"
- % (
- self.__class__.__name__,
- len(self.fields),
- len(self.fields) != 1 and "s" or "",
- )
+ f"{self.__class__.__name__!r} takes 0 or {len(self.fields)}"
+ f" argument{'s' if len(self.fields) != 1 else ''}"
)
for name, arg in zip(self.fields, fields):
setattr(self, name, arg)
for attr in self.attributes:
setattr(self, attr, attributes.pop(attr, None))
if attributes:
- raise TypeError("unknown attribute %r" % next(iter(attributes)))
+ raise TypeError(f"unknown attribute {next(iter(attributes))!r}")
def iter_fields(self, exclude=None, only=None):
"""This method iterates over all fields that are defined and yields
@@ -174,8 +172,7 @@ class Node(metaclass=NodeType):
for child in self.iter_child_nodes():
if isinstance(child, node_type):
yield child
- for result in child.find_all(node_type):
- yield result
+ yield from child.find_all(node_type)
def set_ctx(self, ctx):
"""Reset the context of a node and all child nodes. Per default the
@@ -221,10 +218,8 @@ class Node(metaclass=NodeType):
return hash(tuple(self.iter_fields()))
def __repr__(self):
- return "%s(%s)" % (
- self.__class__.__name__,
- ", ".join("%s=%r" % (arg, getattr(self, arg, None)) for arg in self.fields),
- )
+ args_str = ", ".join(f"{a}={getattr(self, a, None)!r}" for a in self.fields)
+ return f"{self.__class__.__name__}({args_str})"
def dump(self):
def _dump(node):
@@ -232,7 +227,7 @@ class Node(metaclass=NodeType):
buf.append(repr(node))
return
- buf.append("nodes.%s(" % node.__class__.__name__)
+ buf.append(f"nodes.{node.__class__.__name__}(")
if not node.fields:
buf.append(")")
return
@@ -809,15 +804,6 @@ class Operand(Helper):
fields = ("op", "expr")
-if __debug__:
- Operand.__doc__ += "\nThe following operators are available: " + ", ".join(
- sorted(
- "``%s``" % x
- for x in set(_binop_to_func) | set(_uaop_to_func) | set(_cmpop_to_func)
- )
- )
-
-
class Mul(BinExpr):
"""Multiplies the left with the right node."""
diff --git a/src/jinja2/optimizer.py b/src/jinja2/optimizer.py
index 7bc78c4..39d059f 100644
--- a/src/jinja2/optimizer.py
+++ b/src/jinja2/optimizer.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""The optimizer tries to constant fold expressions and modify the AST
in place so that it should be faster to evaluate.
@@ -24,7 +23,7 @@ class Optimizer(NodeTransformer):
self.environment = environment
def generic_visit(self, node, *args, **kwargs):
- node = super(Optimizer, self).generic_visit(node, *args, **kwargs)
+ node = super().generic_visit(node, *args, **kwargs)
# Do constant folding. Some other nodes besides Expr have
# as_const, but folding them causes errors later on.
diff --git a/src/jinja2/parser.py b/src/jinja2/parser.py
index ec0778a..eedea7a 100644
--- a/src/jinja2/parser.py
+++ b/src/jinja2/parser.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Parse tokens from the lexer into nodes for the compiler."""
from . import nodes
from .exceptions import TemplateAssertionError
@@ -34,7 +33,7 @@ _math_nodes = {
}
-class Parser(object):
+class Parser:
"""This is the central parsing class Jinja uses. It's passed to
extensions and can be used to parse expressions or statements.
"""
@@ -68,7 +67,7 @@ class Parser(object):
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]
+ map(repr, map(describe_token_expr, end_token_stack[-1]))
)
else:
currently_looking = None
@@ -76,25 +75,23 @@ class Parser(object):
if name is None:
message = ["Unexpected end of template."]
else:
- message = ["Encountered unknown tag '%s'." % name]
+ message = [f"Encountered unknown tag {name!r}."]
if currently_looking:
if name is not None and name in expected:
message.append(
- "You probably made a nesting mistake. Jinja "
- "is expecting this tag, but currently looking "
- "for %s." % currently_looking
+ "You probably made a nesting mistake. Jinja is expecting this tag,"
+ f" but currently looking for {currently_looking}."
)
else:
message.append(
- "Jinja was looking for the following tags: "
- "%s." % currently_looking
+ f"Jinja was looking for the following tags: {currently_looking}."
)
if self._tag_stack:
message.append(
- "The innermost block that needs to be "
- "closed is '%s'." % self._tag_stack[-1]
+ "The innermost block that needs to be closed is"
+ f" {self._tag_stack[-1]!r}."
)
self.fail(" ".join(message), lineno)
@@ -125,7 +122,7 @@ class Parser(object):
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
self._last_identifier += 1
rv = object.__new__(nodes.InternalName)
- nodes.Node.__init__(rv, "fi%d" % self._last_identifier, lineno=lineno)
+ nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
return rv
def parse_statement(self):
@@ -264,9 +261,8 @@ class Parser(object):
# raise a nicer error message in that case.
if self.stream.current.type == "sub":
self.fail(
- "Block names in Jinja have to be valid Python "
- "identifiers and may not contain hyphens, use an "
- "underscore instead."
+ "Block names in Jinja have to be valid Python identifiers and may not"
+ " contain hyphens, use an underscore instead."
)
node.body = self.parse_statements(("name:endblock",), drop_needle=True)
@@ -434,7 +430,7 @@ class Parser(object):
target.set_ctx("store")
if not target.can_assign():
self.fail(
- "can't assign to %r" % target.__class__.__name__.lower(), target.lineno
+ f"can't assign to {target.__class__.__name__.lower()!r}", target.lineno
)
return target
@@ -595,7 +591,7 @@ class Parser(object):
elif token.type == "lbrace":
node = self.parse_dict()
else:
- self.fail("unexpected '%s'" % describe_token(token), token.lineno)
+ self.fail(f"unexpected {describe_token(token)!r}", token.lineno)
return node
def parse_tuple(
@@ -657,8 +653,8 @@ class Parser(object):
# tuple.
if not explicit_parentheses:
self.fail(
- "Expected an expression, got '%s'"
- % describe_token(self.stream.current)
+ "Expected an expression,"
+ f" got {describe_token(self.stream.current)!r}"
)
return nodes.Tuple(args, "load", lineno=lineno)
diff --git a/src/jinja2/runtime.py b/src/jinja2/runtime.py
index 4a06b70..292ac72 100644
--- a/src/jinja2/runtime.py
+++ b/src/jinja2/runtime.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""The runtime functions and state used by compiled templates."""
import sys
from collections import abc
@@ -53,7 +52,7 @@ def markup_join(seq):
for arg in iterator:
buf.append(arg)
if hasattr(arg, "__html__"):
- return Markup(u"").join(chain(buf, iterator))
+ return Markup("").join(chain(buf, iterator))
return concat(buf)
@@ -101,7 +100,7 @@ def new_context(
return environment.context_class(environment, parent, template_name, blocks)
-class TemplateReference(object):
+class TemplateReference:
"""The `self` in templates."""
def __init__(self, context):
@@ -112,7 +111,7 @@ class TemplateReference(object):
return BlockReference(name, self.__context, blocks, 0)
def __repr__(self):
- return "<%s %r>" % (self.__class__.__name__, self.__context.name)
+ return f"<{self.__class__.__name__} {self.__context.name!r}>"
def _get_func(x):
@@ -206,7 +205,7 @@ class Context(metaclass=ContextMeta):
blocks[index]
except LookupError:
return self.environment.undefined(
- "there is no parent block called %r." % name, name="super"
+ f"there is no parent block called {name!r}.", name="super"
)
return BlockReference(name, self, blocks, index)
@@ -244,7 +243,7 @@ class Context(metaclass=ContextMeta):
def get_exported(self):
"""Get a new dict with the exported variables."""
- return dict((k, self.vars[k]) for k in self.exported_vars)
+ return {k: self.vars[k] for k in self.exported_vars}
def get_all(self):
"""Return the complete context as dict including the exported
@@ -290,9 +289,8 @@ class Context(metaclass=ContextMeta):
return __obj(*args, **kwargs)
except StopIteration:
return __self.environment.undefined(
- "value was undefined because "
- "a callable raised a "
- "StopIteration exception"
+ "value was undefined because a callable raised a"
+ " StopIteration exception"
)
def derived(self, locals=None):
@@ -333,14 +331,10 @@ class Context(metaclass=ContextMeta):
return item
def __repr__(self):
- return "<%s %s of %r>" % (
- self.__class__.__name__,
- repr(self.get_all()),
- self.name,
- )
+ return f"<{self.__class__.__name__} {self.get_all()!r} of {self.name!r}>"
-class BlockReference(object):
+class BlockReference:
"""One block on a template reference."""
def __init__(self, name, context, stack, depth):
@@ -354,7 +348,7 @@ class BlockReference(object):
"""Super the block."""
if self._depth + 1 >= len(self._stack):
return self._context.environment.undefined(
- "there is no parent block called %r." % self.name, name="super"
+ f"there is no parent block called {self.name!r}.", name="super"
)
return BlockReference(self.name, self._context, self._stack, self._depth + 1)
@@ -554,10 +548,10 @@ class LoopContext:
return self._recurse(iterable, self._recurse, depth=self.depth)
def __repr__(self):
- return "<%s %d/%d>" % (self.__class__.__name__, self.index, self.length)
+ return f"<{self.__class__.__name__} {self.index}/{self.length}>"
-class Macro(object):
+class Macro:
"""Wraps a macro function."""
def __init__(
@@ -646,20 +640,18 @@ class Macro(object):
elif kwargs:
if "caller" in kwargs:
raise TypeError(
- "macro %r was invoked with two values for "
- "the special caller argument. This is "
- "most likely a bug." % self.name
+ f"macro {self.name!r} was invoked with two values for the special"
+ " caller argument. This is most likely a bug."
)
raise TypeError(
- "macro %r takes no keyword argument %r"
- % (self.name, next(iter(kwargs)))
+ f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}"
)
if self.catch_varargs:
arguments.append(args[self._argument_count :])
elif len(args) > self._argument_count:
raise TypeError(
- "macro %r takes not more than %d argument(s)"
- % (self.name, len(self.arguments))
+ f"macro {self.name!r} takes not more than"
+ f" {len(self.arguments)} argument(s)"
)
return self._invoke(arguments, autoescape)
@@ -672,13 +664,11 @@ class Macro(object):
return rv
def __repr__(self):
- return "<%s %s>" % (
- self.__class__.__name__,
- self.name is None and "anonymous" or repr(self.name),
- )
+ name = "anonymous" if self.name is None else repr(self.name)
+ return f"<{self.__class__.__name__} {name}>"
-class Undefined(object):
+class Undefined:
"""The default undefined type. This undefined type can be printed and
iterated over, but every other access will raise an :exc:`UndefinedError`:
@@ -715,17 +705,17 @@ class Undefined(object):
return self._undefined_hint
if self._undefined_obj is missing:
- return "%r is undefined" % self._undefined_name
+ return f"{self._undefined_name!r} is undefined"
if not isinstance(self._undefined_name, str):
- return "%s has no element %r" % (
- object_type_repr(self._undefined_obj),
- self._undefined_name,
+ return (
+ f"{object_type_repr(self._undefined_obj)} has no"
+ f" element {self._undefined_name!r}"
)
- return "%r has no attribute %r" % (
- object_type_repr(self._undefined_obj),
- self._undefined_name,
+ return (
+ f"{object_type_repr(self._undefined_obj)!r} has no"
+ f" attribute {self._undefined_name!r}"
)
@internalcode
@@ -762,7 +752,7 @@ class Undefined(object):
return id(type(self))
def __str__(self):
- return u""
+ return ""
def __len__(self):
return 0
@@ -807,45 +797,27 @@ def make_logging_undefined(logger=None, base=None):
base = Undefined
def _log_message(undef):
- 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, str):
- hint = "%s has no element %s" % (
- object_type_repr(undef._undefined_obj),
- undef._undefined_name,
- )
- else:
- hint = "%s has no attribute %s" % (
- object_type_repr(undef._undefined_obj),
- undef._undefined_name,
- )
- else:
- hint = undef._undefined_hint
- logger.warning("Template variable warning: %s", hint)
+ logger.warning("Template variable warning: %s", undef._undefined_message)
class LoggingUndefined(base):
def _fail_with_undefined_error(self, *args, **kwargs):
try:
- return base._fail_with_undefined_error(self, *args, **kwargs)
+ return super()._fail_with_undefined_error(*args, **kwargs)
except self._undefined_exception as e:
- logger.error("Template variable error: %s", str(e))
+ logger.error(f"Template variable error: %s", e)
raise e
def __str__(self):
- rv = base.__str__(self)
_log_message(self)
- return rv
+ return super().__str__()
def __iter__(self):
- rv = base.__iter__(self)
_log_message(self)
- return rv
+ return super().__iter__()
def __bool__(self):
- rv = base.__bool__(self)
_log_message(self)
- return rv
+ return super().__bool__()
return LoggingUndefined
@@ -894,14 +866,19 @@ class DebugUndefined(Undefined):
__slots__ = ()
def __str__(self):
- if self._undefined_hint is None:
- if self._undefined_obj is missing:
- return u"{{ %s }}" % self._undefined_name
- return "{{ no such element: %s[%r] }}" % (
- object_type_repr(self._undefined_obj),
- self._undefined_name,
+ if self._undefined_hint:
+ message = f"undefined value printed: {self._undefined_hint}"
+
+ elif self._undefined_obj is missing:
+ message = self._undefined_name
+
+ else:
+ message = (
+ f"no such element: {object_type_repr(self._undefined_obj)}"
+ f"[{self._undefined_name!r}]"
)
- return u"{{ undefined value printed: %s }}" % self._undefined_hint
+
+ return f"{{{{ {message} }}}}"
class StrictUndefined(Undefined):
diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py
index 1263c77..deecf61 100644
--- a/src/jinja2/sandbox.py
+++ b/src/jinja2/sandbox.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""A sandbox layer that ensures unsafe operations cannot be performed.
Useful when the template itself comes from an untrusted source.
"""
@@ -126,7 +125,7 @@ def safe_range(*args):
if len(rng) > MAX_RANGE:
raise OverflowError(
"Range too big. The sandbox blocks ranges larger than"
- " MAX_RANGE (%d)." % MAX_RANGE
+ f" MAX_RANGE ({MAX_RANGE})."
)
return rng
@@ -135,7 +134,7 @@ def safe_range(*args):
def unsafe(f):
"""Marks a function or method as unsafe.
- ::
+ .. code-block: python
@unsafe
def delete(self):
@@ -370,8 +369,8 @@ class SandboxedEnvironment(Environment):
def unsafe_undefined(self, obj, attribute):
"""Return an undefined object for unsafe attributes."""
return self.undefined(
- "access to attribute %r of %r "
- "object is unsafe." % (attribute, obj.__class__.__name__),
+ f"access to attribute {attribute!r} of"
+ f" {obj.__class__.__name__!r} object is unsafe.",
name=attribute,
obj=obj,
exc=SecurityError,
@@ -389,8 +388,8 @@ class SandboxedEnvironment(Environment):
if format_func is not None and format_func.__name__ == "format_map":
if len(args) != 1 or kwargs:
raise TypeError(
- "format_map() takes exactly one argument %d given"
- % (len(args) + (kwargs is not None))
+ "format_map() takes exactly one argument"
+ f" {len(args) + (kwargs is not None)} given"
)
kwargs = args[0]
@@ -409,7 +408,7 @@ class SandboxedEnvironment(Environment):
# the double prefixes are to avoid double keyword argument
# errors when proxying the call.
if not __self.is_safe_callable(__obj):
- raise SecurityError("%r is not safely callable" % (__obj,))
+ raise SecurityError(f"{__obj!r} is not safely callable")
return __context.call(__obj, *args, **kwargs)
@@ -425,7 +424,7 @@ class ImmutableSandboxedEnvironment(SandboxedEnvironment):
return not modifies_known_mutable(obj, attr)
-class SandboxedFormatterMixin(object):
+class SandboxedFormatterMixin:
def __init__(self, env):
self._env = env
diff --git a/src/jinja2/tests.py b/src/jinja2/tests.py
index 6a24d9c..bc76326 100644
--- a/src/jinja2/tests.py
+++ b/src/jinja2/tests.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Built-in template tests used with the ``is`` operator."""
import operator
import re
diff --git a/src/jinja2/utils.py b/src/jinja2/utils.py
index dfc518f..99293cd 100644
--- a/src/jinja2/utils.py
+++ b/src/jinja2/utils.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import json
import os
import re
@@ -13,12 +12,10 @@ from markupsafe import escape
from markupsafe import Markup
_word_split_re = re.compile(r"(\s+)")
+_lead_pattern = "|".join(map(re.escape, ("(", "<", "&lt;")))
+_trail_pattern = "|".join(map(re.escape, (".", ",", ")", ">", "\n", "&gt;")))
_punctuation_re = re.compile(
- "^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$"
- % (
- "|".join(map(re.escape, ("(", "<", "&lt;"))),
- "|".join(map(re.escape, (".", ",", ")", ">", "\n", "&gt;"))),
- )
+ fr"^(?P<lead>(?:{_lead_pattern})*)(?P<middle>.*?)(?P<trail>(?:{_trail_pattern})*)$"
)
_simple_email_re = re.compile(r"^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$")
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
@@ -32,7 +29,7 @@ missing = type("MissingType", (), {"__repr__": lambda x: "missing"})()
# internal code
internal_code = set()
-concat = u"".join
+concat = "".join
_slash_escape = "\\/" not in json.dumps("/")
@@ -165,8 +162,8 @@ def object_type_repr(obj):
if obj.__class__.__module__ in ("__builtin__", "builtins"):
name = obj.__class__.__name__
else:
- name = obj.__class__.__module__ + "." + obj.__class__.__name__
- return "%s object" % name
+ name = f"{obj.__class__.__module__}.{obj.__class__.__name__}"
+ return f"{name} object"
def pformat(obj):
@@ -191,14 +188,16 @@ def urlize(text, trim_url_limit=None, rel=None, target=None):
If target is not None, a target attribute will be added to the link.
"""
- trim_url = (
- lambda x, limit=trim_url_limit: limit is not None
- and (x[:limit] + (len(x) >= limit and "..." or ""))
- or x
- )
+
+ def trim_url(x, limit=trim_url_limit):
+ if limit is not None:
+ return x[:limit] + ("..." if len(x) >= limit else "")
+
+ return x
+
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 ""
+ rel_attr = f' rel="{escape(rel)}"' if rel else ""
+ target_attr = f' target="{escape(target)}"' if target else ""
for i, word in enumerate(words):
match = _punctuation_re.match(word)
@@ -216,18 +215,13 @@ def urlize(text, trim_url_limit=None, rel=None, target=None):
or middle.endswith(".com")
)
):
- middle = '<a href="http://%s"%s%s>%s</a>' % (
- middle,
- rel_attr,
- target_attr,
- trim_url(middle),
+ middle = (
+ f'<a href="http://{middle}"{rel_attr}{target_attr}>'
+ f"{trim_url(middle)}</a>"
)
if middle.startswith("http://") or middle.startswith("https://"):
- middle = '<a href="%s"%s%s>%s</a>' % (
- middle,
- rel_attr,
- target_attr,
- trim_url(middle),
+ middle = (
+ f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
)
if (
"@" in middle
@@ -235,10 +229,10 @@ def urlize(text, trim_url_limit=None, rel=None, target=None):
and ":" not in middle
and _simple_email_re.match(middle)
):
- middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
+ middle = f'<a href="mailto:{middle}">{middle}</a>'
if lead + middle + trail != word:
words[i] = lead + middle + trail
- return u"".join(words)
+ return "".join(words)
def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
@@ -278,7 +272,7 @@ def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
p.append(word)
# ensure that the paragraph ends with a dot.
- p = u" ".join(p)
+ p = " ".join(p)
if p.endswith(","):
p = p[:-1] + "."
elif not p.endswith("."):
@@ -286,8 +280,8 @@ def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
result.append(p)
if not html:
- return u"\n\n".join(result)
- return Markup(u"\n".join(u"<p>%s</p>" % escape(x) for x in result))
+ return "\n\n".join(result)
+ return Markup("\n".join(f"<p>{escape(x)}</p>" for x in result))
def url_quote(obj, charset="utf-8", for_qs=False):
@@ -329,7 +323,7 @@ def unicode_urlencode(obj, charset="utf-8", for_qs=False):
@abc.MutableMapping.register
-class LRUCache(object):
+class LRUCache:
"""A simple LRU Cache implementation."""
# this is fast for small capacities (something below 1000) but doesn't
@@ -406,7 +400,7 @@ class LRUCache(object):
return len(self._mapping)
def __repr__(self):
- return "<%s %r>" % (self.__class__.__name__, self._mapping)
+ return f"<{self.__class__.__name__} {self._mapping!r}>"
def __getitem__(self, key):
"""Get an item from the cache. Moves the item up so that it has the
@@ -525,8 +519,8 @@ def select_autoescape(
.. versionadded:: 2.9
"""
- enabled_patterns = tuple("." + x.lstrip(".").lower() for x in enabled_extensions)
- disabled_patterns = tuple("." + x.lstrip(".").lower() for x in disabled_extensions)
+ enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
+ disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
def autoescape(template_name):
if template_name is None:
@@ -563,15 +557,15 @@ def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
dumper = json.dumps
rv = (
dumper(obj, **kwargs)
- .replace(u"<", u"\\u003c")
- .replace(u">", u"\\u003e")
- .replace(u"&", u"\\u0026")
- .replace(u"'", u"\\u0027")
+ .replace("<", "\\u003c")
+ .replace(">", "\\u003e")
+ .replace("&", "\\u0026")
+ .replace("'", "\\u0027")
)
return Markup(rv)
-class Cycler(object):
+class Cycler:
"""Cycle through values by yield them one at a time, then restarting
once the end is reached. Available as ``cycler`` in templates.
@@ -625,21 +619,21 @@ class Cycler(object):
__next__ = next
-class Joiner(object):
+class Joiner:
"""A joining helper for templates."""
- def __init__(self, sep=u", "):
+ def __init__(self, sep=", "):
self.sep = sep
self.used = False
def __call__(self):
if not self.used:
self.used = True
- return u""
+ return ""
return self.sep
-class Namespace(object):
+class Namespace:
"""A namespace object that can hold arbitrary attributes. It may be
initialized from a dictionary or with keyword arguments."""
@@ -659,7 +653,7 @@ class Namespace(object):
self.__attrs[name] = value
def __repr__(self):
- return "<Namespace %r>" % self.__attrs
+ return f"<Namespace {self.__attrs!r}>"
# does this python version support async for in and async generators?
diff --git a/src/jinja2/visitor.py b/src/jinja2/visitor.py
index d1365bf..590fa9e 100644
--- a/src/jinja2/visitor.py
+++ b/src/jinja2/visitor.py
@@ -1,11 +1,10 @@
-# -*- coding: utf-8 -*-
"""API for traversing the AST nodes. Implemented by the compiler and
meta introspection.
"""
from .nodes import Node
-class NodeVisitor(object):
+class NodeVisitor:
"""Walks the abstract syntax tree and call visitor functions for every
node found. The visitor functions may return values which will be
forwarded by the `visit` method.
@@ -22,8 +21,7 @@ class NodeVisitor(object):
exists for this node. In that case the generic visit function is
used instead.
"""
- method = "visit_" + node.__class__.__name__
- return getattr(self, method, None)
+ return getattr(self, f"visit_{node.__class__.__name__}", None)
def visit(self, node, *args, **kwargs):
"""Visit a node."""
diff --git a/tests/conftest.py b/tests/conftest.py
index bb26409..794e933 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import pytest
diff --git a/tests/test_api.py b/tests/test_api.py
index 76e0534..387fb3f 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
import tempfile
@@ -27,7 +26,7 @@ from jinja2.utils import evalcontextfunction
@pytest.mark.api
@pytest.mark.extended
-class TestExtendedAPI(object):
+class TestExtendedAPI:
def test_item_and_attribute(self, env):
from jinja2.sandbox import SandboxedEnvironment
@@ -164,11 +163,11 @@ class TestExtendedAPI(object):
@pytest.mark.api
@pytest.mark.meta
-class TestMeta(object):
+class TestMeta:
def test_find_undeclared_variables(self, env):
ast = env.parse("{% set foo = 42 %}{{ bar + foo }}")
x = meta.find_undeclared_variables(ast)
- assert x == set(["bar"])
+ assert x == {"bar"}
ast = env.parse(
"{% set foo = 42 %}{{ bar + foo }}"
@@ -177,11 +176,11 @@ class TestMeta(object):
"{% endfor %}"
)
x = meta.find_undeclared_variables(ast)
- assert x == set(["bar", "seq", "muh"])
+ assert x == {"bar", "seq", "muh"}
ast = env.parse("{% for x in range(5) %}{{ x }}{% endfor %}{{ foo }}")
x = meta.find_undeclared_variables(ast)
- assert x == set(["foo"])
+ assert x == {"foo"}
def test_find_refererenced_templates(self, env):
ast = env.parse('{% extends "layout.html" %}{% include helper %}')
@@ -219,7 +218,7 @@ class TestMeta(object):
@pytest.mark.api
@pytest.mark.streaming
-class TestStreaming(object):
+class TestStreaming:
def test_basic_streaming(self, env):
t = env.from_string(
"<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
@@ -236,8 +235,8 @@ class TestStreaming(object):
)
stream = tmpl.stream(seq=list(range(3)))
stream.enable_buffering(size=3)
- assert next(stream) == u"<ul><li>1"
- assert next(stream) == u" - 0</li>"
+ assert next(stream) == "<ul><li>1"
+ assert next(stream) == " - 0</li>"
def test_streaming_behavior(self, env):
tmpl = env.from_string("")
@@ -251,7 +250,7 @@ class TestStreaming(object):
def test_dump_stream(self, env):
tmp = tempfile.mkdtemp()
try:
- tmpl = env.from_string(u"\u2713")
+ tmpl = env.from_string("\u2713")
stream = tmpl.stream()
stream.dump(os.path.join(tmp, "dump.txt"), "utf-8")
with open(os.path.join(tmp, "dump.txt"), "rb") as f:
@@ -262,7 +261,7 @@ class TestStreaming(object):
@pytest.mark.api
@pytest.mark.undefined
-class TestUndefined(object):
+class TestUndefined:
def test_stopiteration_is_undefined(self):
def test():
raise StopIteration()
@@ -279,7 +278,7 @@ class TestUndefined(object):
def test_logging_undefined(self):
_messages = []
- class DebugLogger(object):
+ class DebugLogger:
def warning(self, msg, *args):
_messages.append("W:" + msg % args)
@@ -288,23 +287,23 @@ class TestUndefined(object):
logging_undefined = make_logging_undefined(DebugLogger())
env = Environment(undefined=logging_undefined)
- assert env.from_string("{{ missing }}").render() == u""
+ assert env.from_string("{{ missing }}").render() == ""
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
assert env.from_string("{{ missing|list }}").render() == "[]"
assert env.from_string("{{ missing is not defined }}").render() == "True"
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
assert env.from_string("{{ not missing }}").render() == "True"
assert _messages == [
- "W:Template variable warning: missing is undefined",
+ "W:Template variable warning: 'missing' is undefined",
"E:Template variable error: 'missing' is undefined",
- "W:Template variable warning: missing is undefined",
- "W:Template variable warning: int object has no attribute missing",
- "W:Template variable warning: missing is undefined",
+ "W:Template variable warning: 'missing' is undefined",
+ "W:Template variable warning: 'int object' has no attribute 'missing'",
+ "W:Template variable warning: 'missing' is undefined",
]
def test_default_undefined(self):
env = Environment(undefined=Undefined)
- assert env.from_string("{{ missing }}").render() == u""
+ assert env.from_string("{{ missing }}").render() == ""
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
assert env.from_string("{{ missing|list }}").render() == "[]"
assert env.from_string("{{ missing is not defined }}").render() == "True"
@@ -322,7 +321,7 @@ class TestUndefined(object):
def test_chainable_undefined(self):
env = Environment(undefined=ChainableUndefined)
# The following tests are copied from test_default_undefined
- assert env.from_string("{{ missing }}").render() == u""
+ assert env.from_string("{{ missing }}").render() == ""
assert env.from_string("{{ missing|list }}").render() == "[]"
assert env.from_string("{{ missing is not defined }}").render() == "True"
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
@@ -332,19 +331,17 @@ class TestUndefined(object):
getattr(ChainableUndefined, "__slots__") # noqa: B009
# The following tests ensure subclass functionality works as expected
- assert env.from_string('{{ missing.bar["baz"] }}').render() == u""
- assert (
- env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() == u"foo"
- )
+ assert env.from_string('{{ missing.bar["baz"] }}').render() == ""
+ assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() == "foo"
assert (
env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(foo=42)
- == u"bar"
+ == "bar"
)
assert (
env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(
foo={"bar": 42}
)
- == u"baz"
+ == "baz"
)
def test_debug_undefined(self):
@@ -355,13 +352,13 @@ class TestUndefined(object):
assert env.from_string("{{ missing is not defined }}").render() == "True"
assert (
env.from_string("{{ foo.missing }}").render(foo=42)
- == u"{{ no such element: int object['missing'] }}"
+ == "{{ no such element: int object['missing'] }}"
)
assert env.from_string("{{ not missing }}").render() == "True"
undefined_hint = "this is testing undefined hint of DebugUndefined"
assert (
str(DebugUndefined(hint=undefined_hint))
- == u"{{ undefined value printed: %s }}" % undefined_hint
+ == f"{{{{ undefined value printed: {undefined_hint} }}}}"
)
with pytest.raises(AttributeError):
getattr(DebugUndefined, "__slots__") # noqa: B009
@@ -401,7 +398,7 @@ class TestUndefined(object):
@pytest.mark.api
@pytest.mark.lowlevel
-class TestLowLevel(object):
+class TestLowLevel:
def test_custom_code_generator(self):
class CustomCodeGenerator(CodeGenerator):
def visit_Const(self, node, frame=None):
@@ -409,7 +406,7 @@ class TestLowLevel(object):
if node.value == "foo":
self.write(repr("bar"))
else:
- super(CustomCodeGenerator, self).visit_Const(node, frame)
+ super().visit_Const(node, frame)
class CustomEnvironment(Environment):
code_generator_class = CustomCodeGenerator
diff --git a/tests/test_async.py b/tests/test_async.py
index f7e2792..a6a7727 100644
--- a/tests/test_async.py
+++ b/tests/test_async.py
@@ -131,7 +131,7 @@ def test_env_async():
@pytest.mark.imports
-class TestAsyncImports(object):
+class TestAsyncImports:
def test_context_imports(self, test_env_async):
t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}')
assert t.render(foo=42) == "[|23]"
@@ -182,7 +182,7 @@ class TestAsyncImports(object):
@pytest.mark.imports
@pytest.mark.includes
-class TestAsyncIncludes(object):
+class TestAsyncIncludes:
def test_context_include(self, test_env_async):
t = test_env_async.from_string('{% include "header" %}')
assert t.render(foo=42) == "[42|23]"
@@ -281,7 +281,7 @@ class TestAsyncIncludes(object):
@pytest.mark.core_tags
@pytest.mark.for_loop
-class TestAsyncForLoop(object):
+class TestAsyncForLoop:
def test_simple(self, test_env_async):
tmpl = test_env_async.from_string("{% for item in seq %}{{ item }}{% endfor %}")
assert tmpl.render(seq=list(range(10))) == "0123456789"
@@ -345,8 +345,7 @@ class TestAsyncForLoop(object):
def test_varlen(self, test_env_async):
def inner():
- for item in range(5):
- yield item
+ yield from range(5)
tmpl = test_env_async.from_string(
"{% for item in iter %}{{ item }}{% endfor %}"
diff --git a/tests/test_bytecode_cache.py b/tests/test_bytecode_cache.py
index 2970cb5..8979d15 100644
--- a/tests/test_bytecode_cache.py
+++ b/tests/test_bytecode_cache.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from jinja2 import Environment
@@ -15,14 +14,14 @@ def env(package_loader, tmp_path):
@pytest.mark.byte_code_cache
-class TestByteCodeCache(object):
+class TestByteCodeCache:
def test_simple(self, env):
tmpl = env.get_template("test.html")
assert tmpl.render().strip() == "BAR"
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
-class MockMemcached(object):
+class MockMemcached:
class Error(Exception):
pass
@@ -45,7 +44,7 @@ class MockMemcached(object):
raise self.Error()
-class TestMemcachedBytecodeCache(object):
+class TestMemcachedBytecodeCache:
def test_dump_load(self):
memcached = MockMemcached()
m = MemcachedBytecodeCache(memcached)
diff --git a/tests/test_core_tags.py b/tests/test_core_tags.py
index 4132c4f..df41348 100644
--- a/tests/test_core_tags.py
+++ b/tests/test_core_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from jinja2 import DictLoader
@@ -15,7 +14,7 @@ def env_trim():
@pytest.mark.core_tags
@pytest.mark.for_loop
-class TestForLoop(object):
+class TestForLoop:
def test_simple(self, env):
tmpl = env.from_string("{% for item in seq %}{{ item }}{% endfor %}")
assert tmpl.render(seq=list(range(10))) == "0123456789"
@@ -103,12 +102,8 @@ class TestForLoop(object):
assert not output
def test_varlen(self, env):
- def inner():
- for item in range(5):
- yield item
-
tmpl = env.from_string("{% for item in iter %}{{ item }}{% endfor %}")
- output = tmpl.render(iter=inner())
+ output = tmpl.render(iter=range(5))
assert output == "01234"
def test_noniter(self, env):
@@ -307,7 +302,7 @@ class TestForLoop(object):
@pytest.mark.core_tags
@pytest.mark.if_condition
-class TestIfCondition(object):
+class TestIfCondition:
def test_simple(self, env):
tmpl = env.from_string("""{% if true %}...{% endif %}""")
assert tmpl.render() == "..."
@@ -320,10 +315,8 @@ class TestIfCondition(object):
assert tmpl.render() == "..."
def test_elif_deep(self, env):
- elifs = "\n".join("{{% elif a == {0} %}}{0}".format(i) for i in range(1, 1000))
- tmpl = env.from_string(
- "{{% if a == 0 %}}0{0}{{% else %}}x{{% endif %}}".format(elifs)
- )
+ elifs = "\n".join(f"{{% elif a == {i} %}}{i}" for i in range(1, 1000))
+ tmpl = env.from_string(f"{{% if a == 0 %}}0{elifs}{{% else %}}x{{% endif %}}")
for x in (0, 10, 999):
assert tmpl.render(a=x).strip() == str(x)
assert tmpl.render(a=1000).strip() == "x"
@@ -351,7 +344,7 @@ class TestIfCondition(object):
@pytest.mark.core_tags
@pytest.mark.macros
-class TestMacros(object):
+class TestMacros:
def test_simple(self, env_trim):
tmpl = env_trim.from_string(
"""\
@@ -477,7 +470,7 @@ class TestMacros(object):
@pytest.mark.core_tags
@pytest.mark.set
-class TestSet(object):
+class TestSet:
def test_normal(self, env_trim):
tmpl = env_trim.from_string("{% set foo = 1 %}{{ foo }}")
assert tmpl.render() == "1"
@@ -486,7 +479,7 @@ class TestSet(object):
def test_block(self, env_trim):
tmpl = env_trim.from_string("{% set foo %}42{% endset %}{{ foo }}")
assert tmpl.render() == "42"
- assert tmpl.module.foo == u"42"
+ assert tmpl.module.foo == "42"
def test_block_escaping(self):
env = Environment(autoescape=True)
@@ -565,7 +558,7 @@ class TestSet(object):
"{% set foo | trim | length | string %} 42 {% endset %}{{ foo }}"
)
assert tmpl.render() == "2"
- assert tmpl.module.foo == u"2"
+ assert tmpl.module.foo == "2"
def test_block_filtered_set(self, env_trim):
def _myfilter(val, arg):
@@ -581,12 +574,12 @@ class TestSet(object):
"{{ foo }}"
)
assert tmpl.render() == "11"
- assert tmpl.module.foo == u"11"
+ assert tmpl.module.foo == "11"
@pytest.mark.core_tags
@pytest.mark.with_
-class TestWith(object):
+class TestWith:
def test_with(self, env):
tmpl = env.from_string(
"""\
diff --git a/tests/test_debug.py b/tests/test_debug.py
index 5ca92d9..d3c3ac5 100644
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pickle
import re
from traceback import format_exception
@@ -18,17 +17,16 @@ def fs_env(filesystem_loader):
@pytest.mark.debug
-class TestDebug(object):
+class TestDebug:
def assert_traceback_matches(self, callback, expected_tb):
with pytest.raises(Exception) as exc_info:
callback()
tb = format_exception(exc_info.type, exc_info.value, exc_info.tb)
m = re.search(expected_tb.strip(), "".join(tb))
- assert m is not None, "Traceback did not match:\n\n%s\nexpected:\n%s" % (
- "".join(tb),
- expected_tb,
- )
+ assert (
+ m is not None
+ ), "Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}"
def test_runtime_error(self, fs_env):
def test():
diff --git a/tests/test_ext.py b/tests/test_ext.py
index 03e94e7..00656b3 100644
--- a/tests/test_ext.py
+++ b/tests/test_ext.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import re
from io import BytesIO
@@ -52,14 +51,14 @@ newstyle_i18n_templates = {
languages = {
"de": {
- "missing": u"fehlend",
- "watch out": u"pass auf",
- "One user online": u"Ein Benutzer online",
- "%(user_count)s users online": u"%(user_count)s Benutzer online",
- "User: %(num)s": u"Benutzer: %(num)s",
- "User: %(count)s": u"Benutzer: %(count)s",
- "%(num)s apple": u"%(num)s Apfel",
- "%(num)s apples": u"%(num)s Äpfel",
+ "missing": "fehlend",
+ "watch out": "pass auf",
+ "One user online": "Ein Benutzer online",
+ "%(user_count)s users online": "%(user_count)s Benutzer online",
+ "User: %(num)s": "Benutzer: %(num)s",
+ "User: %(count)s": "Benutzer: %(count)s",
+ "%(num)s apple": "%(num)s Apfel",
+ "%(num)s apples": "%(num)s Äpfel",
}
}
@@ -95,7 +94,7 @@ newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
class ExampleExtension(Extension):
- tags = set(["test"])
+ tags = {"test"}
ext_attr = 42
context_reference_node_cls = nodes.ContextReference
@@ -115,12 +114,9 @@ class ExampleExtension(Extension):
).set_lineno(next(parser.stream).lineno)
def _dump(self, sandboxed, ext_attr, imported_object, context):
- return "%s|%s|%s|%s|%s" % (
- sandboxed,
- ext_attr,
- imported_object,
- context.blocks,
- context.get("test_var"),
+ return (
+ f"{sandboxed}|{ext_attr}|{imported_object}|{context.blocks}"
+ f"|{context.get('test_var')}"
)
@@ -137,8 +133,7 @@ class StreamFilterExtension(Extension):
def filter_stream(self, stream):
for token in stream:
if token.type == "data":
- for t in self.interpolate(token):
- yield t
+ yield from self.interpolate(token)
else:
yield token
@@ -166,7 +161,7 @@ class StreamFilterExtension(Extension):
@pytest.mark.ext
-class TestExtensions(object):
+class TestExtensions:
def test_extend_late(self):
env = Environment()
env.add_extension("jinja2.ext.autoescape")
@@ -262,11 +257,11 @@ class TestExtensions(object):
out = t.render()
for value in ("context", "cycler", "filters", "abs", "tests", "!="):
- assert "'{}'".format(value) in out
+ assert f"'{value}'" in out
@pytest.mark.ext
-class TestInternationalization(object):
+class TestInternationalization:
def test_trans(self):
tmpl = i18n_env.get_template("child.html")
assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
@@ -347,9 +342,9 @@ class TestInternationalization(object):
"""
)
assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
- (2, "gettext", u"Hello World", []),
- (3, "gettext", u"Hello World", []),
- (4, "ngettext", (u"%(users)s user", u"%(users)s users", None), []),
+ (2, "gettext", "Hello World", []),
+ (3, "gettext", "Hello World", []),
+ (4, "ngettext", ("%(users)s user", "%(users)s users", None), []),
]
def test_extract_trimmed(self):
@@ -364,9 +359,9 @@ class TestInternationalization(object):
"""
)
assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
- (2, "gettext", u" Hello \n World", []),
- (4, "gettext", u"Hello World", []),
- (6, "ngettext", (u"%(users)s user", u"%(users)s users", None), []),
+ (2, "gettext", " Hello \n World", []),
+ (4, "gettext", "Hello World", []),
+ (6, "ngettext", ("%(users)s user", "%(users)s users", None), []),
]
def test_extract_trimmed_option(self):
@@ -382,9 +377,9 @@ class TestInternationalization(object):
)
opts = {"trimmed": "true"}
assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], opts)) == [
- (2, "gettext", u" Hello \n World", []),
- (4, "gettext", u"Hello World", []),
- (6, "ngettext", (u"%(users)s user", u"%(users)s users", None), []),
+ (2, "gettext", " Hello \n World", []),
+ (4, "gettext", "Hello World", []),
+ (6, "ngettext", ("%(users)s user", "%(users)s users", None), []),
]
def test_comment_extract(self):
@@ -402,18 +397,18 @@ class TestInternationalization(object):
assert list(
babel_extract(source, ("gettext", "ngettext", "_"), ["trans", ":"], {})
) == [
- (3, "gettext", u"Hello World", ["first"]),
- (4, "gettext", u"Hello World", ["second"]),
- (6, "ngettext", (u"%(users)s user", u"%(users)s users", None), ["third"]),
+ (3, "gettext", "Hello World", ["first"]),
+ (4, "gettext", "Hello World", ["second"]),
+ (6, "ngettext", ("%(users)s user", "%(users)s users", None), ["third"]),
]
@pytest.mark.ext
-class TestScope(object):
+class TestScope:
def test_basic_scope_behavior(self):
# This is what the old with statement compiled down to
class ScopeExt(Extension):
- tags = set(["scope"])
+ tags = {"scope"}
def parse(self, parser):
node = nodes.Scope(lineno=next(parser.stream).lineno)
@@ -443,7 +438,7 @@ class TestScope(object):
@pytest.mark.ext
-class TestNewstyleInternationalization(object):
+class TestNewstyleInternationalization:
def test_trans(self):
tmpl = newstyle_i18n_env.get_template("child.html")
assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
@@ -472,12 +467,12 @@ class TestNewstyleInternationalization(object):
def test_newstyle_plural(self):
tmpl = newstyle_i18n_env.get_template("ngettext.html")
assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apfel"
- assert tmpl.render(LANGUAGE="de", apples=5) == u"5 Äpfel"
+ assert tmpl.render(LANGUAGE="de", apples=5) == "5 Äpfel"
def test_autoescape_support(self):
env = Environment(extensions=["jinja2.ext.autoescape", "jinja2.ext.i18n"])
env.install_gettext_callables(
- lambda x: u"<strong>Wert: %(name)s</strong>",
+ lambda x: "<strong>Wert: %(name)s</strong>",
lambda s, p, n: s,
newstyle=True,
)
@@ -498,7 +493,7 @@ class TestNewstyleInternationalization(object):
def test_num_used_twice(self):
tmpl = newstyle_i18n_env.get_template("ngettext_long.html")
- assert tmpl.render(apples=5, LANGUAGE="de") == u"5 Äpfel"
+ assert tmpl.render(apples=5, LANGUAGE="de") == "5 Äpfel"
def test_num_called_num(self):
source = newstyle_i18n_env.compile(
@@ -513,7 +508,7 @@ class TestNewstyleInternationalization(object):
# would work) for better performance. This only works on the
# newstyle gettext of course
assert (
- re.search(r"u?'\%\(num\)s apple', u?'\%\(num\)s " r"apples', 3", source)
+ re.search(r"u?'%\(num\)s apple', u?'%\(num\)s apples', 3", source)
is not None
)
@@ -535,7 +530,7 @@ class TestNewstyleInternationalization(object):
@pytest.mark.ext
-class TestAutoEscape(object):
+class TestAutoEscape:
def test_scoped_setting(self):
env = Environment(extensions=["jinja2.ext.autoescape"], autoescape=True)
tmpl = env.from_string(
@@ -548,9 +543,9 @@ class TestAutoEscape(object):
"""
)
assert tmpl.render().split() == [
- u"&lt;HelloWorld&gt;",
- u"<HelloWorld>",
- u"&lt;HelloWorld&gt;",
+ "&lt;HelloWorld&gt;",
+ "<HelloWorld>",
+ "&lt;HelloWorld&gt;",
]
env = Environment(extensions=["jinja2.ext.autoescape"], autoescape=False)
@@ -564,9 +559,9 @@ class TestAutoEscape(object):
"""
)
assert tmpl.render().split() == [
- u"<HelloWorld>",
- u"&lt;HelloWorld&gt;",
- u"<HelloWorld>",
+ "<HelloWorld>",
+ "&lt;HelloWorld&gt;",
+ "<HelloWorld>",
]
def test_nonvolatile(self):
@@ -623,7 +618,7 @@ class TestAutoEscape(object):
def test_overlay_scopes(self):
class MagicScopeExtension(Extension):
- tags = set(["overlay"])
+ tags = {"overlay"}
def parse(self, parser):
node = nodes.OverlayScope(lineno=next(parser.stream).lineno)
diff --git a/tests/test_features.py b/tests/test_features.py
index c7c43e8..4f36458 100644
--- a/tests/test_features.py
+++ b/tests/test_features.py
@@ -3,8 +3,9 @@ import pytest
from jinja2 import Template
+# Python < 3.7
def test_generator_stop():
- class X(object):
+ class X:
def __getattr__(self, name):
raise StopIteration()
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 03db039..bda5821 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import random
from collections import namedtuple
@@ -8,7 +7,7 @@ from jinja2 import Environment
from jinja2 import Markup
-class Magic(object):
+class Magic:
def __init__(self, value):
self.value = value
@@ -16,17 +15,17 @@ class Magic(object):
return str(self.value)
-class Magic2(object):
+class Magic2:
def __init__(self, value1, value2):
self.value1 = value1
self.value2 = value2
def __str__(self):
- return u"(%s,%s)" % (str(self.value1), str(self.value2))
+ return f"({self.value1},{self.value2})"
@pytest.mark.filter
-class TestFilter(object):
+class TestFilter:
def test_filter_calling(self, env):
rv = env.call_filter("sum", [1, 2, 3])
assert rv == 6
@@ -56,7 +55,7 @@ class TestFilter(object):
),
)
def test_dictsort(self, env, args, expect):
- t = env.from_string("{{{{ foo|dictsort({args}) }}}}".format(args=args))
+ t = env.from_string(f"{{{{ foo|dictsort({args}) }}}}")
out = t.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
assert out == expect
@@ -139,18 +138,18 @@ class TestFilter(object):
assert out == "0"
@pytest.mark.parametrize(
- ("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32"),)
+ ("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32"))
)
def test_float(self, env, value, expect):
- t = env.from_string("{{ '%s'|float }}" % value)
- assert t.render() == expect
+ t = env.from_string("{{ value|float }}")
+ assert t.render(value=value) == expect
def test_float_default(self, env):
t = env.from_string("{{ value|float(default=1.0) }}")
assert t.render(value="abc") == "1.0"
def test_format(self, env):
- tmpl = env.from_string("""{{ "%s|%s"|format("a", "b") }}""")
+ tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}")
out = tmpl.render()
assert out == "a|b"
@@ -193,23 +192,23 @@ class TestFilter(object):
),
)
def test_int(self, env, value, expect):
- t = env.from_string("{{ '%s'|int }}" % value)
- assert t.render() == expect
+ t = env.from_string("{{ value|int }}")
+ assert t.render(value=value) == expect
@pytest.mark.parametrize(
("value", "base", "expect"),
(("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0"),),
)
def test_int_base(self, env, value, base, expect):
- t = env.from_string("{{ '%s'|int(base=%d) }}" % (value, base))
- assert t.render() == expect
+ t = env.from_string("{{ value|int(base=base) }}")
+ assert t.render(value=value, base=base) == expect
def test_int_default(self, env):
t = env.from_string("{{ value|int(default=1) }}")
assert t.render(value="abc") == "1"
def test_int_special_method(self, env):
- class IntIsh(object):
+ class IntIsh:
def __int__(self):
return 42
@@ -318,20 +317,19 @@ class TestFilter(object):
"{{ smalldata|truncate(15) }}"
)
out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar")
- msg = "Current output: %s" % out
- assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar", msg
+ assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar"
def test_truncate_very_short(self, env):
tmpl = env.from_string(
'{{ "foo bar baz"|truncate(9) }}|{{ "foo bar baz"|truncate(9, true) }}'
)
out = tmpl.render()
- assert out == "foo bar baz|foo bar baz", out
+ assert out == "foo bar baz|foo bar baz"
def test_truncate_end_length(self, env):
tmpl = env.from_string('{{ "Joel is a slug"|truncate(7, true) }}')
out = tmpl.render()
- assert out == "Joel...", "Current output: %s" % out
+ assert out == "Joel..."
def test_upper(self, env):
tmpl = env.from_string('{{ "foo"|upper }}')
@@ -580,7 +578,7 @@ class TestFilter(object):
def test_forceescape(self, env):
tmpl = env.from_string("{{ x|forceescape }}")
- assert tmpl.render(x=Markup("<div />")) == u"&lt;div /&gt;"
+ assert tmpl.render(x=Markup("<div />")) == "&lt;div /&gt;"
def test_safe(self, env):
env = Environment(autoescape=True)
@@ -593,10 +591,10 @@ class TestFilter(object):
("value", "expect"),
[
("Hello, world!", "Hello%2C%20world%21"),
- (u"Hello, world\u203d", "Hello%2C%20world%E2%80%BD"),
+ ("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"),
({"f": 1}, "f=1"),
([("f", 1), ("z", 2)], "f=1&amp;z=2"),
- ({u"\u203d": 1}, "%E2%80%BD=1"),
+ ({"\u203d": 1}, "%E2%80%BD=1"),
({0: 1}, "0=1"),
([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"),
("a b/c", "a%20b/c"),
diff --git a/tests/test_idtracking.py b/tests/test_idtracking.py
index 4c79ee6..8a88467 100644
--- a/tests/test_idtracking.py
+++ b/tests/test_idtracking.py
@@ -38,7 +38,7 @@ def test_basics():
def test_complex():
title_block = nodes.Block(
- "title", [nodes.Output([nodes.TemplateData(u"Page Title")])], False
+ "title", [nodes.Output([nodes.TemplateData("Page Title")])], False
)
render_title_macro = nodes.Macro(
@@ -48,11 +48,11 @@ def test_complex():
[
nodes.Output(
[
- nodes.TemplateData(u'\n <div class="title">\n <h1>'),
+ nodes.TemplateData('\n <div class="title">\n <h1>'),
nodes.Name("title", "load"),
- nodes.TemplateData(u"</h1>\n <p>"),
+ nodes.TemplateData("</h1>\n <p>"),
nodes.Name("subtitle", "load"),
- nodes.TemplateData(u"</p>\n "),
+ nodes.TemplateData("</p>\n "),
]
),
nodes.Assign(
@@ -60,9 +60,9 @@ def test_complex():
),
nodes.Output(
[
- nodes.TemplateData(u"\n <p>"),
+ nodes.TemplateData("\n <p>"),
nodes.Name("subtitle", "load"),
- nodes.TemplateData(u"</p>\n </div>\n"),
+ nodes.TemplateData("</p>\n </div>\n"),
nodes.If(
nodes.Name("something", "load"),
[
@@ -104,13 +104,13 @@ def test_complex():
[
nodes.Output(
[
- nodes.TemplateData(u"\n <li>"),
+ nodes.TemplateData("\n <li>"),
nodes.Name("item", "load"),
- nodes.TemplateData(u"</li>\n <span>"),
+ nodes.TemplateData("</li>\n <span>"),
]
),
nodes.Include(nodes.Const("helper.html"), True, False),
- nodes.Output([nodes.TemplateData(u"</span>\n ")]),
+ nodes.Output([nodes.TemplateData("</span>\n ")]),
],
[],
None,
@@ -122,7 +122,7 @@ def test_complex():
[
nodes.Output(
[
- nodes.TemplateData(u"\n "),
+ nodes.TemplateData("\n "),
nodes.Call(
nodes.Name("render_title", "load"),
[nodes.Name("item", "load")],
@@ -130,11 +130,11 @@ def test_complex():
None,
None,
),
- nodes.TemplateData(u"\n <ul>\n "),
+ nodes.TemplateData("\n <ul>\n "),
]
),
for_loop,
- nodes.Output([nodes.TemplateData(u"\n </ul>\n")]),
+ nodes.Output([nodes.TemplateData("\n </ul>\n")]),
],
False,
)
@@ -155,7 +155,7 @@ def test_complex():
assert tmpl_sym.loads == {
"l_0_render_title": ("undefined", None),
}
- assert tmpl_sym.stores == set(["render_title"])
+ assert tmpl_sym.stores == {"render_title"}
assert tmpl_sym.dump_stores() == {
"render_title": "l_0_render_title",
}
@@ -173,7 +173,7 @@ def test_complex():
"l_1_title": ("param", None),
"l_1_title_upper": ("resolve", "title_upper"),
}
- assert macro_sym.stores == set(["title", "title_upper", "subtitle"])
+ assert macro_sym.stores == {"title", "title_upper", "subtitle"}
assert macro_sym.find_ref("render_title") == "l_0_render_title"
assert macro_sym.dump_stores() == {
"title": "l_1_title",
@@ -193,7 +193,7 @@ def test_complex():
"l_0_seq": ("resolve", "seq"),
"l_0_render_title": ("resolve", "render_title"),
}
- assert body_sym.stores == set([])
+ assert body_sym.stores == set()
for_sym = symbols_for_node(for_loop, body_sym)
assert for_sym.refs == {
@@ -202,7 +202,7 @@ def test_complex():
assert for_sym.loads == {
"l_1_item": ("param", None),
}
- assert for_sym.stores == set(["item"])
+ assert for_sym.stores == {"item"}
assert for_sym.dump_stores() == {
"item": "l_1_item",
}
@@ -222,7 +222,7 @@ def test_if_branching_stores():
sym = symbols_for_node(tmpl)
assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"}
- assert sym.stores == set(["variable"])
+ assert sym.stores == {"variable"}
assert sym.loads == {
"l_0_variable": ("resolve", "variable"),
"l_0_expression": ("resolve", "expression"),
@@ -247,7 +247,7 @@ def test_if_branching_stores_undefined():
sym = symbols_for_node(tmpl)
assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"}
- assert sym.stores == set(["variable"])
+ assert sym.stores == {"variable"}
assert sym.loads == {
"l_0_variable": ("undefined", None),
"l_0_expression": ("resolve", "expression"),
@@ -281,7 +281,7 @@ def test_if_branching_multi_scope():
tmpl_sym = symbols_for_node(tmpl)
for_sym = symbols_for_node(for_loop, tmpl_sym)
- assert for_sym.stores == set(["item", "x"])
+ assert for_sym.stores == {"item", "x"}
assert for_sym.loads == {
"l_1_x": ("alias", "l_0_x"),
"l_1_item": ("param", None),
diff --git a/tests/test_imports.py b/tests/test_imports.py
index 0dae217..ca4de70 100644
--- a/tests/test_imports.py
+++ b/tests/test_imports.py
@@ -1,11 +1,11 @@
-# -*- coding: utf-8 -*-
import pytest
-from jinja2 import DictLoader
-from jinja2 import Environment
+from jinja2.environment import Environment
from jinja2.exceptions import TemplateNotFound
from jinja2.exceptions import TemplatesNotFound
from jinja2.exceptions import TemplateSyntaxError
+from jinja2.exceptions import UndefinedError
+from jinja2.loaders import DictLoader
@pytest.fixture
@@ -24,7 +24,7 @@ def test_env():
@pytest.mark.imports
-class TestImports(object):
+class TestImports:
def test_context_imports(self, test_env):
t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
assert t.render(foo=42) == "[|23]"
@@ -93,10 +93,16 @@ class TestImports(object):
assert m.variable == 42
assert not hasattr(m, "notthere")
+ def test_not_exported(self, test_env):
+ t = test_env.from_string("{% from 'module' import nothing %}{{ nothing() }}")
+
+ with pytest.raises(UndefinedError, match="does not export the requested name"):
+ t.render()
+
@pytest.mark.imports
@pytest.mark.includes
-class TestIncludes(object):
+class TestIncludes:
def test_context_include(self, test_env):
t = test_env.from_string('{% include "header" %}')
assert t.render(foo=42) == "[42|23]"
diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py
index 92f66e0..331427e 100644
--- a/tests/test_inheritance.py
+++ b/tests/test_inheritance.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from jinja2 import DictLoader
@@ -75,7 +74,7 @@ def env():
@pytest.mark.inheritance
-class TestInheritance(object):
+class TestInheritance:
def test_layout(self, env):
tmpl = env.get_template("layout")
assert tmpl.render() == (
@@ -158,7 +157,7 @@ class TestInheritance(object):
)
tmpl = env.get_template("child")
for m in range(1, 3):
- assert tmpl.render(master="master%d" % m) == "MASTER%dCHILD" % m
+ assert tmpl.render(master=f"master{m}") == f"MASTER{m}CHILD"
def test_multi_inheritance(self, env):
env = Environment(
@@ -234,7 +233,7 @@ class TestInheritance(object):
@pytest.mark.inheritance
-class TestBugFix(object):
+class TestBugFix:
def test_fixed_macro_scoping_bug(self, env):
assert (
Environment(
@@ -276,7 +275,7 @@ class TestBugFix(object):
.get_template("test.html")
.render()
.split()
- == [u"outer_box", u"my_macro"]
+ == ["outer_box", "my_macro"]
)
def test_double_extends(self, env):
diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py
index 75dd755..fabbd36 100644
--- a/tests/test_lexnparse.py
+++ b/tests/test_lexnparse.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from jinja2 import Environment
@@ -15,7 +14,7 @@ from jinja2.lexer import TokenStream
@pytest.mark.lexnparse
@pytest.mark.tokenstream
-class TestTokenStream(object):
+class TestTokenStream:
test_tokens = [
Token(1, TOKEN_BLOCK_BEGIN, ""),
Token(2, TOKEN_BLOCK_END, ""),
@@ -45,7 +44,7 @@ class TestTokenStream(object):
@pytest.mark.lexnparse
@pytest.mark.lexer
-class TestLexer(object):
+class TestLexer:
def test_raw1(self, env):
tmpl = env.from_string(
"{% raw %}foo{% endraw %}|"
@@ -96,16 +95,16 @@ 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 }}" % repr(char))
+ for char in "\0", "\u2668", "\xe4", "\t", "\r", "\n":
+ tmpl = env.from_string(f"{{{{ {char!r} }}}}")
assert tmpl.render() == char
- assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u"\u2668"
+ assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == "\u2668"
def test_bytefallback(self, env):
from pprint import pformat
- tmpl = env.from_string(u"""{{ 'foo'|pprint }}|{{ 'bär'|pprint }}""")
- assert tmpl.render() == pformat("foo") + "|" + pformat(u"bär")
+ tmpl = env.from_string("""{{ 'foo'|pprint }}|{{ 'bär'|pprint }}""")
+ assert tmpl.render() == pformat("foo") + "|" + pformat("bär")
def test_operators(self, env):
from jinja2.lexer import operators
@@ -113,7 +112,7 @@ class TestLexer(object):
for test, expect in operators.items():
if test in "([{}])":
continue
- stream = env.lexer.tokenize("{{ %s }}" % test)
+ stream = env.lexer.tokenize(f"{{{{ {test} }}}}")
next(stream)
assert stream.current.type == expect
@@ -141,26 +140,26 @@ class TestLexer(object):
@pytest.mark.parametrize(
("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
+ ("foo", True),
+ ("föö", True),
+ ("き", True),
+ ("_", True),
+ ("1a", False), # invalid ascii start
+ ("a-", False), # invalid ascii continue
+ ("\U0001f40da", False), # invalid unicode start
+ ("a🐍\U0001f40d", False), # invalid unicode continue
# start characters not matched by \w
- (u"\u1885", True),
- (u"\u1886", True),
- (u"\u2118", True),
- (u"\u212e", True),
+ ("\u1885", True),
+ ("\u1886", True),
+ ("\u2118", True),
+ ("\u212e", True),
# continue character not matched by \w
- (u"\xb7", False),
- (u"a\xb7", True),
+ ("\xb7", False),
+ ("a\xb7", True),
],
)
def test_name(self, env, name, valid):
- t = u"{{ " + name + u" }}"
+ t = "{{ " + name + " }}"
if valid:
# valid for version being tested, shouldn't raise
@@ -171,7 +170,7 @@ class TestLexer(object):
@pytest.mark.lexnparse
@pytest.mark.parser
-class TestParser(object):
+class TestParser:
def test_php_syntax(self, env):
env = Environment("<?", "?>", "<?=", "?>", "<!--", "-->")
tmpl = env.from_string(
@@ -306,7 +305,7 @@ and bar comment #}
@pytest.mark.lexnparse
@pytest.mark.syntax
-class TestSyntax(object):
+class TestSyntax:
def test_call(self, env):
env = Environment()
env.globals["foo"] = lambda a, b, c, e, g: a + b + c + e + g
@@ -357,7 +356,7 @@ class TestSyntax(object):
],
)
def test_compare(self, env, a, op, b):
- t = env.from_string("{{ %d %s %d }}" % (a, op, b))
+ t = env.from_string(f"{{{{ {a} {op} {b} }}}}")
assert t.render() == "True"
def test_compare_parens(self, env):
@@ -385,7 +384,7 @@ class TestSyntax(object):
@pytest.mark.parametrize("value", ("[]", "{}", "()"))
def test_collection_literal(self, env, value):
- t = env.from_string("{{ %s }}" % value)
+ t = env.from_string(f"{{{{ {value} }}}}")
assert t.render() == value
@pytest.mark.parametrize(
@@ -406,7 +405,7 @@ class TestSyntax(object):
),
)
def test_numeric_literal(self, env, value, expect):
- t = env.from_string("{{ %s }}" % value)
+ t = env.from_string(f"{{{{ {value} }}}}")
assert t.render() == expect
def test_bool(self, env):
@@ -460,11 +459,10 @@ class TestSyntax(object):
]
for should_fail, sig in tests:
if should_fail:
- pytest.raises(
- TemplateSyntaxError, env.from_string, "{{ foo(%s) }}" % sig
- )
+ with pytest.raises(TemplateSyntaxError):
+ env.from_string(f"{{{{ foo({sig}) }}}}")
else:
- env.from_string("foo(%s)" % sig)
+ env.from_string(f"foo({sig})")
def test_tuple_expr(self, env):
for tmpl in [
@@ -491,11 +489,11 @@ class TestSyntax(object):
def test_constant_casing(self, env):
for const in True, False, None:
+ const = str(const)
tmpl = env.from_string(
- "{{ %s }}|{{ %s }}|{{ %s }}"
- % (str(const), str(const).lower(), str(const).upper())
+ f"{{{{ {const} }}}}|{{{{ {const.lower()} }}}}|{{{{ {const.upper()} }}}}"
)
- assert tmpl.render() == "%s|%s|" % (const, const)
+ assert tmpl.render() == f"{const}|{const}|"
def test_test_chaining(self, env):
pytest.raises(
@@ -517,12 +515,12 @@ class TestSyntax(object):
assert tmpl.render() == "5"
def test_implicit_subscribed_tuple(self, env):
- class Foo(object):
+ class Foo:
def __getitem__(self, x):
return x
t = env.from_string("{{ foo[1, 2] }}")
- assert t.render(foo=Foo()) == u"(1, 2)"
+ assert t.render(foo=Foo()) == "(1, 2)"
def test_raw2(self, env):
tmpl = env.from_string("{% raw %}{{ FOO }} and {% BAR %}{% endraw %}")
@@ -563,7 +561,7 @@ class TestSyntax(object):
@pytest.mark.lexnparse
@pytest.mark.lstripblocks
-class TestLstripBlocks(object):
+class TestLstripBlocks:
def test_lstrip(self, env):
env = Environment(lstrip_blocks=True, trim_blocks=False)
tmpl = env.from_string(""" {% if True %}\n {% endif %}""")
@@ -683,7 +681,7 @@ hello
${item} ## the rest of the stuff
<% endfor %>"""
)
- assert tmpl.render(seq=range(5)) == "".join("%s\n" % x for x in range(5))
+ assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5))
def test_lstrip_angle_bracket_compact(self, env):
env = Environment(
@@ -705,7 +703,7 @@ ${item} ## the rest of the stuff
${item} ## the rest of the stuff
<%endfor%>"""
)
- assert tmpl.render(seq=range(5)) == "".join("%s\n" % x for x in range(5))
+ assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5))
def test_php_syntax_with_manual(self, env):
env = Environment(
@@ -731,9 +729,7 @@ ${item} ## the rest of the stuff
<?= item ?>
<? endfor ?>"""
)
- assert tmpl.render(seq=range(5)) == "".join(
- " %s\n" % x for x in range(5)
- )
+ assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5))
def test_php_syntax_compact(self, env):
env = Environment(
@@ -746,19 +742,12 @@ ${item} ## the rest of the stuff
<?=item?>
<?endfor?>"""
)
- assert tmpl.render(seq=range(5)) == "".join(
- " %s\n" % x for x in range(5)
- )
+ assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5))
def test_erb_syntax(self, env):
env = Environment(
"<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True
)
- # env.from_string('')
- # for n,r in env.lexer.rules.iteritems():
- # print n
- # print env.lexer.rules['root'][0][0].pattern
- # print "'%s'" % tmpl.render(seq=range(5))
tmpl = env.from_string(
"""\
<%# I'm a comment, I'm not interesting %>
@@ -767,7 +756,7 @@ ${item} ## the rest of the stuff
<% endfor %>
"""
)
- assert tmpl.render(seq=range(5)) == "".join(" %s\n" % x for x in range(5))
+ assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5))
def test_erb_syntax_with_manual(self, env):
env = Environment(
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 84bd175..0ad9538 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import platform
import shutil
@@ -17,7 +16,7 @@ from jinja2.loaders import split_template_path
@pytest.mark.loaders
-class TestLoaders(object):
+class TestLoaders:
def test_dict_loader(self, dict_loader):
env = Environment(loader=dict_loader)
tmpl = env.get_template("justdict.html")
@@ -67,7 +66,7 @@ class TestLoaders(object):
class TestLoader(loaders.BaseLoader):
def get_source(self, environment, template):
- return u"foo", None, lambda: not changed
+ return "foo", None, lambda: not changed
env = Environment(loader=TestLoader(), cache_size=-1)
tmpl = env.get_template("template")
@@ -118,7 +117,7 @@ class TestLoaders(object):
@pytest.mark.loaders
@pytest.mark.filesystemloader
-class TestFileSystemLoader(object):
+class TestFileSystemLoader:
searchpath = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "res", "templates"
)
@@ -168,8 +167,8 @@ class TestFileSystemLoader(object):
@pytest.mark.parametrize(
("encoding", "expect"),
[
- ("utf-8", u"文字化け"),
- ("iso-8859-1", u"æ\x96\x87\xe5\xad\x97\xe5\x8c\x96\xe3\x81\x91"),
+ ("utf-8", "文字化け"),
+ ("iso-8859-1", "æ\x96\x87\xe5\xad\x97\xe5\x8c\x96\xe3\x81\x91"),
],
)
def test_uses_specified_encoding(self, encoding, expect):
@@ -181,7 +180,7 @@ class TestFileSystemLoader(object):
@pytest.mark.loaders
@pytest.mark.moduleloader
-class TestModuleLoader(object):
+class TestModuleLoader:
archive = None
def compile_down(self, prefix_loader, zip="deflated"):
@@ -348,7 +347,7 @@ def test_package_zip_source(package_zip_loader, template, expect):
@pytest.mark.xfail(
platform.python_implementation() == "PyPy",
- reason="PyPy's zipimporter doesn't have a _files attribute.",
+ reason="PyPy's zipimporter doesn't have a '_files' attribute.",
raises=TypeError,
)
def test_package_zip_list(package_zip_loader):
diff --git a/tests/test_regression.py b/tests/test_regression.py
index 993a922..7205bde 100644
--- a/tests/test_regression.py
+++ b/tests/test_regression.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from jinja2 import DictLoader
@@ -11,7 +10,7 @@ from jinja2 import TemplateSyntaxError
@pytest.mark.regression
-class TestCorner(object):
+class TestCorner:
def test_assigned_scoping(self, env):
t = env.from_string(
"""
@@ -83,7 +82,7 @@ class TestCorner(object):
@pytest.mark.regression
-class TestBug(object):
+class TestBug:
def test_keyword_folding(self, env):
env = Environment()
env.filters["testing"] = lambda value, some: value + some
@@ -192,7 +191,7 @@ class TestBug(object):
"""
)
rv = t.render(foo=[1]).strip()
- assert rv == u"1"
+ assert rv == "1"
def test_call_with_args(self, env):
t = Template(
@@ -226,13 +225,13 @@ class TestBug(object):
]
).splitlines()
] == [
- u"<ul><li><p>apo</p><dl>",
- u"<dl>Realname</dl>",
- u"<dd>something else</dd>",
- u"<dl>Description</dl>",
- u"<dd>test</dd>",
- u"</dl>",
- u"</li></ul>",
+ "<ul><li><p>apo</p><dl>",
+ "<dl>Realname</dl>",
+ "<dd>something else</dd>",
+ "<dl>Description</dl>",
+ "<dd>test</dd>",
+ "</dl>",
+ "</li></ul>",
]
def test_empty_if_condition_fails(self, env):
@@ -294,7 +293,7 @@ class TestBug(object):
def test_contextfunction_callable_classes(self, env):
from jinja2.utils import contextfunction
- class CallableClass(object):
+ class CallableClass:
@contextfunction
def __call__(self, ctx):
return ctx.resolve("hello")
@@ -595,7 +594,7 @@ class TestBug(object):
def resolve(self, name):
if name == "foo":
return 42
- return super(MyContext, self).resolve(name)
+ return super().resolve(name)
x = MyContext(env, parent={"bar": 23}, name="foo", blocks={})
assert x._legacy_resolve_mode
diff --git a/tests/test_security.py b/tests/test_security.py
index 43057ec..c3b3f9c 100644
--- a/tests/test_security.py
+++ b/tests/test_security.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from jinja2 import Environment
@@ -12,7 +11,7 @@ from jinja2.sandbox import SandboxedEnvironment
from jinja2.sandbox import unsafe
-class PrivateStuff(object):
+class PrivateStuff:
def bar(self):
return 23
@@ -24,7 +23,7 @@ class PrivateStuff(object):
return "PrivateStuff"
-class PublicStuff(object):
+class PublicStuff:
def bar(self):
return 23
@@ -36,7 +35,7 @@ class PublicStuff(object):
@pytest.mark.sandbox
-class TestSandbox(object):
+class TestSandbox:
def test_unsafe(self, env):
env = SandboxedEnvironment()
pytest.raises(
@@ -105,10 +104,10 @@ class TestSandbox(object):
for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"):
env = SandboxedEnvironment()
env.binop_table["+"] = disable_op
- t = env.from_string("{{ %s }}" % expr)
+ t = env.from_string(f"{{{{ {expr} }}}}")
assert t.render(ctx) == rv
env.intercepted_binops = frozenset(["+"])
- t = env.from_string("{{ %s }}" % expr)
+ t = env.from_string(f"{{{{ {expr} }}}}")
with pytest.raises(TemplateRuntimeError):
t.render(ctx)
@@ -119,16 +118,16 @@ class TestSandbox(object):
for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"):
env = SandboxedEnvironment()
env.unop_table["-"] = disable_op
- t = env.from_string("{{ %s }}" % expr)
+ t = env.from_string(f"{{{{ {expr} }}}}")
assert t.render(ctx) == rv
env.intercepted_unops = frozenset(["-"])
- t = env.from_string("{{ %s }}" % expr)
+ t = env.from_string(f"{{{{ {expr} }}}}")
with pytest.raises(TemplateRuntimeError):
t.render(ctx)
@pytest.mark.sandbox
-class TestStringFormat(object):
+class TestStringFormat:
def test_basic_format_safety(self):
env = SandboxedEnvironment()
t = env.from_string('{{ "a{0.__class__}b".format(42) }}')
@@ -151,7 +150,7 @@ class TestStringFormat(object):
@pytest.mark.sandbox
-class TestStringFormatMap(object):
+class TestStringFormatMap:
def test_basic_format_safety(self):
env = SandboxedEnvironment()
t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}')
diff --git a/tests/test_tests.py b/tests/test_tests.py
index 42595e8..dbf6e60 100644
--- a/tests/test_tests.py
+++ b/tests/test_tests.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from jinja2 import Environment
@@ -10,7 +9,7 @@ class MyDict(dict):
@pytest.mark.test_tests
-class TestTestsCase(object):
+class TestTestsCase:
def test_defined(self, env):
tmpl = env.from_string("{{ missing is defined }}|{{ true is defined }}")
assert tmpl.render() == "False|True"
@@ -111,7 +110,7 @@ class TestTestsCase(object):
),
)
def test_types(self, env, op, expect):
- t = env.from_string("{{{{ {op} }}}}".format(op=op))
+ t = env.from_string(f"{{{{ {op} }}}}")
assert t.render(mydict=MyDict(), complex=complex(1, 2)) == str(expect)
def test_upper(self, env):
@@ -152,7 +151,7 @@ class TestTestsCase(object):
),
)
def test_compare_aliases(self, env, op, expect):
- t = env.from_string("{{{{ 2 is {op} }}}}".format(op=op))
+ t = env.from_string(f"{{{{ 2 is {op} }}}}")
assert t.render() == str(expect)
def test_sameas(self, env):
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 9e75c6d..750002f 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pickle
import random
from collections import deque
@@ -18,7 +17,7 @@ from jinja2.utils import urlize
@pytest.mark.utils
@pytest.mark.lrucache
-class TestLRUCache(object):
+class TestLRUCache:
def test_simple(self):
d = LRUCache(3)
d["a"] = 1
@@ -80,7 +79,7 @@ class TestLRUCache(object):
d["b"] = 2
d["c"] = 3
# Sort the strings - mapping is unordered
- assert sorted(repr(d)) == sorted(u"<LRUCache {'a': 1, 'b': 2, 'c': 3}>")
+ assert sorted(repr(d)) == sorted("<LRUCache {'a': 1, 'b': 2, 'c': 3}>")
def test_items(self):
"""Test various items, keys, values and iterators of LRUCache."""
@@ -113,9 +112,9 @@ class TestLRUCache(object):
@pytest.mark.utils
@pytest.mark.helpers
-class TestHelpers(object):
+class TestHelpers:
def test_object_type_repr(self):
- class X(object):
+ class X:
pass
assert object_type_repr(42) == "int object"
@@ -143,7 +142,7 @@ class TestHelpers(object):
@pytest.mark.utils
@pytest.mark.escapeUrlizeTarget
-class TestEscapeUrlizeTarget(object):
+class TestEscapeUrlizeTarget:
def test_escape_urlize_target(self):
url = "http://example.org"
target = "<script>"
@@ -156,7 +155,7 @@ class TestEscapeUrlizeTarget(object):
@pytest.mark.utils
@pytest.mark.loremIpsum
-class TestLoremIpsum(object):
+class TestLoremIpsum:
def test_lorem_ipsum_markup(self):
"""Test that output of lorem_ipsum is Markup by default."""
assert isinstance(generate_lorem_ipsum(), Markup)
@@ -167,7 +166,7 @@ class TestLoremIpsum(object):
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""
+ assert generate_lorem_ipsum(n=0, html=False) == ""
for n in range(1, 50):
assert generate_lorem_ipsum(n=n, html=False).count("\n") == (n - 1) * 2
@@ -188,7 +187,7 @@ class TestLoremIpsum(object):
def test_missing():
"""Test the repr of missing."""
- assert repr(missing) == u"missing"
+ assert repr(missing) == "missing"
def test_consume():