aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2020-01-28 20:16:59 -0800
committerDavid Lord <davidism@gmail.com>2020-02-05 08:44:15 -0800
commit2a8515d2e53a2be475d9df3fe44e308501201a95 (patch)
tree88c9e788ce0e0be96b27e0e40f82e7c406567ee3 /src
parent42edc132902562e1e16f190bea60362865da894f (diff)
downloadjinja-2a8515d2e53a2be475d9df3fe44e308501201a95.tar.gz
apply pyupgrade and f-strings
Diffstat (limited to 'src')
-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
24 files changed, 398 insertions, 538 deletions
diff --git a/src/jinja2/asyncfilters.py b/src/jinja2/asyncfilters.py
index 3caee858..0aad12c8 100644
--- a/src/jinja2/asyncfilters.py
+++ b/src/jinja2/asyncfilters.py
@@ -84,7 +84,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 d7d6259a..3aef7ad2 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 b328b3b0..70b99c53 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 8b7203bb..045a3a88 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) is True:
self.write("context, ")
elif getattr(func, "evalcontextfilter", False) is True:
@@ -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 bf7f2ca7..41a1c23b 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 a0efa29a..46c24eba 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 72a93578..1f0b0ab0 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 247f7bf1..3c93c484 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) is True:
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 8d5e89d7..07cfba26 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 fd36e2d4..7c36bb43 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 0675f54f..f39d389f 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 7889a2b5..78cad916 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 4988f7e3..e0b7a2e9 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 e48155dc..b02cc357 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 d112cbed..899e179a 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 4f8106bc..e0ad94d3 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 1989fcf0..d5133f75 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 7bc78c45..39d059f1 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 ec0778a7..eedea7a0 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 462238b5..00f1f59f 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 1263c77f..deecf61c 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 6a24d9c0..bc763268 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 dfc518f2..99293cd6 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 d1365bf1..590fa9eb 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."""