back to Claude Sonnet 3.5 - Base summary
Claude Sonnet 3.5 - Base: jinja
Failed to run pytests for test tests
ImportError while loading conftest '/testbed/tests/conftest.py'.
tests/conftest.py:5: in <module>
from jinja2 import loaders
src/jinja2/__init__.py:9: in <module>
from .environment import Environment as Environment
src/jinja2/environment.py:14: in <module>
from . import nodes
src/jinja2/nodes.py:808: in <module>
NodeType.__new__ = staticmethod(_failing_new)
E NameError: name '_failing_new' is not defined
Patch diff
diff --git a/src/jinja2/bccache.py b/src/jinja2/bccache.py
index ae575a3..7417030 100644
--- a/src/jinja2/bccache.py
+++ b/src/jinja2/bccache.py
@@ -47,23 +47,31 @@ class Bucket:
def reset(self) ->None:
"""Resets the bucket (unloads the bytecode)."""
- pass
+ self.code = None
def load_bytecode(self, f: t.BinaryIO) ->None:
"""Loads bytecode from a file or file like object."""
- pass
+ code = marshal.load(f)
+ if isinstance(code, CodeType):
+ self.code = code
def write_bytecode(self, f: t.IO[bytes]) ->None:
"""Dump the bytecode into the file or file like object passed."""
- pass
+ if self.code is not None:
+ marshal.dump(self.code, f)
def bytecode_from_string(self, string: bytes) ->None:
"""Load bytecode from bytes."""
- pass
+ f = BytesIO(string)
+ self.load_bytecode(f)
def bytecode_to_string(self) ->bytes:
"""Return the bytecode as bytes."""
- pass
+ if self.code is None:
+ return b""
+ f = BytesIO()
+ self.write_bytecode(f)
+ return f.getvalue()
class BytecodeCache:
@@ -100,41 +108,43 @@ class BytecodeCache:
bucket. If they are not able to find code in the cache for the
bucket, it must not do anything.
"""
- pass
+ raise NotImplementedError()
def dump_bytecode(self, bucket: Bucket) ->None:
"""Subclasses have to override this method to write the bytecode
from a bucket back to the cache. If it unable to do so it must not
fail silently but raise an exception.
"""
- pass
+ raise NotImplementedError()
def clear(self) ->None:
"""Clears the cache. This method is not used by Jinja but should be
implemented to allow applications to clear the bytecode cache used
by a particular environment.
"""
- pass
+ raise NotImplementedError()
def get_cache_key(self, name: str, filename: t.Optional[t.Union[str]]=None
) ->str:
"""Returns the unique hash key for this template name."""
- pass
+ return sha1(f"{name}|{filename}".encode("utf-8")).hexdigest()
def get_source_checksum(self, source: str) ->str:
"""Returns a checksum for the source."""
- pass
+ return sha1(source.encode("utf-8")).hexdigest()
def get_bucket(self, environment: 'Environment', name: str, filename: t
.Optional[str], source: str) ->Bucket:
"""Return a cache bucket for the given template. All arguments are
mandatory but filename may be `None`.
"""
- pass
+ key = self.get_cache_key(name, filename)
+ checksum = self.get_source_checksum(source)
+ return Bucket(environment, key, checksum)
def set_bucket(self, bucket: Bucket) ->None:
"""Put the bucket into the cache."""
- pass
+ self.dump_bytecode(bucket)
class FileSystemBytecodeCache(BytecodeCache):
@@ -162,6 +172,38 @@ class FileSystemBytecodeCache(BytecodeCache):
self.directory = directory
self.pattern = pattern
+ def _get_default_cache_dir(self) ->str:
+ if sys.platform == 'win32':
+ return os.path.join(tempfile.gettempdir(), 'jinja2_cache')
+ else:
+ return os.path.join(tempfile.gettempdir(), f'jinja2_cache_{os.getuid()}')
+
+ def _get_cache_filename(self, bucket: Bucket) ->str:
+ return os.path.join(self.directory, self.pattern % bucket.key)
+
+ def load_bytecode(self, bucket: Bucket) ->None:
+ filename = self._get_cache_filename(bucket)
+ if os.path.exists(filename):
+ with open(filename, 'rb') as f:
+ bucket.load_bytecode(f)
+
+ def dump_bytecode(self, bucket: Bucket) ->None:
+ filename = self._get_cache_filename(bucket)
+ try:
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
+ with open(filename, 'wb') as f:
+ bucket.write_bytecode(f)
+ except OSError as e:
+ raise OSError(f'Unable to write bytecode cache file: {e}')
+
+ def clear(self) ->None:
+ for filename in os.listdir(self.directory):
+ if fnmatch.fnmatch(filename, self.pattern % '*'):
+ try:
+ os.remove(os.path.join(self.directory, filename))
+ except OSError:
+ pass
+
class MemcachedBytecodeCache(BytecodeCache):
"""This class implements a bytecode cache that uses a memcache cache for
@@ -215,3 +257,26 @@ class MemcachedBytecodeCache(BytecodeCache):
self.prefix = prefix
self.timeout = timeout
self.ignore_memcache_errors = ignore_memcache_errors
+
+ def load_bytecode(self, bucket: Bucket) ->None:
+ try:
+ code = self.client.get(self.prefix + bucket.key)
+ if code is not None:
+ bucket.bytecode_from_string(code)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
+
+ def dump_bytecode(self, bucket: Bucket) ->None:
+ try:
+ args = [self.prefix + bucket.key, bucket.bytecode_to_string()]
+ if self.timeout is not None:
+ args.append(self.timeout)
+ self.client.set(*args)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
+
+ def clear(self) ->None:
+ # Memcached doesn't support clearing specific keys, so this is a no-op
+ pass
diff --git a/src/jinja2/compiler.py b/src/jinja2/compiler.py
index 32df45a..307559e 100644
--- a/src/jinja2/compiler.py
+++ b/src/jinja2/compiler.py
@@ -31,12 +31,16 @@ def generate(node: nodes.Template, environment: 'Environment', name: t.
Optional[str], filename: t.Optional[str], stream: t.Optional[t.TextIO]=
None, defer_init: bool=False, optimized: bool=True) ->t.Optional[str]:
"""Generate the python source for a node tree."""
- pass
+ codegen = CodeGenerator(environment, name, filename, stream, defer_init, optimized)
+ codegen.visit(node)
+ if stream is None:
+ return codegen.stream.getvalue()
+ return None
def has_safe_repr(value: t.Any) ->bool:
"""Does the node have a safe representation?"""
- pass
+ return isinstance(value, (bool, int, float, str, tuple, frozenset))
def find_undeclared(nodes: t.Iterable[nodes.Node], names: t.Iterable[str]
@@ -44,7 +48,13 @@ def find_undeclared(nodes: t.Iterable[nodes.Node], names: t.Iterable[str]
"""Check if the names passed are accessed undeclared. The return value
is a set of all the undeclared names from the sequence of names found.
"""
- pass
+ visitor = UndeclaredNameVisitor(names)
+ try:
+ for node in nodes:
+ visitor.visit(node)
+ except VisitorExit:
+ pass
+ return visitor.undeclared
class MacroRef:
@@ -81,11 +91,21 @@ class Frame:
def copy(self) ->'Frame':
"""Create a copy of the current one."""
- pass
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.symbols = self.symbols.copy()
+ return rv
def inner(self, isolated: bool=False) ->'Frame':
"""Return an inner frame."""
- pass
+ rv = self.copy()
+ if isolated:
+ rv.symbols = Symbols(parent=rv.symbols)
+ rv.block_frame = False
+ rv.loop_frame = False
+ rv.toplevel = False
+ rv.rootlevel = False
+ return rv
def soft(self) ->'Frame':
"""Return a soft frame. A soft frame may not be modified as
@@ -95,7 +115,13 @@ class Frame:
This is only used to implement if-statements and conditional
expressions.
"""
- pass
+ rv = self.copy()
+ rv.toplevel = False
+ rv.rootlevel = False
+ rv.loop_frame = False
+ rv.block_frame = False
+ rv.soft_frame = True
+ return rv
__copy__ = copy
@@ -112,8 +138,7 @@ class DependencyFinderVisitor(NodeVisitor):
def visit_Block(self, node: nodes.Block) ->None:
"""Stop visiting at blocks."""
- pass
-
+ return
class UndeclaredNameVisitor(NodeVisitor):
"""A visitor that checks if a name is accessed without being
@@ -125,9 +150,15 @@ class UndeclaredNameVisitor(NodeVisitor):
self.names = set(names)
self.undeclared: t.Set[str] = set()
+ def visit_Name(self, node: nodes.Name) ->None:
+ if node.name in self.names:
+ self.undeclared.add(node.name)
+ if len(self.undeclared) == len(self.names):
+ raise VisitorExit()
+
def visit_Block(self, node: nodes.Block) ->None:
"""Stop visiting a blocks."""
- pass
+ return
class CompilerExit(Exception):
diff --git a/src/jinja2/debug.py b/src/jinja2/debug.py
index 412f2c2..6055a20 100644
--- a/src/jinja2/debug.py
+++ b/src/jinja2/debug.py
@@ -20,7 +20,25 @@ def rewrite_traceback_stack(source: t.Optional[str]=None) ->BaseException:
known.
:return: The original exception with the rewritten traceback.
"""
- pass
+ exc_type, exc_value, tb = sys.exc_info()
+ if isinstance(exc_value, TemplateSyntaxError) and source is not None:
+ exc_value.source = source
+
+ while tb is not None:
+ if tb.tb_frame.f_code.co_filename == '<template>':
+ filename = exc_value.filename
+ lineno = exc_value.lineno
+
+ # Create a fake traceback
+ new_tb = fake_traceback(exc_value, tb, filename, lineno)
+
+ # Replace the old traceback with the new one
+ exc_value.__traceback__ = new_tb
+ break
+
+ tb = tb.tb_next
+
+ return exc_value
def fake_traceback(exc_value: BaseException, tb: t.Optional[TracebackType],
@@ -37,7 +55,37 @@ def fake_traceback(exc_value: BaseException, tb: t.Optional[TracebackType],
:param filename: The template filename.
:param lineno: The line number in the template source.
"""
- pass
+ if tb is None:
+ raise exc_value
+
+ locals = get_template_locals(tb.tb_frame.f_locals)
+ globals = tb.tb_frame.f_globals
+
+ # Create a fake code object
+ code = CodeType(
+ 0, # argcount
+ 0, # kwonlyargcount
+ 0, # nlocals
+ 0, # stacksize
+ 0, # flags
+ b'', # bytecode
+ (), # constants
+ (), # names
+ (), # varnames
+ filename, # filename
+ '<template>', # name
+ lineno, # firstlineno
+ b'', # lnotab
+ (), # freevars
+ () # cellvars
+ )
+
+ # Create a fake frame
+ fake_frame = tb.tb_frame.__class__(code, globals, locals)
+ fake_frame.f_lineno = lineno
+
+ # Create a new traceback object
+ return TracebackType(None, fake_frame, fake_frame.f_lasti, fake_frame.f_lineno)
def get_template_locals(real_locals: t.Mapping[str, t.Any]) ->t.Dict[str, t.Any
@@ -45,4 +93,12 @@ def get_template_locals(real_locals: t.Mapping[str, t.Any]) ->t.Dict[str, t.Any
"""Based on the runtime locals, get the context that would be
available at that point in the template.
"""
- pass
+ context = real_locals.get('context')
+ if isinstance(context, Context):
+ return {
+ 'context': context,
+ 'environment': context.environment,
+ 'resolver': context.environment.resolver,
+ **context.get_all()
+ }
+ return {}
diff --git a/src/jinja2/environment.py b/src/jinja2/environment.py
index aae9f98..f21e599 100644
--- a/src/jinja2/environment.py
+++ b/src/jinja2/environment.py
@@ -68,19 +68,28 @@ def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any
:param cls: Environment class to create.
:param args: Positional arguments passed to environment.
"""
- pass
+ return cls(*args)
def create_cache(size: int) ->t.Optional[t.MutableMapping[t.Tuple[
'weakref.ref[t.Any]', str], 'Template']]:
"""Return the cache class for the given size."""
- pass
+ if size == 0:
+ return None
+ if size < 0:
+ return {}
+ return LRUCache(size)
def copy_cache(cache: t.Optional[t.MutableMapping[t.Any, t.Any]]) ->t.Optional[
t.MutableMapping[t.Tuple['weakref.ref[t.Any]', str], 'Template']]:
"""Create an empty copy of the given cache."""
- pass
+ if cache is None:
+ return None
+ elif isinstance(cache, LRUCache):
+ return LRUCache(cache.capacity)
+ else:
+ return {}
def load_extensions(environment: 'Environment', extensions: t.Sequence[t.
@@ -88,12 +97,39 @@ def load_extensions(environment: 'Environment', extensions: t.Sequence[t.
"""Load the extensions from the list and bind it to the environment.
Returns a dict of instantiated extensions.
"""
- pass
+ result = {}
+ for extension in extensions:
+ if isinstance(extension, str):
+ extension = import_string(extension)
+ if isinstance(extension, type):
+ extension = extension(environment)
+ result[extension.identifier] = extension
+ return result
def _environment_config_check(environment: 'Environment') ->'Environment':
"""Perform a sanity check on the environment."""
- pass
+ if not isinstance(environment.block_start_string, str):
+ raise TypeError('block_start_string must be a string')
+ if not isinstance(environment.block_end_string, str):
+ raise TypeError('block_end_string must be a string')
+ if not isinstance(environment.variable_start_string, str):
+ raise TypeError('variable_start_string must be a string')
+ if not isinstance(environment.variable_end_string, str):
+ raise TypeError('variable_end_string must be a string')
+ if not isinstance(environment.comment_start_string, str):
+ raise TypeError('comment_start_string must be a string')
+ if not isinstance(environment.comment_end_string, str):
+ raise TypeError('comment_end_string must be a string')
+ if not isinstance(environment.line_statement_prefix, (str, type(None))):
+ raise TypeError('line_statement_prefix must be a string or None')
+ if not isinstance(environment.line_comment_prefix, (str, type(None))):
+ raise TypeError('line_comment_prefix must be a string or None')
+ if not isinstance(environment.trim_blocks, bool):
+ raise TypeError('trim_blocks must be a boolean')
+ if not isinstance(environment.lstrip_blocks, bool):
+ raise TypeError('lstrip_blocks must be a boolean')
+ return environment
class Environment:
diff --git a/src/jinja2/ext.py b/src/jinja2/ext.py
index 337f30c..9d826ca 100644
--- a/src/jinja2/ext.py
+++ b/src/jinja2/ext.py
@@ -62,7 +62,10 @@ class Extension:
def bind(self, environment: Environment) ->'Extension':
"""Create a copy of this extension bound to another environment."""
- pass
+ rv = type(self)(environment)
+ rv.__dict__.update(self.__dict__)
+ rv.environment = environment
+ return rv
def preprocess(self, source: str, name: t.Optional[str], filename: t.
Optional[str]=None) ->str:
@@ -70,7 +73,7 @@ class Extension:
preprocess the source. The `filename` is optional. The return value
must be the preprocessed source.
"""
- pass
+ return source
def filter_stream(self, stream: 'TokenStream') ->t.Union['TokenStream',
t.Iterable['Token']]:
@@ -79,7 +82,7 @@ class Extension:
:class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
:class:`~jinja2.lexer.TokenStream`.
"""
- pass
+ return stream
def parse(self, parser: 'Parser') ->t.Union[nodes.Node, t.List[nodes.Node]
]:
@@ -88,7 +91,7 @@ class Extension:
is the name token that matched. This method has to return one or a
list of multiple nodes.
"""
- pass
+ raise NotImplementedError(f'{self.__class__.__name__}.parse() must be implemented')
def attr(self, name: str, lineno: t.Optional[int]=None
) ->nodes.ExtensionAttribute:
@@ -99,7 +102,7 @@ class Extension:
self.attr('_my_attribute', lineno=lineno)
"""
- pass
+ return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
def call_method(self, name: str, args: t.Optional[t.List[nodes.Expr]]=
None, kwargs: t.Optional[t.List[nodes.Keyword]]=None, dyn_args: t.
@@ -108,7 +111,12 @@ class Extension:
"""Call a method of the extension. This is a shortcut for
:meth:`attr` + :class:`jinja2.nodes.Call`.
"""
- pass
+ if args is None:
+ args = []
+ if kwargs is None:
+ kwargs = []
+ return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
+ dyn_args, dyn_kwargs, lineno=lineno)
class InternationalizationExtension(Extension):
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index 9498dc3..ecd27f0 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -43,7 +43,9 @@ V = t.TypeVar('V')
def ignore_case(value: V) ->V:
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
to lowercase and returns other types as-is."""
- pass
+ if isinstance(value, str):
+ return value.lower()
+ return value
def make_attrgetter(environment: 'Environment', attribute: t.Optional[t.
@@ -54,7 +56,21 @@ def make_attrgetter(environment: 'Environment', attribute: t.Optional[t.
to access attributes of attributes. Integer parts in paths are
looked up as integers.
"""
- pass
+ if attribute is None:
+ return lambda x: x
+ if isinstance(attribute, int):
+ return lambda x: environment.getitem(x, attribute)
+ if '.' not in attribute:
+ return lambda x: environment.getattr(x, attribute, default)
+
+ def getter(x):
+ for part in attribute.split('.'):
+ if part.isdigit():
+ x = environment.getitem(x, int(part))
+ else:
+ x = environment.getattr(x, part, default)
+ return x if postprocess is None else postprocess(x)
+ return getter
def make_multi_attrgetter(environment: 'Environment', attribute: t.Optional
@@ -70,12 +86,22 @@ def make_multi_attrgetter(environment: 'Environment', attribute: t.Optional
Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
"""
- pass
+ if attribute is None:
+ return lambda x: [x]
+
+ getters = [make_attrgetter(environment, attr.strip(), postprocess)
+ for attr in attribute.split(',')]
+
+ def getter(x):
+ return [g(x) for g in getters]
+ return getter
def do_forceescape(value: 't.Union[str, HasHTML]') ->Markup:
"""Enforce HTML escaping. This will probably double escape variables."""
- pass
+ if hasattr(value, '__html__'):
+ value = value.__html__()
+ return Markup(escape(str(value)))
def do_urlencode(value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.
@@ -95,7 +121,16 @@ def do_urlencode(value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.
.. versionadded:: 2.7
"""
- pass
+ from urllib.parse import quote, urlencode
+
+ if isinstance(value, str):
+ return quote(value, safe='/')
+ elif isinstance(value, t.Mapping):
+ return urlencode(value)
+ elif isinstance(value, t.Iterable):
+ return urlencode(list(value))
+ else:
+ raise TypeError("Expected string, mapping, or iterable")
@pass_eval_context
diff --git a/src/jinja2/idtracking.py b/src/jinja2/idtracking.py
index a1d69ca..44d11c4 100644
--- a/src/jinja2/idtracking.py
+++ b/src/jinja2/idtracking.py
@@ -45,32 +45,39 @@ class FrameSymbolVisitor(NodeVisitor):
def visit_Name(self, node: nodes.Name, store_as_param: bool=False, **
kwargs: t.Any) ->None:
"""All assignments to names go through this function."""
- pass
+ if node.ctx == 'store':
+ if store_as_param:
+ self.symbols.loads[node.name] = VAR_LOAD_PARAMETER
+ self.symbols.stores.add(node.name)
+ elif node.ctx == 'param':
+ self.symbols.loads[node.name] = VAR_LOAD_PARAMETER
def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) ->None:
"""Visit assignments in the correct order."""
- pass
+ self.visit(node.node, **kwargs)
+ self.visit(node.target, **kwargs)
def visit_For(self, node: nodes.For, **kwargs: t.Any) ->None:
"""Visiting stops at for blocks. However the block sequence
is visited as part of the outer scope.
"""
- pass
+ self.visit(node.iter, **kwargs)
+ self.visit(node.target, store_as_param=True, **kwargs)
def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any
) ->None:
"""Stop visiting at block assigns."""
- pass
+ self.visit(node.target, **kwargs)
def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) ->None:
"""Stop visiting at scopes."""
- pass
+ # We don't need to do anything here, as we're stopping at scopes
def visit_Block(self, node: nodes.Block, **kwargs: t.Any) ->None:
"""Stop visiting at blocks."""
- pass
+ # We don't need to do anything here, as we're stopping at blocks
def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any
) ->None:
"""Do not visit into overlay scopes."""
- pass
+ # We don't need to do anything here, as we're not visiting into overlay scopes
diff --git a/src/jinja2/lexer.py b/src/jinja2/lexer.py
index 2281b7e..e825327 100644
--- a/src/jinja2/lexer.py
+++ b/src/jinja2/lexer.py
@@ -117,24 +117,39 @@ ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT,
def describe_token(token: 'Token') ->str:
"""Returns a description of the token."""
- pass
+ if token.type == 'name':
+ return token.value
+ return f'{token.type}'
def describe_token_expr(expr: str) ->str:
"""Like `describe_token` but for token expressions."""
- pass
+ if ':' in expr:
+ type, value = expr.split(':', 1)
+ if type == 'name':
+ return value
+ return f'{type}({value})'
+ return expr
def count_newlines(value: str) ->int:
"""Count the number of newline characters in the string. This is
useful for extensions that filter a stream.
"""
- pass
+ return len(newline_re.findall(value))
def compile_rules(environment: 'Environment') ->t.List[t.Tuple[str, str]]:
"""Compiles all the rules from the environment into a list of rules."""
- pass
+ e = re.escape
+ rules = [
+ ('comment', e(environment.comment_start_string)),
+ ('block', e(environment.block_start_string)),
+ ('variable', e(environment.variable_start_string)),
+ ('linestatement', e(environment.line_statement_prefix) if environment.line_statement_prefix else ''),
+ ('linecomment', e(environment.line_comment_prefix) if environment.line_comment_prefix else ''),
+ ]
+ return [(k, v) for k, v in rules if v]
class Failure:
@@ -164,11 +179,14 @@ class Token(t.NamedTuple):
token type or ``'token_type:token_value'``. This can only test
against string values and types.
"""
- pass
+ if ':' in expr:
+ type, value = expr.split(':', 1)
+ return self.type == type and self.value == value
+ return self.type == expr
def test_any(self, *iterable: str) ->bool:
"""Test against multiple token expressions."""
- pass
+ return any(self.test(expr) for expr in iterable)
class TokenStreamIterator:
@@ -216,29 +234,35 @@ class TokenStream:
@property
def eos(self) ->bool:
"""Are we at the end of the stream?"""
- pass
+ return not bool(self)
def push(self, token: Token) ->None:
"""Push a token back to the stream."""
- pass
+ self._pushed.append(token)
def look(self) ->Token:
"""Look at the next token."""
- pass
+ old_token = next(self)
+ result = self.current
+ self.push(old_token)
+ return result
def skip(self, n: int=1) ->None:
"""Got n tokens ahead."""
- pass
+ for _ in range(n):
+ next(self)
def next_if(self, expr: str) ->t.Optional[Token]:
"""Perform the token test and return the token if it matched.
Otherwise the return value is `None`.
"""
- pass
+ if self.current.test(expr):
+ return next(self)
+ return None
def skip_if(self, expr: str) ->bool:
"""Like :meth:`next_if` but only returns `True` or `False`."""
- pass
+ return self.next_if(expr) is not None
def __next__(self) ->Token:
"""Go one token ahead and return the old one.
@@ -257,18 +281,46 @@ class TokenStream:
def close(self) ->None:
"""Close the stream."""
- pass
+ self.closed = True
def expect(self, expr: str) ->Token:
"""Expect a given token type and return it. This accepts the same
argument as :meth:`jinja2.lexer.Token.test`.
"""
- pass
+ if not self.current.test(expr):
+ if ':' in expr:
+ expr = f'{expr.split(":", 1)[0]} token'
+ raise TemplateSyntaxError(
+ f'expected {expr}', self.current.lineno,
+ self.name, self.filename
+ )
+ try:
+ return next(self)
+ except StopIteration:
+ raise TemplateSyntaxError('unexpected end of template',
+ self.current.lineno, self.name, self.filename)
def get_lexer(environment: 'Environment') ->'Lexer':
"""Return a lexer which is probably cached."""
- pass
+ key = (environment.block_start_string,
+ environment.block_end_string,
+ environment.variable_start_string,
+ environment.variable_end_string,
+ environment.comment_start_string,
+ environment.comment_end_string,
+ environment.line_statement_prefix,
+ environment.line_comment_prefix,
+ environment.trim_blocks,
+ environment.lstrip_blocks,
+ environment.newline_sequence,
+ environment.keep_trailing_newline)
+
+ if key in _lexer_cache:
+ return _lexer_cache[key]
+ lexer = Lexer(environment)
+ _lexer_cache[key] = lexer
+ return lexer
class OptionalLStrip(tuple):
@@ -344,12 +396,13 @@ class Lexer:
"""Replace all newlines with the configured sequence in strings
and template data.
"""
- pass
+ return newline_re.sub(self.newline_sequence, value)
def tokenize(self, source: str, name: t.Optional[str]=None, filename: t
.Optional[str]=None, state: t.Optional[str]=None) ->TokenStream:
"""Calls tokeniter + tokenize and wraps it in a token stream."""
- pass
+ stream = self.tokeniter(source, name, filename, state)
+ return TokenStream(self.wrap(stream, name, filename), name, filename)
def wrap(self, stream: t.Iterable[t.Tuple[int, str, str]], name: t.
Optional[str]=None, filename: t.Optional[str]=None) ->t.Iterator[Token
@@ -357,7 +410,12 @@ class Lexer:
"""This is called with the stream as returned by `tokenize` and wraps
every token in a :class:`Token` and converts the value.
"""
- pass
+ for lineno, token, value in stream:
+ if token in ('linestatement_begin', 'linestatement_end'):
+ token = 'block_begin' if token == 'linestatement_begin' else 'block_end'
+ elif token in ('linecomment_begin', 'linecomment_end', 'linecomment'):
+ token = 'comment'
+ yield Token(lineno, token, value)
def tokeniter(self, source: str, name: t.Optional[str], filename: t.
Optional[str]=None, state: t.Optional[str]=None) ->t.Iterator[t.
@@ -369,4 +427,48 @@ class Lexer:
Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line
breaks.
"""
- pass
+ source = self._normalize_newlines(source)
+ lines = source.splitlines(True)
+ lineno = 1
+ state = state or 'root'
+ state_stack = [state]
+ line = ''
+ pos = 0
+ len_lines = len(lines)
+
+ while 1:
+ # tokenizer loop
+ for rule in self.rules[state]:
+ m = rule.pattern.match(line, pos)
+ if m:
+ if isinstance(rule.tokens, tuple):
+ for idx, token in enumerate(rule.tokens):
+ yield lineno, token, m.group(idx + 1)
+ else:
+ yield lineno, rule.tokens, m.group()
+ pos = m.end()
+ if rule.command is not None:
+ cmd = rule.command
+ if cmd == '#pop':
+ state_stack.pop()
+ if not state_stack:
+ state_stack.append('root')
+ elif cmd == '#push':
+ state_stack.append(state)
+ else:
+ state_stack.append(cmd)
+ state = state_stack[-1]
+ break
+ else:
+ # if loop exhausted, move to next line
+ pos = 0
+ lineno += 1
+ if lineno > len_lines:
+ break
+ line = lines[lineno - 1]
+
+ if state != 'root':
+ raise TemplateSyntaxError('Unexpected end of template',
+ lineno, name, filename)
+
+ yield lineno, 'eof', ''
diff --git a/src/jinja2/meta.py b/src/jinja2/meta.py
index 37016c7..2beb63e 100644
--- a/src/jinja2/meta.py
+++ b/src/jinja2/meta.py
@@ -22,7 +22,7 @@ class TrackingCodeGenerator(CodeGenerator):
def enter_frame(self, frame: Frame) ->None:
"""Remember all undeclared identifiers."""
- pass
+ self.undeclared_identifiers.update(frame.identifiers.undeclared)
def find_undeclared_variables(ast: nodes.Template) ->t.Set[str]:
@@ -44,7 +44,9 @@ def find_undeclared_variables(ast: nodes.Template) ->t.Set[str]:
:exc:`TemplateAssertionError` during compilation and as a matter of
fact this function can currently raise that exception as well.
"""
- pass
+ codegen = TrackingCodeGenerator(ast.environment)
+ codegen.visit(ast)
+ return codegen.undeclared_identifiers
_ref_types = nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include
@@ -68,4 +70,17 @@ def find_referenced_templates(ast: nodes.Template) ->t.Iterator[t.Optional[str]
This function is useful for dependency tracking. For example if you want
to rebuild parts of the website after a layout template has changed.
"""
- pass
+ for node in ast.find_all(_ref_types):
+ if isinstance(node, nodes.Extends):
+ if isinstance(node.template, nodes.Const):
+ yield node.template.value
+ else:
+ yield None
+ elif isinstance(node, nodes.Include):
+ if isinstance(node.template, nodes.Const):
+ yield node.template.value
+ else:
+ yield None
+ elif isinstance(node, (nodes.Import, nodes.FromImport)):
+ if isinstance(node.template, nodes.Const):
+ yield node.template.value
diff --git a/src/jinja2/nativetypes.py b/src/jinja2/nativetypes.py
index 9eae726..da64b12 100644
--- a/src/jinja2/nativetypes.py
+++ b/src/jinja2/nativetypes.py
@@ -1,4 +1,5 @@
import typing as t
+import sys
from ast import literal_eval
from ast import parse
from itertools import chain
@@ -21,7 +22,16 @@ def native_concat(values: t.Iterable[t.Any]) ->t.Optional[t.Any]:
:param values: Iterable of outputs to concatenate.
"""
- pass
+ result = list(values)
+ if not result:
+ return None
+ if len(result) == 1:
+ return result[0]
+
+ try:
+ return literal_eval("".join(str(v) for v in result))
+ except (ValueError, SyntaxError):
+ return "".join(str(v) for v in result)
class NativeCodeGenerator(CodeGenerator):
@@ -46,7 +56,12 @@ class NativeTemplate(Template):
with :func:`ast.literal_eval`, the parsed value is returned.
Otherwise, the string is returned.
"""
- pass
+ ctx = self.new_context(dict(*args, **kwargs))
+ try:
+ return self.environment.concat(self.root_render_func(ctx))
+ except Exception:
+ exc_info = sys.exc_info()
+ return self.environment.handle_exception(exc_info, True)
NativeEnvironment.template_class = NativeTemplate
diff --git a/src/jinja2/nodes.py b/src/jinja2/nodes.py
index 4ec1d17..416aa80 100644
--- a/src/jinja2/nodes.py
+++ b/src/jinja2/nodes.py
@@ -107,7 +107,9 @@ class Node(metaclass=NodeType):
parameter or to exclude some using the `exclude` parameter. Both
should be sets or tuples of field names.
"""
- pass
+ for name in self.fields:
+ if (exclude is None or name not in exclude) and (only is None or name in only):
+ yield name, getattr(self, name)
def iter_child_nodes(self, exclude: t.Optional[t.Container[str]]=None,
only: t.Optional[t.Container[str]]=None) ->t.Iterator['Node']:
@@ -115,20 +117,35 @@ class Node(metaclass=NodeType):
over all fields and yields the values of they are nodes. If the value
of a field is a list all the nodes in that list are returned.
"""
- pass
+ for _, field in self.iter_fields(exclude, only):
+ if isinstance(field, Node):
+ yield field
+ elif isinstance(field, list):
+ for item in field:
+ if isinstance(item, Node):
+ yield item
def find(self, node_type: t.Type[_NodeBound]) ->t.Optional[_NodeBound]:
"""Find the first node of a given type. If no such node exists the
return value is `None`.
"""
- pass
+ for child in self.iter_child_nodes():
+ if isinstance(child, node_type):
+ return child
+ result = child.find(node_type)
+ if result is not None:
+ return result
+ return None
def find_all(self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.
Type[_NodeBound], ...]]) ->t.Iterator[_NodeBound]:
"""Find all the nodes of a given type. If the type is a tuple,
the check is performed for any of the tuple items.
"""
- pass
+ for child in self.iter_child_nodes():
+ if isinstance(child, node_type):
+ yield child
+ yield from child.find_all(node_type)
def set_ctx(self, ctx: str) ->'Node':
"""Reset the context of a node and all child nodes. Per default the
@@ -136,15 +153,26 @@ class Node(metaclass=NodeType):
most common one. This method is used in the parser to set assignment
targets and other nodes to a store context.
"""
- pass
+ if 'ctx' in self.fields:
+ self.ctx = ctx
+ for child in self.iter_child_nodes():
+ child.set_ctx(ctx)
+ return self
def set_lineno(self, lineno: int, override: bool=False) ->'Node':
"""Set the line numbers of the node and children."""
- pass
+ if not hasattr(self, 'lineno') or override:
+ self.lineno = lineno
+ for child in self.iter_child_nodes():
+ child.set_lineno(lineno, override)
+ return self
def set_environment(self, environment: 'Environment') ->'Node':
"""Set the environment for all nodes."""
- pass
+ self.environment = environment
+ for child in self.iter_child_nodes():
+ child.set_environment(environment)
+ return self
def __eq__(self, other: t.Any) ->bool:
if type(self) is not type(other):
@@ -340,11 +368,11 @@ class Expr(Node):
.. versionchanged:: 2.4
the `eval_ctx` parameter was added.
"""
- pass
+ raise Impossible()
def can_assign(self) ->bool:
"""Check if it's possible to assign something to this node."""
- pass
+ return False
class BinExpr(Expr):
@@ -405,7 +433,17 @@ class Const(Literal):
constant value in the generated code, otherwise it will raise
an `Impossible` exception.
"""
- pass
+ if isinstance(value, (bool, int, float, str, type(None))):
+ return cls(value, lineno=lineno, environment=environment)
+ elif isinstance(value, (list, tuple)):
+ items = [cls.from_untrusted(item, lineno, environment) for item in value]
+ return cls(type(value)(item.value for item in items), lineno=lineno, environment=environment)
+ elif isinstance(value, dict):
+ items = {cls.from_untrusted(k, lineno, environment).value:
+ cls.from_untrusted(v, lineno, environment).value
+ for k, v in value.items()}
+ return cls(items, lineno=lineno, environment=environment)
+ raise Impossible(f"Cannot convert {type(value)} to Const")
class TemplateData(Literal):
diff --git a/src/jinja2/optimizer.py b/src/jinja2/optimizer.py
index 53d50e4..3136cb0 100644
--- a/src/jinja2/optimizer.py
+++ b/src/jinja2/optimizer.py
@@ -17,10 +17,68 @@ if t.TYPE_CHECKING:
def optimize(node: nodes.Node, environment: 'Environment') ->nodes.Node:
"""The context hint can be used to perform an static optimization
based on the context given."""
- pass
+ optimizer = Optimizer(environment)
+ return optimizer.visit(node)
class Optimizer(NodeTransformer):
def __init__(self, environment: 't.Optional[Environment]') ->None:
self.environment = environment
+
+ def visit_Const(self, node: nodes.Const) ->nodes.Node:
+ """Optimize constant nodes."""
+ return node
+
+ def visit_List(self, node: nodes.List) ->nodes.Node:
+ """Optimize list nodes."""
+ node.items = [self.visit(item) for item in node.items]
+ return node
+
+ def visit_Dict(self, node: nodes.Dict) ->nodes.Node:
+ """Optimize dict nodes."""
+ node.items = [(self.visit(key), self.visit(value)) for key, value in node.items]
+ return node
+
+ def visit_Getitem(self, node: nodes.Getitem) ->nodes.Node:
+ """Optimize getitem nodes."""
+ node.node = self.visit(node.node)
+ node.arg = self.visit(node.arg)
+ return node
+
+ def visit_Getattr(self, node: nodes.Getattr) ->nodes.Node:
+ """Optimize getattr nodes."""
+ node.node = self.visit(node.node)
+ return node
+
+ def visit_Call(self, node: nodes.Call) ->nodes.Node:
+ """Optimize call nodes."""
+ node.node = self.visit(node.node)
+ node.args = [self.visit(arg) for arg in node.args]
+ node.kwargs = [(key, self.visit(value)) for key, value in node.kwargs]
+ return node
+
+ def visit_Filter(self, node: nodes.Filter) ->nodes.Node:
+ """Optimize filter nodes."""
+ node.node = self.visit(node.node)
+ node.args = [self.visit(arg) for arg in node.args]
+ node.kwargs = [(key, self.visit(value)) for key, value in node.kwargs]
+ return node
+
+ def visit_Test(self, node: nodes.Test) ->nodes.Node:
+ """Optimize test nodes."""
+ node.node = self.visit(node.node)
+ node.args = [self.visit(arg) for arg in node.args]
+ node.kwargs = [(key, self.visit(value)) for key, value in node.kwargs]
+ return node
+
+ def visit_CondExpr(self, node: nodes.CondExpr) ->nodes.Node:
+ """Optimize conditional expression nodes."""
+ node.test = self.visit(node.test)
+ node.expr1 = self.visit(node.expr1)
+ node.expr2 = self.visit(node.expr2)
+ return node
+
+ def generic_visit(self, node: nodes.Node) ->nodes.Node:
+ """Visit a node."""
+ return super().generic_visit(node)
diff --git a/src/jinja2/parser.py b/src/jinja2/parser.py
index 05ce33d..1a2147c 100644
--- a/src/jinja2/parser.py
+++ b/src/jinja2/parser.py
@@ -47,7 +47,9 @@ class Parser:
line number or last line number as well as the current name and
filename.
"""
- pass
+ if lineno is None:
+ lineno = self.stream.current.lineno
+ raise exc(msg, lineno, self.name, self.filename)
def fail_unknown_tag(self, name: str, lineno: t.Optional[int]=None
) ->'te.NoReturn':
@@ -55,26 +57,54 @@ class Parser:
with a human readable error message that could help to identify
the problem.
"""
- pass
+ if lineno is None:
+ lineno = self.stream.current.lineno
+ if name in ('endif', 'endfor', 'endblock', 'endmacro', 'endcall'):
+ self.fail(f'Unexpected end of block tag {name!r}', lineno)
+ elif name in _statement_keywords:
+ self.fail(f'Block tag {name!r} expected', lineno)
+ self.fail(f'Unknown tag {name!r}', lineno)
def fail_eof(self, end_tokens: t.Optional[t.Tuple[str, ...]]=None,
lineno: t.Optional[int]=None) ->'te.NoReturn':
"""Like fail_unknown_tag but for end of template situations."""
- pass
+ if end_tokens is not None:
+ expected = ' or '.join(repr(x) for x in end_tokens)
+ msg = f'Unexpected end of template. Expected {expected}.'
+ else:
+ msg = 'Unexpected end of template.'
+ self.fail(msg, lineno)
def is_tuple_end(self, extra_end_rules: t.Optional[t.Tuple[str, ...]]=None
) ->bool:
"""Are we at the end of a tuple?"""
- pass
+ if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
+ return True
+ if extra_end_rules is not None:
+ return self.stream.current.test_any(extra_end_rules)
+ return False
def free_identifier(self, lineno: t.Optional[int]=None
) ->nodes.InternalName:
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
- pass
+ self._last_identifier += 1
+ rv = object.__new__(nodes.InternalName)
+ rv.name = f'fi{self._last_identifier}'
+ rv.lineno = lineno
+ return rv
def parse_statement(self) ->t.Union[nodes.Node, t.List[nodes.Node]]:
"""Parse a single statement."""
- pass
+ token = self.stream.current
+ if token.type != 'name':
+ return self.parse_expression()
+ if token.value in _statement_keywords:
+ return getattr(self, f'parse_{token.value}')()
+ if token.value == 'call':
+ return self.parse_call_block()
+ if token.value == 'filter':
+ return self.parse_filter_block()
+ return self.parse_expression()
def parse_statements(self, end_tokens: t.Tuple[str, ...], drop_needle:
bool=False) ->t.List[nodes.Node]:
@@ -87,7 +117,20 @@ class Parser:
the call is the matched end token. If this is not wanted `drop_needle`
can be set to `True` and the end token is removed.
"""
- pass
+ result = []
+ while 1:
+ if self.stream.current.type == 'data':
+ result.append(nodes.Output([self.parse_tuple(with_condexpr=True)]))
+ elif self.stream.current.type == 'block_begin':
+ self.stream.next()
+ if self.stream.current.test_any(end_tokens):
+ if drop_needle:
+ self.stream.next()
+ return result
+ result.append(self.parse_statement())
+ else:
+ break
+ self.fail_eof(end_tokens)
def parse_set(self) ->t.Union[nodes.Assign, nodes.AssignBlock]:
"""Parse an assign statement."""
diff --git a/src/jinja2/runtime.py b/src/jinja2/runtime.py
index c88211d..1bceb54 100644
--- a/src/jinja2/runtime.py
+++ b/src/jinja2/runtime.py
@@ -43,17 +43,17 @@ def identity(x: V) ->V:
"""Returns its argument. Useful for certain things in the
environment.
"""
- pass
+ return x
def markup_join(seq: t.Iterable[t.Any]) ->str:
"""Concatenation that escapes if necessary and converts to string."""
- pass
+ return Markup('').join(escape(soft_str(v)) for v in seq)
def str_join(seq: t.Iterable[t.Any]) ->str:
"""Simple args to string conversion and concatenation."""
- pass
+ return ''.join(map(str, seq))
def new_context(environment: 'Environment', template_name: t.Optional[str],
@@ -62,7 +62,14 @@ def new_context(environment: 'Environment', template_name: t.Optional[str],
Optional[t.MutableMapping[str, t.Any]]=None, locals: t.Optional[t.
Mapping[str, t.Any]]=None) ->'Context':
"""Internal helper for context creation."""
- pass
+ parent = environment.make_globals(globals)
+ if vars is not None:
+ parent.update(vars)
+ if shared:
+ parent = vars or {}
+ if locals:
+ parent.update(locals)
+ return Context(environment, parent, template_name, blocks)
class TemplateReference:
@@ -116,7 +123,14 @@ class Context:
def super(self, name: str, current: t.Callable[['Context'], t.Iterator[
str]]) ->t.Union['BlockReference', 'Undefined']:
"""Render a parent block."""
- pass
+ try:
+ blocks = self.blocks[name]
+ index = blocks.index(current) + 1
+ if index < len(blocks):
+ return BlockReference(name, self, blocks, index)
+ except (LookupError, ValueError):
+ pass
+ return self.environment.undefined(f'there is no parent block called {name!r}.', name='super')
def get(self, key: str, default: t.Any=None) ->t.Any:
"""Look up a variable by name, or return a default if the key is
@@ -125,7 +139,10 @@ class Context:
:param key: The variable name to look up.
:param default: The value to return if the key is not found.
"""
- pass
+ try:
+ return self[key]
+ except KeyError:
+ return default
def resolve(self, key: str) ->t.Union[t.Any, 'Undefined']:
"""Look up a variable by name, or return an :class:`Undefined`
@@ -137,7 +154,10 @@ class Context:
:param key: The variable name to look up.
"""
- pass
+ rv = self.resolve_or_missing(key)
+ if rv is missing:
+ return self.environment.undefined(name=key)
+ return rv
def resolve_or_missing(self, key: str) ->t.Any:
"""Look up a variable by name, or return a ``missing`` sentinel
@@ -149,18 +169,22 @@ class Context:
:param key: The variable name to look up.
"""
- pass
+ if key in self.vars:
+ return self.vars[key]
+ if key in self.parent:
+ return self.parent[key]
+ return missing
def get_exported(self) ->t.Dict[str, t.Any]:
"""Get a new dict with the exported variables."""
- pass
+ return {k: self.vars[k] for k in self.exported_vars}
def get_all(self) ->t.Dict[str, t.Any]:
"""Return the complete context as dict including the exported
variables. For optimizations reasons this might not return an
actual copy so be careful with using it.
"""
- pass
+ return {**self.parent, **self.vars}
@internalcode
def call(__self, __obj: t.Callable[..., t.Any], *args: t.Any, **kwargs:
@@ -170,14 +194,23 @@ class Context:
argument if the callable has :func:`pass_context` or
:func:`pass_environment`.
"""
- pass
+ if isinstance(__obj, _PassArg):
+ if __obj._type == 'context':
+ args = (__self,) + args
+ elif __obj._type == 'environment':
+ args = (__self.environment,) + args
+ return __obj._func(*args, **kwargs)
+ return __obj(*args, **kwargs)
def derived(self, locals: t.Optional[t.Dict[str, t.Any]]=None) ->'Context':
"""Internal helper function to create a derived context. This is
used in situations where the system needs a new context in the same
template that is independent.
"""
- pass
+ context = new_context(self.environment, self.name, self.blocks,
+ self.get_all(), True, None, locals)
+ context.globals_keys = self.globals_keys
+ return context
keys = _dict_method_all(dict.keys)
values = _dict_method_all(dict.values)
items = _dict_method_all(dict.items)
diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py
index b73a983..9ea91b9 100644
--- a/src/jinja2/sandbox.py
+++ b/src/jinja2/sandbox.py
@@ -35,7 +35,22 @@ def safe_range(*args: int) ->range:
"""A range that can't generate ranges with a length of more than
MAX_RANGE items.
"""
- pass
+ if len(args) == 1:
+ start, stop, step = 0, args[0], 1
+ elif len(args) == 2:
+ start, stop, step = args[0], args[1], 1
+ elif len(args) == 3:
+ start, stop, step = args
+ else:
+ raise TypeError('range() requires 1-3 integer arguments')
+
+ # Calculate the length of the range
+ length = (stop - start + step - 1) // step
+
+ if length > MAX_RANGE:
+ raise OverflowError(f'range() result has too many items (maximum is {MAX_RANGE})')
+
+ return range(start, stop, step)
def unsafe(f: F) ->F:
@@ -47,7 +62,8 @@ def unsafe(f: F) ->F:
def delete(self):
pass
"""
- pass
+ f.unsafe_callable = True
+ return f
def is_internal_attribute(obj: t.Any, attr: str) ->bool:
@@ -62,7 +78,11 @@ def is_internal_attribute(obj: t.Any, attr: str) ->bool:
>>> is_internal_attribute(str, "upper")
False
"""
- pass
+ return attr.startswith('__') and attr.endswith('__') or \
+ attr.startswith('func_') or \
+ attr.startswith('im_') or \
+ attr in UNSAFE_FUNCTION_ATTRIBUTES or \
+ attr in UNSAFE_METHOD_ATTRIBUTES
def modifies_known_mutable(obj: t.Any, attr: str) ->bool:
@@ -84,7 +104,10 @@ def modifies_known_mutable(obj: t.Any, attr: str) ->bool:
>>> modifies_known_mutable("foo", "upper")
False
"""
- pass
+ for typ, mutable_attrs in _mutable_spec:
+ if isinstance(obj, typ):
+ return attr in mutable_attrs
+ return False
class SandboxedEnvironment(Environment):
@@ -120,7 +143,7 @@ class SandboxedEnvironment(Environment):
special attributes of internal python objects as returned by the
:func:`is_internal_attribute` function.
"""
- pass
+ return not (attr.startswith('_') or is_internal_attribute(obj, attr))
def is_safe_callable(self, obj: t.Any) ->bool:
"""Check if an object is safely callable. By default callables
@@ -129,7 +152,10 @@ class SandboxedEnvironment(Environment):
This also recognizes the Django convention of setting
``func.alters_data = True``.
"""
- pass
+ return callable(obj) and not (
+ getattr(obj, 'unsafe_callable', False) or
+ getattr(obj, 'alters_data', False)
+ )
def call_binop(self, context: Context, operator: str, left: t.Any,
right: t.Any) ->t.Any:
@@ -139,7 +165,9 @@ class SandboxedEnvironment(Environment):
.. versionadded:: 2.6
"""
- pass
+ if operator not in self.binop_table:
+ raise SecurityError(f'unsupported binary operator: {operator}')
+ return self.binop_table[operator](left, right)
def call_unop(self, context: Context, operator: str, arg: t.Any) ->t.Any:
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
@@ -148,22 +176,47 @@ class SandboxedEnvironment(Environment):
.. versionadded:: 2.6
"""
- pass
+ if operator not in self.unop_table:
+ raise SecurityError(f'unsupported unary operator: {operator}')
+ return self.unop_table[operator](arg)
def getitem(self, obj: t.Any, argument: t.Union[str, t.Any]) ->t.Union[
t.Any, Undefined]:
"""Subscribe an object from sandboxed code."""
- pass
+ try:
+ return obj[argument]
+ except (TypeError, LookupError):
+ if isinstance(argument, str):
+ try:
+ attr = str(argument)
+ except Exception:
+ pass
+ else:
+ try:
+ return self.getattr(obj, attr)
+ except RuntimeError:
+ return self.undefined(obj=obj, name=argument)
+ return self.undefined(obj=obj, name=argument)
def getattr(self, obj: t.Any, attribute: str) ->t.Union[t.Any, Undefined]:
"""Subscribe an object from sandboxed code and prefer the
attribute. The attribute passed *must* be a bytestring.
"""
- pass
+ try:
+ value = getattr(obj, attribute)
+ except AttributeError:
+ return self.undefined(obj=obj, name=attribute)
+ else:
+ if self.is_safe_attribute(obj, attribute, value):
+ return value
+ return self.unsafe_undefined(obj, attribute)
def unsafe_undefined(self, obj: t.Any, attribute: str) ->Undefined:
"""Return an undefined object for unsafe attributes."""
- pass
+ return self.undefined('access to attribute %r of %r object is unsafe.' % (
+ attribute,
+ obj.__class__.__name__
+ ), name=attribute, obj=obj, exc=SecurityError)
def format_string(self, s: str, args: t.Tuple[t.Any, ...], kwargs: t.
Dict[str, t.Any], format_func: t.Optional[t.Callable[..., t.Any]]=None
@@ -171,12 +224,21 @@ class SandboxedEnvironment(Environment):
"""If a format call is detected, then this is routed through this
method so that our safety sandbox can be used for it.
"""
- pass
+ if format_func is not None:
+ formatter = SandboxedEscapeFormatter(self, format_func)
+ elif isinstance(s, Markup):
+ formatter = SandboxedEscapeFormatter(self, lambda x: x)
+ else:
+ formatter = SandboxedFormatter(self)
+
+ return formatter.vformat(s, args, kwargs)
def call(__self, __context: Context, __obj: t.Any, *args: t.Any, **
kwargs: t.Any) ->t.Any:
"""Call an object from sandboxed code."""
- pass
+ if not __self.is_safe_callable(__obj):
+ raise SecurityError(f'{__obj!r} is not safely callable')
+ return __obj(*args, **kwargs)
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
diff --git a/src/jinja2/tests.py b/src/jinja2/tests.py
index 2823a4b..0963a3e 100644
--- a/src/jinja2/tests.py
+++ b/src/jinja2/tests.py
@@ -11,17 +11,17 @@ if t.TYPE_CHECKING:
def test_odd(value: int) ->bool:
"""Return true if the variable is odd."""
- pass
+ return value % 2 != 0
def test_even(value: int) ->bool:
"""Return true if the variable is even."""
- pass
+ return value % 2 == 0
def test_divisibleby(value: int, num: int) ->bool:
"""Check if a variable is divisible by a number."""
- pass
+ return value % num == 0
def test_defined(value: t.Any) ->bool:
@@ -38,12 +38,12 @@ def test_defined(value: t.Any) ->bool:
See the :func:`default` filter for a simple way to set undefined
variables.
"""
- pass
+ return not isinstance(value, Undefined)
def test_undefined(value: t.Any) ->bool:
"""Like :func:`defined` but the other way round."""
- pass
+ return isinstance(value, Undefined)
@pass_environment
@@ -61,7 +61,7 @@ def test_filter(env: 'Environment', value: str) ->bool:
.. versionadded:: 3.0
"""
- pass
+ return value in env.filters
@pass_environment
@@ -83,12 +83,12 @@ def test_test(env: 'Environment', value: str) ->bool:
.. versionadded:: 3.0
"""
- pass
+ return value in env.tests
def test_none(value: t.Any) ->bool:
"""Return true if the variable is none."""
- pass
+ return value is None
def test_boolean(value: t.Any) ->bool:
@@ -96,7 +96,7 @@ def test_boolean(value: t.Any) ->bool:
.. versionadded:: 2.11
"""
- pass
+ return isinstance(value, bool)
def test_false(value: t.Any) ->bool:
@@ -104,7 +104,7 @@ def test_false(value: t.Any) ->bool:
.. versionadded:: 2.11
"""
- pass
+ return value is False
def test_true(value: t.Any) ->bool:
@@ -112,7 +112,7 @@ def test_true(value: t.Any) ->bool:
.. versionadded:: 2.11
"""
- pass
+ return value is True
def test_integer(value: t.Any) ->bool:
@@ -120,7 +120,7 @@ def test_integer(value: t.Any) ->bool:
.. versionadded:: 2.11
"""
- pass
+ return isinstance(value, int)
def test_float(value: t.Any) ->bool:
@@ -128,22 +128,22 @@ def test_float(value: t.Any) ->bool:
.. versionadded:: 2.11
"""
- pass
+ return isinstance(value, float)
def test_lower(value: str) ->bool:
"""Return true if the variable is lowercased."""
- pass
+ return isinstance(value, str) and value.islower()
def test_upper(value: str) ->bool:
"""Return true if the variable is uppercased."""
- pass
+ return isinstance(value, str) and value.isupper()
def test_string(value: t.Any) ->bool:
"""Return true if the object is a string."""
- pass
+ return isinstance(value, str)
def test_mapping(value: t.Any) ->bool:
@@ -151,19 +151,19 @@ def test_mapping(value: t.Any) ->bool:
.. versionadded:: 2.6
"""
- pass
+ return isinstance(value, abc.Mapping)
def test_number(value: t.Any) ->bool:
"""Return true if the variable is a number."""
- pass
+ return isinstance(value, Number)
def test_sequence(value: t.Any) ->bool:
"""Return true if the variable is a sequence. Sequences are variables
that are iterable.
"""
- pass
+ return isinstance(value, (abc.Sequence, abc.Set)) and not isinstance(value, str)
def test_sameas(value: t.Any, other: t.Any) ->bool:
@@ -176,17 +176,21 @@ def test_sameas(value: t.Any, other: t.Any) ->bool:
the foo attribute really is the `False` singleton
{% endif %}
"""
- pass
+ return value is other
def test_iterable(value: t.Any) ->bool:
"""Check if it's possible to iterate over an object."""
- pass
+ try:
+ iter(value)
+ return True
+ except TypeError:
+ return False
def test_escaped(value: t.Any) ->bool:
"""Check if the value is escaped."""
- pass
+ return hasattr(value, '__html__')
def test_in(value: t.Any, seq: t.Container[t.Any]) ->bool:
@@ -194,7 +198,7 @@ def test_in(value: t.Any, seq: t.Container[t.Any]) ->bool:
.. versionadded:: 2.10
"""
- pass
+ return value in seq
TESTS = {'odd': test_odd, 'even': test_even, 'divisibleby':
diff --git a/src/jinja2/utils.py b/src/jinja2/utils.py
index 7563812..9a4ac9c 100644
--- a/src/jinja2/utils.py
+++ b/src/jinja2/utils.py
@@ -32,7 +32,8 @@ def pass_context(f: F) ->F:
.. versionadded:: 3.0.0
Replaces ``contextfunction`` and ``contextfilter``.
"""
- pass
+ f.jinja_pass_arg = _PassArg.context
+ return f
def pass_eval_context(f: F) ->F:
@@ -48,7 +49,8 @@ def pass_eval_context(f: F) ->F:
.. versionadded:: 3.0.0
Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
"""
- pass
+ f.jinja_pass_arg = _PassArg.eval_context
+ return f
def pass_environment(f: F) ->F:
@@ -60,7 +62,8 @@ def pass_environment(f: F) ->F:
.. versionadded:: 3.0.0
Replaces ``environmentfunction`` and ``environmentfilter``.
"""
- pass
+ f.jinja_pass_arg = _PassArg.environment
+ return f
class _PassArg(enum.Enum):
@@ -71,7 +74,8 @@ class _PassArg(enum.Enum):
def internalcode(f: F) ->F:
"""Marks the function as internally used"""
- pass
+ internal_code.add(f.__code__)
+ return f
def is_undefined(obj: t.Any) ->bool:
@@ -86,7 +90,8 @@ def is_undefined(obj: t.Any) ->bool:
return default
return var
"""
- pass
+ from .runtime import Undefined
+ return isinstance(obj, Undefined)
def consume(iterable: t.Iterable[t.Any]) ->None:
diff --git a/src/jinja2/visitor.py b/src/jinja2/visitor.py
index ebb34c6..17dcc82 100644
--- a/src/jinja2/visitor.py
+++ b/src/jinja2/visitor.py
@@ -30,15 +30,19 @@ class NodeVisitor:
exists for this node. In that case the generic visit function is
used instead.
"""
- pass
+ method = 'visit_' + node.__class__.__name__
+ return getattr(self, method, None)
def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) ->t.Any:
"""Visit a node."""
- pass
+ f = self.get_visitor(node)
+ if f is not None:
+ return f(node, *args, **kwargs)
+ return self.generic_visit(node, *args, **kwargs)
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) ->t.Any:
"""Called if no explicit visitor function exists for a node."""
- pass
+ return node
class NodeTransformer(NodeVisitor):
@@ -52,9 +56,16 @@ class NodeTransformer(NodeVisitor):
replacement takes place.
"""
- def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) ->t.List[
- Node]:
+ def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) ->t.List[Node]:
"""As transformers may return lists in some places this method
can be used to enforce a list as return value.
"""
- pass
+ result = []
+ for child in node:
+ new_node = self.visit(child, *args, **kwargs)
+ if new_node is not None:
+ if isinstance(new_node, list):
+ result.extend(new_node)
+ else:
+ result.append(new_node)
+ return result