删减jinja

Signed-off-by: fangting <fangting12@huawei.com>
This commit is contained in:
fangting
2023-06-13 16:37:01 +08:00
parent f81761a919
commit 2d8d28883b
96 changed files with 0 additions and 12531 deletions
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

View File
-19
View File
@@ -1,19 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

-922
View File
@@ -1,922 +0,0 @@
API
===
.. module:: jinja2
:noindex:
:synopsis: public Jinja API
This document describes the API to Jinja and not the template language
(for that, see :doc:`/templates`). It will be most useful as reference
to those implementing the template interface to the application and not
those who are creating Jinja templates.
Basics
------
Jinja uses a central object called the template :class:`Environment`.
Instances of this class are used to store the configuration and global objects,
and are used to load templates from the file system or other locations.
Even if you are creating templates from strings by using the constructor of
:class:`Template` class, an environment is created automatically for you,
albeit a shared one.
Most applications will create one :class:`Environment` object on application
initialization and use that to load templates. In some cases however, it's
useful to have multiple environments side by side, if different configurations
are in use.
The simplest way to configure Jinja to load templates for your
application is to use :class:`~loaders.PackageLoader`.
.. code-block:: python
from jinja2 import Environment, PackageLoader, select_autoescape
env = Environment(
loader=PackageLoader("yourapp"),
autoescape=select_autoescape()
)
This will create a template environment with a loader that looks up
templates in the ``templates`` folder inside the ``yourapp`` Python
package (or next to the ``yourapp.py`` Python module). It also enables
autoescaping for HTML files. This loader only requires that ``yourapp``
is importable, it figures out the absolute path to the folder for you.
Different loaders are available to load templates in other ways or from
other locations. They're listed in the `Loaders`_ section below. You can
also write your own if you want to load templates from a source that's
more specialized to your project.
To load a template from this environment, call the :meth:`get_template`
method, which returns the loaded :class:`Template`.
.. code-block:: python
template = env.get_template("mytemplate.html")
To render it with some variables, call the :meth:`render` method.
.. code-block:: python
print(template.render(the="variables", go="here"))
Using a template loader rather than passing strings to :class:`Template`
or :meth:`Environment.from_string` has multiple advantages. Besides being
a lot easier to use it also enables template inheritance.
.. admonition:: Notes on Autoescaping
In future versions of Jinja we might enable autoescaping by default
for security reasons. As such you are encouraged to explicitly
configure autoescaping now instead of relying on the default.
High Level API
--------------
The high-level API is the API you will use in the application to load and
render Jinja templates. The :ref:`low-level-api` on the other side is only
useful if you want to dig deeper into Jinja or :ref:`develop extensions
<jinja-extensions>`.
.. autoclass:: Environment([options])
:members: from_string, get_template, select_template,
get_or_select_template, join_path, extend, compile_expression,
compile_templates, list_templates, add_extension
.. attribute:: shared
If a template was created by using the :class:`Template` constructor
an environment is created automatically. These environments are
created as shared environments which means that multiple templates
may have the same anonymous environment. For all shared environments
this attribute is `True`, else `False`.
.. attribute:: sandboxed
If the environment is sandboxed this attribute is `True`. For the
sandbox mode have a look at the documentation for the
:class:`~jinja2.sandbox.SandboxedEnvironment`.
.. attribute:: filters
A dict of filters for this environment. As long as no template was
loaded it's safe to add new filters or remove old. For custom filters
see :ref:`writing-filters`. For valid filter names have a look at
:ref:`identifier-naming`.
.. attribute:: tests
A dict of test functions for this environment. As long as no
template was loaded it's safe to modify this dict. For custom tests
see :ref:`writing-tests`. For valid test names have a look at
:ref:`identifier-naming`.
.. attribute:: globals
A dict of variables that are available in every template loaded
by the environment. As long as no template was loaded it's safe
to modify this. For more details see :ref:`global-namespace`.
For valid object names see :ref:`identifier-naming`.
.. attribute:: policies
A dictionary with :ref:`policies`. These can be reconfigured to
change the runtime behavior or certain template features. Usually
these are security related.
.. attribute:: code_generator_class
The class used for code generation. This should not be changed
in most cases, unless you need to modify the Python code a
template compiles to.
.. attribute:: context_class
The context used for templates. This should not be changed
in most cases, unless you need to modify internals of how
template variables are handled. For details, see
:class:`~jinja2.runtime.Context`.
.. automethod:: overlay([options])
.. method:: undefined([hint, obj, name, exc])
Creates a new :class:`Undefined` object for `name`. This is useful
for filters or functions that may return undefined objects for
some operations. All parameters except of `hint` should be provided
as keyword parameters for better readability. The `hint` is used as
error message for the exception if provided, otherwise the error
message will be generated from `obj` and `name` automatically. The exception
provided as `exc` is raised if something with the generated undefined
object is done that the undefined object does not allow. The default
exception is :exc:`UndefinedError`. If a `hint` is provided the
`name` may be omitted.
The most common way to create an undefined object is by providing
a name only::
return environment.undefined(name='some_name')
This means that the name `some_name` is not defined. If the name
was from an attribute of an object it makes sense to tell the
undefined object the holder object to improve the error message::
if not hasattr(obj, 'attr'):
return environment.undefined(obj=obj, name='attr')
For a more complex example you can provide a hint. For example
the :func:`first` filter creates an undefined object that way::
return environment.undefined('no first item, sequence was empty')
If it the `name` or `obj` is known (for example because an attribute
was accessed) it should be passed to the undefined object, even if
a custom `hint` is provided. This gives undefined objects the
possibility to enhance the error message.
.. autoclass:: Template
:members: module, make_module
.. attribute:: globals
A dict of variables that are available every time the template
is rendered, without needing to pass them during render. This
should not be modified, as depending on how the template was
loaded it may be shared with the environment and other
templates.
Defaults to :attr:`Environment.globals` unless extra values are
passed to :meth:`Environment.get_template`.
Globals are only intended for data that is common to every
render of the template. Specific data should be passed to
:meth:`render`.
See :ref:`global-namespace`.
.. attribute:: name
The loading name of the template. If the template was loaded from a
string this is `None`.
.. attribute:: filename
The filename of the template on the file system if it was loaded from
there. Otherwise this is `None`.
.. automethod:: render([context])
.. automethod:: generate([context])
.. automethod:: stream([context])
.. automethod:: render_async([context])
.. automethod:: generate_async([context])
.. autoclass:: jinja2.environment.TemplateStream()
:members: disable_buffering, enable_buffering, dump
Autoescaping
------------
.. versionchanged:: 2.4
Jinja now comes with autoescaping support. As of Jinja 2.9 the
autoescape extension is removed and built-in. However autoescaping is
not yet enabled by default though this will most likely change in the
future. It's recommended to configure a sensible default for
autoescaping. This makes it possible to enable and disable autoescaping
on a per-template basis (HTML versus text for instance).
.. autofunction:: jinja2.select_autoescape
Here a recommended setup that enables autoescaping for templates ending
in ``'.html'``, ``'.htm'`` and ``'.xml'`` and disabling it by default
for all other extensions. You can use the :func:`~jinja2.select_autoescape`
function for this::
from jinja2 import Environment, PackageLoader, select_autoescape
env = Environment(autoescape=select_autoescape(['html', 'htm', 'xml']),
loader=PackageLoader('mypackage'))
The :func:`~jinja.select_autoescape` function returns a function that
works roughly like this::
def autoescape(template_name):
if template_name is None:
return False
if template_name.endswith(('.html', '.htm', '.xml'))
When implementing a guessing autoescape function, make sure you also
accept `None` as valid template name. This will be passed when generating
templates from strings. You should always configure autoescaping as
defaults in the future might change.
Inside the templates the behaviour can be temporarily changed by using
the `autoescape` block (see :ref:`autoescape-overrides`).
.. _identifier-naming:
Notes on Identifiers
--------------------
Jinja uses Python naming rules. Valid identifiers can be any combination
of characters accepted by Python.
Filters and tests are looked up in separate namespaces and have slightly
modified identifier syntax. Filters and tests may contain dots to group
filters and tests by topic. For example it's perfectly valid to add a
function into the filter dict and call it `to.str`. The regular
expression for filter and test identifiers is
``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*``.
Undefined Types
---------------
These classes can be used as undefined types. The :class:`Environment`
constructor takes an `undefined` parameter that can be one of those classes
or a custom subclass of :class:`Undefined`. Whenever the template engine is
unable to look up a name or access an attribute one of those objects is
created and returned. Some operations on undefined values are then allowed,
others fail.
The closest to regular Python behavior is the :class:`StrictUndefined` which
disallows all operations beside testing if it's an undefined object.
.. autoclass:: jinja2.Undefined()
.. attribute:: _undefined_hint
Either `None` or a string with the error message for the
undefined object.
.. attribute:: _undefined_obj
Either `None` or the owner object that caused the undefined object
to be created (for example because an attribute does not exist).
.. attribute:: _undefined_name
The name for the undefined variable / attribute or just `None`
if no such information exists.
.. attribute:: _undefined_exception
The exception that the undefined object wants to raise. This
is usually one of :exc:`UndefinedError` or :exc:`SecurityError`.
.. method:: _fail_with_undefined_error(\*args, \**kwargs)
When called with any arguments this method raises
:attr:`_undefined_exception` with an error message generated
from the undefined hints stored on the undefined object.
.. autoclass:: jinja2.ChainableUndefined()
.. autoclass:: jinja2.DebugUndefined()
.. autoclass:: jinja2.StrictUndefined()
There is also a factory function that can decorate undefined objects to
implement logging on failures:
.. autofunction:: jinja2.make_logging_undefined
Undefined objects are created by calling :attr:`undefined`.
.. admonition:: Implementation
:class:`Undefined` is implemented by overriding the special
``__underscore__`` methods. For example the default
:class:`Undefined` class implements ``__str__`` to returns an empty
string, while ``__int__`` and others fail with an exception. To
allow conversion to int by returning ``0`` you can implement your
own subclass.
.. code-block:: python
class NullUndefined(Undefined):
def __int__(self):
return 0
def __float__(self):
return 0.0
To disallow a method, override it and raise
:attr:`~Undefined._undefined_exception`. Because this is very
common there is the helper method
:meth:`~Undefined._fail_with_undefined_error` that raises the error
with the correct information. Here's a class that works like the
regular :class:`Undefined` but fails on iteration::
class NonIterableUndefined(Undefined):
def __iter__(self):
self._fail_with_undefined_error()
The Context
-----------
.. autoclass:: jinja2.runtime.Context()
:members: get, resolve, resolve_or_missing, get_exported, get_all
.. attribute:: parent
A dict of read only, global variables the template looks up. These
can either come from another :class:`Context`, from the
:attr:`Environment.globals` or :attr:`Template.globals` or points
to a dict created by combining the globals with the variables
passed to the render function. It must not be altered.
.. attribute:: vars
The template local variables. This list contains environment and
context functions from the :attr:`parent` scope as well as local
modifications and exported variables from the template. The template
will modify this dict during template evaluation but filters and
context functions are not allowed to modify it.
.. attribute:: environment
The environment that loaded the template.
.. attribute:: exported_vars
This set contains all the names the template exports. The values for
the names are in the :attr:`vars` dict. In order to get a copy of the
exported variables as dict, :meth:`get_exported` can be used.
.. attribute:: name
The load name of the template owning this context.
.. attribute:: blocks
A dict with the current mapping of blocks in the template. The keys
in this dict are the names of the blocks, and the values a list of
blocks registered. The last item in each list is the current active
block (latest in the inheritance chain).
.. attribute:: eval_ctx
The current :ref:`eval-context`.
.. automethod:: jinja2.runtime.Context.call(callable, \*args, \**kwargs)
The context is immutable, it prevents modifications, and if it is
modified somehow despite that those changes may not show up. For
performance, Jinja does not use the context as data storage for, only as
a primary data source. Variables that the template does not define are
looked up in the context, but variables the template does define are
stored locally.
Instead of modifying the context directly, a function should return
a value that can be assigned to a variable within the template itself.
.. code-block:: jinja
{% set comments = get_latest_comments() %}
.. _loaders:
Loaders
-------
Loaders are responsible for loading templates from a resource such as the
file system. The environment will keep the compiled modules in memory like
Python's `sys.modules`. Unlike `sys.modules` however this cache is limited in
size by default and templates are automatically reloaded.
All loaders are subclasses of :class:`BaseLoader`. If you want to create your
own loader, subclass :class:`BaseLoader` and override `get_source`.
.. autoclass:: jinja2.BaseLoader
:members: get_source, load
Here a list of the builtin loaders Jinja provides:
.. autoclass:: jinja2.FileSystemLoader
.. autoclass:: jinja2.PackageLoader
.. autoclass:: jinja2.DictLoader
.. autoclass:: jinja2.FunctionLoader
.. autoclass:: jinja2.PrefixLoader
.. autoclass:: jinja2.ChoiceLoader
.. autoclass:: jinja2.ModuleLoader
.. _bytecode-cache:
Bytecode Cache
--------------
Jinja 2.1 and higher support external bytecode caching. Bytecode caches make
it possible to store the generated bytecode on the file system or a different
location to avoid parsing the templates on first use.
This is especially useful if you have a web application that is initialized on
the first request and Jinja compiles many templates at once which slows down
the application.
To use a bytecode cache, instantiate it and pass it to the :class:`Environment`.
.. autoclass:: jinja2.BytecodeCache
:members: load_bytecode, dump_bytecode, clear
.. autoclass:: jinja2.bccache.Bucket
:members: write_bytecode, load_bytecode, bytecode_from_string,
bytecode_to_string, reset
.. attribute:: environment
The :class:`Environment` that created the bucket.
.. attribute:: key
The unique cache key for this bucket
.. attribute:: code
The bytecode if it's loaded, otherwise `None`.
Builtin bytecode caches:
.. autoclass:: jinja2.FileSystemBytecodeCache
.. autoclass:: jinja2.MemcachedBytecodeCache
Async Support
-------------
.. versionadded:: 2.9
Jinja supports the Python ``async`` and ``await`` syntax. For the
template designer, this support (when enabled) is entirely transparent,
templates continue to look exactly the same. However, developers should
be aware of the implementation as it affects what types of APIs you can
use.
By default, async support is disabled. Enabling it will cause the
environment to compile different code behind the scenes in order to
handle async and sync code in an asyncio event loop. This has the
following implications:
- Template rendering requires an event loop to be available to the
current thread. :func:`asyncio.get_running_loop` must return an
event loop.
- The compiled code uses ``await`` for functions and attributes, and
uses ``async for`` loops. In order to support using both async and
sync functions in this context, a small wrapper is placed around
all calls and access, which adds overhead compared to purely async
code.
- Sync methods and filters become wrappers around their corresponding
async implementations where needed. For example, ``render`` invokes
``async_render``, and ``|map`` supports async iterables.
Awaitable objects can be returned from functions in templates and any
function call in a template will automatically await the result. The
``await`` you would normally add in Python is implied. For example, you
can provide a method that asynchronously loads data from a database, and
from the template designer's point of view it can be called like any
other function.
.. _policies:
Policies
--------
Starting with Jinja 2.9 policies can be configured on the environment
which can slightly influence how filters and other template constructs
behave. They can be configured with the
:attr:`~jinja2.Environment.policies` attribute.
Example::
env.policies['urlize.rel'] = 'nofollow noopener'
``truncate.leeway``:
Configures the leeway default for the `truncate` filter. Leeway as
introduced in 2.9 but to restore compatibility with older templates
it can be configured to `0` to get the old behavior back. The default
is `5`.
``urlize.rel``:
A string that defines the items for the `rel` attribute of generated
links with the `urlize` filter. These items are always added. The
default is `noopener`.
``urlize.target``:
The default target that is issued for links from the `urlize` filter
if no other target is defined by the call explicitly.
``urlize.extra_schemes``:
Recognize URLs that start with these schemes in addition to the
default ``http://``, ``https://``, and ``mailto:``.
``json.dumps_function``:
If this is set to a value other than `None` then the `tojson` filter
will dump with this function instead of the default one. Note that
this function should accept arbitrary extra arguments which might be
passed in the future from the filter. Currently the only argument
that might be passed is `indent`. The default dump function is
``json.dumps``.
``json.dumps_kwargs``:
Keyword arguments to be passed to the dump function. The default is
``{'sort_keys': True}``.
.. _ext-i18n-trimmed:
``ext.i18n.trimmed``:
If this is set to `True`, ``{% trans %}`` blocks of the
:ref:`i18n-extension` will always unify linebreaks and surrounding
whitespace as if the `trimmed` modifier was used.
Utilities
---------
These helper functions and classes are useful if you add custom filters or
functions to a Jinja environment.
.. autofunction:: jinja2.pass_context
.. autofunction:: jinja2.pass_eval_context
.. autofunction:: jinja2.pass_environment
.. autofunction:: jinja2.clear_caches
.. autofunction:: jinja2.is_undefined
Exceptions
----------
.. autoexception:: jinja2.TemplateError
.. autoexception:: jinja2.UndefinedError
.. autoexception:: jinja2.TemplateNotFound
.. autoexception:: jinja2.TemplatesNotFound
.. autoexception:: jinja2.TemplateSyntaxError
.. attribute:: message
The error message.
.. attribute:: lineno
The line number where the error occurred.
.. attribute:: name
The load name for the template.
.. attribute:: filename
The filename that loaded the template in the encoding of the
file system (most likely utf-8, or mbcs on Windows systems).
.. autoexception:: jinja2.TemplateRuntimeError
.. autoexception:: jinja2.TemplateAssertionError
.. _writing-filters:
Custom Filters
--------------
Filters are Python functions that take the value to the left of the
filter as the first argument and produce a new value. Arguments passed
to the filter are passed after the value.
For example, the filter ``{{ 42|myfilter(23) }}`` is called behind the
scenes as ``myfilter(42, 23)``.
Jinja comes with some :ref:`built-in filters <builtin-filters>`. To use
a custom filter, write a function that takes at least a ``value``
argument, then register it in :attr:`Environment.filters`.
Here's a filter that formats datetime objects:
.. code-block:: python
def datetime_format(value, format="%H:%M %d-%m-%y"):
return value.strftime(format)
environment.filters["datetime_format"] = datetime_format
Now it can be used in templates:
.. sourcecode:: jinja
{{ article.pub_date|datetimeformat }}
{{ article.pub_date|datetimeformat("%B %Y") }}
Some decorators are available to tell Jinja to pass extra information to
the filter. The object is passed as the first argument, making the value
being filtered the second argument.
- :func:`pass_environment` passes the :class:`Environment`.
- :func:`pass_eval_context` passes the :ref:`eval-context`.
- :func:`pass_context` passes the current
:class:`~jinja2.runtime.Context`.
Here's a filter that converts line breaks into HTML ``<br>`` and ``<p>``
tags. It uses the eval context to check if autoescape is currently
enabled before escaping the input and marking the output safe.
.. code-block:: python
import re
from jinja2 import pass_eval_context
from markupsafe import Markup, escape
@pass_eval_context
def nl2br(eval_ctx, value):
br = "<br>\n"
if eval_ctx.autoescape:
value = escape(value)
br = Markup(br)
result = "\n\n".join(
f"<p>{br.join(p.splitlines())}<\p>"
for p in re.split(r"(?:\r\n|\r(?!\n)|\n){2,}", value)
)
return Markup(result) if autoescape else result
.. _writing-tests:
Custom Tests
------------
Test are Python functions that take the value to the left of the test as
the first argument, and return ``True`` or ``False``. Arguments passed
to the test are passed after the value.
For example, the test ``{{ 42 is even }}`` is called behind the scenes
as ``is_even(42)``.
Jinja comes with some :ref:`built-in tests <builtin-tests>`. To use a
custom tests, write a function that takes at least a ``value`` argument,
then register it in :attr:`Environment.tests`.
Here's a test that checks if a value is a prime number:
.. code-block:: python
import math
def is_prime(n):
if n == 2:
return True
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
if n % i == 0:
return False
return True
environment.tests["prime"] = is_prime
Now it can be used in templates:
.. sourcecode:: jinja
{% if value is prime %}
{{ value }} is a prime number
{% else %}
{{ value }} is not a prime number
{% endif %}
Some decorators are available to tell Jinja to pass extra information to
the filter. The object is passed as the first argument, making the value
being filtered the second argument.
- :func:`pass_environment` passes the :class:`Environment`.
- :func:`pass_eval_context` passes the :ref:`eval-context`.
- :func:`pass_context` passes the current
:class:`~jinja2.runtime.Context`.
.. _eval-context:
Evaluation Context
------------------
The evaluation context (short eval context or eval ctx) makes it
possible to activate and deactivate compiled features at runtime.
Currently it is only used to enable and disable automatic escaping, but
it can be used by extensions as well.
The ``autoescape`` setting should be checked on the evaluation context,
not the environment. The evaluation context will have the computed value
for the current template.
Instead of ``pass_environment``:
.. code-block:: python
@pass_environment
def filter(env, value):
result = do_something(value)
if env.autoescape:
result = Markup(result)
return result
Use ``pass_eval_context`` if you only need the setting:
.. code-block:: python
@pass_eval_context
def filter(eval_ctx, value):
result = do_something(value)
if eval_ctx.autoescape:
result = Markup(result)
return result
Or use ``pass_context`` if you need other context behavior as well:
.. code-block:: python
@pass_context
def filter(context, value):
result = do_something(value)
if context.eval_ctx.autoescape:
result = Markup(result)
return result
The evaluation context must not be modified at runtime. Modifications
must only happen with a :class:`nodes.EvalContextModifier` and
:class:`nodes.ScopedEvalContextModifier` from an extension, not on the
eval context object itself.
.. autoclass:: jinja2.nodes.EvalContext
.. attribute:: autoescape
`True` or `False` depending on if autoescaping is active or not.
.. attribute:: volatile
`True` if the compiler cannot evaluate some expressions at compile
time. At runtime this should always be `False`.
.. _global-namespace:
The Global Namespace
--------------------
The global namespace stores variables and functions that should be
available without needing to pass them to :meth:`Template.render`. They
are also available to templates that are imported or included without
context. Most applications should only use :attr:`Environment.globals`.
:attr:`Environment.globals` are intended for data that is common to all
templates loaded by that environment. :attr:`Template.globals` are
intended for data that is common to all renders of that template, and
default to :attr:`Environment.globals` unless they're given in
:meth:`Environment.get_template`, etc. Data that is specific to a
render should be passed as context to :meth:`Template.render`.
Only one set of globals is used during any specific rendering. If
templates A and B both have template globals, and B extends A, then
only B's globals are used for both when using ``b.render()``.
Environment globals should not be changed after loading any templates,
and template globals should not be changed at any time after loading the
template. Changing globals after loading a template will result in
unexpected behavior as they may be shared between the environment and
other templates.
.. _low-level-api:
Low Level API
-------------
The low level API exposes functionality that can be useful to understand some
implementation details, debugging purposes or advanced :ref:`extension
<jinja-extensions>` techniques. Unless you know exactly what you are doing we
don't recommend using any of those.
.. automethod:: Environment.lex
.. automethod:: Environment.parse
.. automethod:: Environment.preprocess
.. automethod:: Template.new_context
.. method:: Template.root_render_func(context)
This is the low level render function. It's passed a :class:`Context`
that has to be created by :meth:`new_context` of the same template or
a compatible template. This render function is generated by the
compiler from the template code and returns a generator that yields
strings.
If an exception in the template code happens the template engine will
not rewrite the exception but pass through the original one. As a
matter of fact this function should only be called from within a
:meth:`render` / :meth:`generate` / :meth:`stream` call.
.. attribute:: Template.blocks
A dict of block render functions. Each of these functions works exactly
like the :meth:`root_render_func` with the same limitations.
.. attribute:: Template.is_up_to_date
This attribute is `False` if there is a newer version of the template
available, otherwise `True`.
.. admonition:: Note
The low-level API is fragile. Future Jinja versions will try not to
change it in a backwards incompatible way but modifications in the Jinja
core may shine through. For example if Jinja introduces a new AST node
in later versions that may be returned by :meth:`~Environment.parse`.
The Meta API
------------
.. versionadded:: 2.2
The meta API returns some information about abstract syntax trees that
could help applications to implement more advanced template concepts. All
the functions of the meta API operate on an abstract syntax tree as
returned by the :meth:`Environment.parse` method.
.. autofunction:: jinja2.meta.find_undeclared_variables
.. autofunction:: jinja2.meta.find_referenced_templates
-4
View File
@@ -1,4 +0,0 @@
Changes
=======
.. include:: ../CHANGES.rst
-53
View File
@@ -1,53 +0,0 @@
from pallets_sphinx_themes import get_version
from pallets_sphinx_themes import ProjectLink
# Project --------------------------------------------------------------
project = "Jinja"
copyright = "2007 Pallets"
author = "Pallets"
release, version = get_version("Jinja2")
# General --------------------------------------------------------------
master_doc = "index"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"pallets_sphinx_themes",
"sphinxcontrib.log_cabinet",
"sphinx_issues",
]
autodoc_typehints = "description"
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
issues_github_path = "pallets/jinja"
# HTML -----------------------------------------------------------------
html_theme = "jinja"
html_theme_options = {"index_sidebar_logo": False}
html_context = {
"project_links": [
ProjectLink("Donate", "https://palletsprojects.com/donate"),
ProjectLink("PyPI Releases", "https://pypi.org/project/Jinja2/"),
ProjectLink("Source Code", "https://github.com/pallets/jinja/"),
ProjectLink("Issue Tracker", "https://github.com/pallets/jinja/issues/"),
ProjectLink("Website", "https://palletsprojects.com/p/jinja/"),
ProjectLink("Twitter", "https://twitter.com/PalletsTeam"),
ProjectLink("Chat", "https://discord.gg/pallets"),
]
}
html_sidebars = {
"index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"],
"**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"],
}
singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]}
html_static_path = ["_static"]
html_favicon = "_static/jinja-logo-sidebar.png"
html_logo = "_static/jinja-logo-sidebar.png"
html_title = f"Jinja Documentation ({version})"
html_show_sourcelink = False
# LaTeX ----------------------------------------------------------------
latex_documents = [(master_doc, f"Jinja-{version}.tex", html_title, author, "manual")]
-54
View File
@@ -1,54 +0,0 @@
from jinja2 import nodes
from jinja2.ext import Extension
class FragmentCacheExtension(Extension):
# a set of names that trigger the extension.
tags = {"cache"}
def __init__(self, environment):
super().__init__(environment)
# add the defaults to the environment
environment.extend(fragment_cache_prefix="", fragment_cache=None)
def parse(self, parser):
# the first token is the token that started the tag. In our case
# we only listen to ``'cache'`` so this will be a name token with
# `cache` as value. We get the line number so that we can give
# that line number to the nodes we create by hand.
lineno = next(parser.stream).lineno
# now we parse a single expression that is used as cache key.
args = [parser.parse_expression()]
# if there is a comma, the user provided a timeout. If not use
# None as second parameter.
if parser.stream.skip_if("comma"):
args.append(parser.parse_expression())
else:
args.append(nodes.Const(None))
# now we parse the body of the cache block up to `endcache` and
# drop the needle (which would always be `endcache` in that case)
body = parser.parse_statements(["name:endcache"], drop_needle=True)
# now return a `CallBlock` node that calls our _cache_support
# helper method on this extension.
return nodes.CallBlock(
self.call_method("_cache_support", args), [], [], body
).set_lineno(lineno)
def _cache_support(self, name, timeout, caller):
"""Helper callback."""
key = self.environment.fragment_cache_prefix + name
# try to load the block from the cache
# if there is no fragment in the cache, render it and store
# it in the cache.
rv = self.environment.fragment_cache.get(key)
if rv is not None:
return rv
rv = caller()
self.environment.fragment_cache.add(key, rv, timeout)
return rv
-72
View File
@@ -1,72 +0,0 @@
import re
from jinja2.exceptions import TemplateSyntaxError
from jinja2.ext import Extension
from jinja2.lexer import count_newlines
from jinja2.lexer import Token
_outside_re = re.compile(r"\\?(gettext|_)\(")
_inside_re = re.compile(r"\\?[()]")
class InlineGettext(Extension):
"""This extension implements support for inline gettext blocks::
<h1>_(Welcome)</h1>
<p>_(This is a paragraph)</p>
Requires the i18n extension to be loaded and configured.
"""
def filter_stream(self, stream):
paren_stack = 0
for token in stream:
if token.type != "data":
yield token
continue
pos = 0
lineno = token.lineno
while True:
if not paren_stack:
match = _outside_re.search(token.value, pos)
else:
match = _inside_re.search(token.value, pos)
if match is None:
break
new_pos = match.start()
if new_pos > pos:
preval = token.value[pos:new_pos]
yield Token(lineno, "data", preval)
lineno += count_newlines(preval)
gtok = match.group()
if gtok[0] == "\\":
yield Token(lineno, "data", gtok[1:])
elif not paren_stack:
yield Token(lineno, "block_begin", None)
yield Token(lineno, "name", "trans")
yield Token(lineno, "block_end", None)
paren_stack = 1
else:
if gtok == "(" or paren_stack > 1:
yield Token(lineno, "data", gtok)
paren_stack += -1 if gtok == ")" else 1
if not paren_stack:
yield Token(lineno, "block_begin", None)
yield Token(lineno, "name", "endtrans")
yield Token(lineno, "block_end", None)
pos = match.end()
if pos < len(token.value):
yield Token(lineno, "data", token.value[pos:])
if paren_stack:
raise TemplateSyntaxError(
"unclosed gettext expression",
token.lineno,
stream.name,
stream.filename,
)
-423
View File
@@ -1,423 +0,0 @@
.. _jinja-extensions:
Extensions
==========
Jinja supports extensions that can add extra filters, tests, globals or even
extend the parser. The main motivation of extensions is to move often used
code into a reusable class like adding support for internationalization.
Adding Extensions
-----------------
Extensions are added to the Jinja environment at creation time. To add an
extension pass a list of extension classes or import paths to the
``extensions`` parameter of the :class:`~jinja2.Environment` constructor. The following
example creates a Jinja environment with the i18n extension loaded::
jinja_env = Environment(extensions=['jinja2.ext.i18n'])
To add extensions after creation time, use the :meth:`~jinja2.Environment.add_extension` method::
jinja_env.add_extension('jinja2.ext.debug')
.. _i18n-extension:
i18n Extension
--------------
**Import name:** ``jinja2.ext.i18n``
The i18n extension can be used in combination with `gettext`_ or
`Babel`_. When it's enabled, Jinja provides a ``trans`` statement that
marks a block as translatable and calls ``gettext``.
After enabling, an application has to provide functions for ``gettext``,
``ngettext``, and optionally ``pgettext`` and ``npgettext``, either
globally or when rendering. A ``_()`` function is added as an alias to
the ``gettext`` function.
Environment Methods
~~~~~~~~~~~~~~~~~~~
After enabling the extension, the environment provides the following
additional methods:
.. method:: jinja2.Environment.install_gettext_translations(translations, newstyle=False)
Installs a translation globally for the environment. The
``translations`` object must implement ``gettext``, ``ngettext``,
and optionally ``pgettext`` and ``npgettext``.
:class:`gettext.NullTranslations`, :class:`gettext.GNUTranslations`,
and `Babel`_\s ``Translations`` are supported.
.. versionchanged:: 3.0
Added ``pgettext`` and ``npgettext``.
.. versionchanged:: 2.5
Added new-style gettext support.
.. method:: jinja2.Environment.install_null_translations(newstyle=False)
Install no-op gettext functions. This is useful if you want to
prepare the application for internationalization but don't want to
implement the full system yet.
.. versionchanged:: 2.5 Added new-style gettext support.
.. method:: jinja2.Environment.install_gettext_callables(gettext, ngettext, newstyle=False, pgettext=None, npgettext=None)
Install the given ``gettext``, ``ngettext``, ``pgettext``, and
``npgettext`` callables into the environment. They should behave
exactly like :func:`gettext.gettext`, :func:`gettext.ngettext`,
:func:`gettext.pgettext` and :func:`gettext.npgettext`.
If ``newstyle`` is activated, the callables are wrapped to work like
newstyle callables. See :ref:`newstyle-gettext` for more information.
.. versionchanged:: 3.0
Added ``pgettext`` and ``npgettext``.
.. versionadded:: 2.5
Added new-style gettext support.
.. method:: jinja2.Environment.uninstall_gettext_translations()
Uninstall the environment's globally installed translation.
.. method:: jinja2.Environment.extract_translations(source)
Extract localizable strings from the given template node or source.
For every string found this function yields a ``(lineno, function,
message)`` tuple, where:
- ``lineno`` is the number of the line on which the string was
found.
- ``function`` is the name of the ``gettext`` function used (if
the string was extracted from embedded Python code).
- ``message`` is the string itself, or a tuple of strings for
functions with multiple arguments.
If `Babel`_ is installed, see :ref:`babel-integration` to extract
the strings.
For a web application that is available in multiple languages but gives
all the users the same language (for example, multilingual forum
software installed for a French community), the translation may be
installed when the environment is created.
.. code-block:: python
translations = get_gettext_translations()
env = Environment(extensions=["jinja2.ext.i18n"])
env.install_gettext_translations(translations)
The ``get_gettext_translations`` function would return the translator
for the current configuration, for example by using ``gettext.find``.
The usage of the ``i18n`` extension for template designers is covered in
:ref:`the template documentation <i18n-in-templates>`.
.. _gettext: https://docs.python.org/3/library/gettext.html
.. _Babel: https://babel.pocoo.org/
Whitespace Trimming
~~~~~~~~~~~~~~~~~~~
.. versionadded:: 2.10
Within ``{% trans %}`` blocks, it can be useful to trim line breaks and
whitespace so that the block of text looks like a simple string with
single spaces in the translation file.
Linebreaks and surrounding whitespace can be automatically trimmed by
enabling the ``ext.i18n.trimmed`` :ref:`policy <ext-i18n-trimmed>`.
.. _newstyle-gettext:
New Style Gettext
~~~~~~~~~~~~~~~~~
.. versionadded:: 2.5
New style gettext calls are less to type, less error prone, and support
autoescaping better.
You can use "new style" gettext calls by setting
``env.newstyle_gettext = True`` or passing ``newstyle=True`` to
``env.install_translations``. They are fully supported by the Babel
extraction tool, but might not work as expected with other extraction
tools.
With standard ``gettext`` calls, string formatting is a separate step
done with the ``|format`` filter. This requires duplicating work for
``ngettext`` calls.
.. sourcecode:: jinja
{{ gettext("Hello, World!") }}
{{ gettext("Hello, %(name)s!")|format(name=name) }}
{{ ngettext(
"%(num)d apple", "%(num)d apples", apples|count
)|format(num=apples|count) }}
{{ pgettext("greeting", "Hello, World!") }}
{{ npgettext(
"fruit", "%(num)d apple", "%(num)d apples", apples|count
)|format(num=apples|count) }}
New style ``gettext`` make formatting part of the call, and behind the
scenes enforce more consistency.
.. sourcecode:: jinja
{{ gettext("Hello, World!") }}
{{ gettext("Hello, %(name)s!", name=name) }}
{{ ngettext("%(num)d apple", "%(num)d apples", apples|count) }}
{{ pgettext("greeting", "Hello, World!") }}
{{ npgettext("fruit", "%(num)d apple", "%(num)d apples", apples|count) }}
The advantages of newstyle gettext are:
- There's no separate formatting step, you don't have to remember to
use the ``|format`` filter.
- Only named placeholders are allowed. This solves a common problem
translators face because positional placeholders can't switch
positions meaningfully. Named placeholders always carry semantic
information about what value goes where.
- String formatting is used even if no placeholders are used, which
makes all strings use a consistent format. Remember to escape any
raw percent signs as ``%%``, such as ``100%%``.
- The translated string is marked safe, formatting performs escaping
as needed. Mark a parameter as ``|safe`` if it has already been
escaped.
Expression Statement
--------------------
**Import name:** ``jinja2.ext.do``
The "do" aka expression-statement extension adds a simple ``do`` tag to the
template engine that works like a variable expression but ignores the
return value.
.. _loopcontrols-extension:
Loop Controls
-------------
**Import name:** ``jinja2.ext.loopcontrols``
This extension adds support for ``break`` and ``continue`` in loops. After
enabling, Jinja provides those two keywords which work exactly like in
Python.
.. _with-extension:
With Statement
--------------
**Import name:** ``jinja2.ext.with_``
.. versionchanged:: 2.9
This extension is now built-in and no longer does anything.
.. _autoescape-extension:
Autoescape Extension
--------------------
**Import name:** ``jinja2.ext.autoescape``
.. versionchanged:: 2.9
This extension was removed and is now built-in. Enabling the
extension no longer does anything.
.. _debug-extension:
Debug Extension
---------------
**Import name:** ``jinja2.ext.debug``
Adds a ``{% debug %}`` tag to dump the current context as well as the
available filters and tests. This is useful to see what's available to
use in the template without setting up a debugger.
.. _writing-extensions:
Writing Extensions
------------------
.. module:: jinja2.ext
By writing extensions you can add custom tags to Jinja. This is a non-trivial
task and usually not needed as the default tags and expressions cover all
common use cases. The i18n extension is a good example of why extensions are
useful. Another one would be fragment caching.
When writing extensions you have to keep in mind that you are working with the
Jinja template compiler which does not validate the node tree you are passing
to it. If the AST is malformed you will get all kinds of compiler or runtime
errors that are horrible to debug. Always make sure you are using the nodes
you create correctly. The API documentation below shows which nodes exist and
how to use them.
Example Extensions
------------------
Cache
~~~~~
The following example implements a ``cache`` tag for Jinja by using the
`cachelib`_ library:
.. literalinclude:: examples/cache_extension.py
:language: python
And here is how you use it in an environment::
from jinja2 import Environment
from cachelib import SimpleCache
env = Environment(extensions=[FragmentCacheExtension])
env.fragment_cache = SimpleCache()
Inside the template it's then possible to mark blocks as cacheable. The
following example caches a sidebar for 300 seconds:
.. sourcecode:: html+jinja
{% cache 'sidebar', 300 %}
<div class="sidebar">
...
</div>
{% endcache %}
.. _cachelib: https://github.com/pallets/cachelib
Inline ``gettext``
~~~~~~~~~~~~~~~~~~
The following example demonstrates using :meth:`Extension.filter_stream`
to parse calls to the ``_()`` gettext function inline with static data
without needing Jinja blocks.
.. code-block:: html
<h1>_(Welcome)</h1>
<p>_(This is a paragraph)</p>
It requires the i18n extension to be loaded and configured.
.. literalinclude:: examples/inline_gettext_extension.py
:language: python
Extension API
-------------
Extension
~~~~~~~~~
Extensions always have to extend the :class:`jinja2.ext.Extension` class:
.. autoclass:: Extension
:members: preprocess, filter_stream, parse, attr, call_method
.. attribute:: identifier
The identifier of the extension. This is always the true import name
of the extension class and must not be changed.
.. attribute:: tags
If the extension implements custom tags this is a set of tag names
the extension is listening for.
Parser
~~~~~~
The parser passed to :meth:`Extension.parse` provides ways to parse
expressions of different types. The following methods may be used by
extensions:
.. autoclass:: jinja2.parser.Parser
:members: parse_expression, parse_tuple, parse_assign_target,
parse_statements, free_identifier, fail
.. attribute:: filename
The filename of the template the parser processes. This is **not**
the load name of the template. For the load name see :attr:`name`.
For templates that were not loaded form the file system this is
``None``.
.. attribute:: name
The load name of the template.
.. attribute:: stream
The current :class:`~jinja2.lexer.TokenStream`
.. autoclass:: jinja2.lexer.TokenStream
:members: push, look, eos, skip, __next__, next_if, skip_if, expect
.. attribute:: current
The current :class:`~jinja2.lexer.Token`.
.. autoclass:: jinja2.lexer.Token
:members: test, test_any
.. attribute:: lineno
The line number of the token
.. attribute:: type
The type of the token. This string is interned so you may compare
it with arbitrary strings using the ``is`` operator.
.. attribute:: value
The value of the token.
There is also a utility function in the lexer module that can count newline
characters in strings:
.. autofunction:: jinja2.lexer.count_newlines
AST
~~~
The AST (Abstract Syntax Tree) is used to represent a template after parsing.
It's build of nodes that the compiler then converts into executable Python
code objects. Extensions that provide custom statements can return nodes to
execute custom Python code.
The list below describes all nodes that are currently available. The AST may
change between Jinja versions but will stay backwards compatible.
For more information have a look at the repr of :meth:`jinja2.Environment.parse`.
.. module:: jinja2.nodes
.. jinja:nodes:: jinja2.nodes.Node
.. autoexception:: Impossible
-75
View File
@@ -1,75 +0,0 @@
Frequently Asked Questions
==========================
Why is it called Jinja?
-----------------------
"Jinja" is a Japanese `Shinto shrine`_, or temple, and temple and
template share a similar English pronunciation. It is not named after
the `city in Uganda`_.
.. _Shinto shrine: https://en.wikipedia.org/wiki/Shinto_shrine
.. _city in Uganda: https://en.wikipedia.org/wiki/Jinja%2C_Uganda
How fast is Jinja?
------------------
Jinja is relatively fast among template engines because it compiles and
caches template code to Python code, so that the template does not need
to be parsed and interpreted each time. Rendering a template becomes as
close to executing a Python function as possible.
Jinja also makes extensive use of caching. Templates are cached by name
after loading, so future uses of the template avoid loading. The
template loading itself uses a bytecode cache to avoid repeated
compiling. The caches can be external to persist across restarts.
Templates can also be precompiled and loaded as fast Python imports.
We dislike benchmarks because they don't reflect real use. Performance
depends on many factors. Different engines have different default
configurations and tradeoffs that make it unclear how to set up a useful
comparison. Often, database access, API calls, and data processing have
a much larger effect on performance than the template engine.
Isn't it a bad idea to put logic in templates?
----------------------------------------------
Without a doubt you should try to remove as much logic from templates as
possible. With less logic, the template is easier to understand, has
fewer potential side effects, and is faster to compile and render. But a
template without any logic means processing must be done in code before
rendering. A template engine that does that is shipped with Python,
called :class:`string.Template`, and while it's definitely fast it's not
convenient.
Jinja's features such as blocks, statements, filters, and function calls
make it much easier to write expressive templates, with very few
restrictions. Jinja doesn't allow arbitrary Python code in templates, or
every feature available in the Python language. This keeps the engine
easier to maintain, and keeps templates more readable.
Some amount of logic is required in templates to keep everyone happy.
Too much logic in the template can make it complex to reason about and
maintain. It's up to you to decide how your application will work and
balance how much logic you want to put in the template.
Why is HTML escaping not the default?
-------------------------------------
Jinja provides a feature that can be enabled to escape HTML syntax in
rendered templates. However, it is disabled by default.
Jinja is a general purpose template engine, it is not only used for HTML
documents. You can generate plain text, LaTeX, emails, CSS, JavaScript,
configuration files, etc. HTML escaping wouldn't make sense for any of
these document types.
While automatic escaping means that you are less likely have an XSS
problem, it also requires significant extra processing during compiling
and rendering, which can reduce performance. Jinja uses MarkupSafe for
escaping, which provides optimized C code for speed, but it still
introduces overhead to track escaping across methods and formatting.
-29
View File
@@ -1,29 +0,0 @@
.. rst-class:: hide-header
Jinja
=====
.. image:: _static/jinja-logo.png
:align: center
:target: https://palletsprojects.com/p/jinja/
Jinja is a fast, expressive, extensible templating engine. Special
placeholders in the template allow writing code similar to Python
syntax. Then the template is passed data to render the final document.
.. toctree::
:maxdepth: 2
:caption: Contents:
intro
api
sandbox
nativetypes
templates
extensions
integration
switching
tricks
faq
license
changes
-94
View File
@@ -1,94 +0,0 @@
Integration
===========
Flask
-----
The `Flask`_ web application framework, also maintained by Pallets, uses
Jinja templates by default. Flask sets up a Jinja environment and
template loader for you, and provides functions to easily render
templates from view functions.
.. _Flask: https://flask.palletsprojects.com
Django
------
Django supports using Jinja as its template engine, see
https://docs.djangoproject.com/en/stable/topics/templates/#support-for-template-engines.
.. _babel-integration:
Babel
-----
Jinja provides support for extracting gettext messages from templates
via a `Babel`_ extractor entry point called
``jinja2.ext.babel_extract``. The support is implemented as part of the
:ref:`i18n-extension` extension.
Gettext messages are extracted from both ``trans`` tags and code
expressions.
To extract gettext messages from templates, the project needs a Jinja
section in its Babel extraction method `mapping file`_:
.. sourcecode:: ini
[jinja2: **/templates/**.html]
encoding = utf-8
The syntax related options of the :class:`Environment` are also
available as configuration values in the mapping file. For example, to
tell the extractor that templates use ``%`` as
``line_statement_prefix`` you can use this code:
.. sourcecode:: ini
[jinja2: **/templates/**.html]
encoding = utf-8
line_statement_prefix = %
:ref:`jinja-extensions` may also be defined by passing a comma separated
list of import paths as the ``extensions`` value. The i18n extension is
added automatically.
Template syntax errors are ignored by default. The assumption is that
tests will catch syntax errors in templates. If you don't want to ignore
errors, add ``silent = false`` to the settings.
.. _Babel: https://babel.readthedocs.io/
.. _mapping file: https://babel.readthedocs.io/en/latest/messages.html#extraction-method-mapping-and-configuration
Pylons
------
It's easy to integrate Jinja into a `Pylons`_ application.
The template engine is configured in ``config/environment.py``. The
configuration for Jinja looks something like this:
.. code-block:: python
from jinja2 import Environment, PackageLoader
config['pylons.app_globals'].jinja_env = Environment(
loader=PackageLoader('yourapplication', 'templates')
)
After that you can render Jinja templates by using the ``render_jinja``
function from the ``pylons.templating`` module.
Additionally it's a good idea to set the Pylons ``c`` object to strict
mode. By default attribute access on missing attributes on the ``c``
object returns an empty string and not an undefined object. To change
this add this to ``config/environment.py``:
.. code-block:: python
config['pylons.strict_c'] = True
.. _Pylons: https://pylonshq.com/
-63
View File
@@ -1,63 +0,0 @@
Introduction
============
Jinja is a fast, expressive, extensible templating engine. Special
placeholders in the template allow writing code similar to Python
syntax. Then the template is passed data to render the final document.
It includes:
- Template inheritance and inclusion.
- Define and import macros within templates.
- HTML templates can use autoescaping to prevent XSS from untrusted
user input.
- A sandboxed environment can safely render untrusted templates.
- Async support for generating templates that automatically handle
sync and async functions without extra syntax.
- I18N support with Babel.
- Templates are compiled to optimized Python code just-in-time and
cached, or can be compiled ahead-of-time.
- Exceptions point to the correct line in templates to make debugging
easier.
- Extensible filters, tests, functions, and even syntax.
Jinja's philosophy is that while application logic belongs in Python if
possible, it shouldn't make the template designer's job difficult by
restricting functionality too much.
Installation
------------
We recommend using the latest version of Python. Jinja supports Python
3.7 and newer. We also recommend using a `virtual environment`_ in order
to isolate your project dependencies from other projects and the system.
.. _virtual environment: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments
Install the most recent Jinja version using pip:
.. code-block:: text
$ pip install Jinja2
Dependencies
~~~~~~~~~~~~
These will be installed automatically when installing Jinja.
- `MarkupSafe`_ escapes untrusted input when rendering templates to
avoid injection attacks.
.. _MarkupSafe: https://markupsafe.palletsprojects.com/
Optional Dependencies
~~~~~~~~~~~~~~~~~~~~~
These distributions will not be installed automatically.
- `Babel`_ provides translation support in templates.
.. _Babel: https://babel.pocoo.org/
-4
View File
@@ -1,4 +0,0 @@
BSD-3-Clause License
====================
.. include:: ../LICENSE.rst
-35
View File
@@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
-64
View File
@@ -1,64 +0,0 @@
.. module:: jinja2.nativetypes
.. _nativetypes:
Native Python Types
===================
The default :class:`~jinja2.Environment` renders templates to strings. With
:class:`NativeEnvironment`, rendering a template produces a native Python type.
This is useful if you are using Jinja outside the context of creating text
files. For example, your code may have an intermediate step where users may use
templates to define values that will then be passed to a traditional string
environment.
Examples
--------
Adding two values results in an integer, not a string with a number:
>>> env = NativeEnvironment()
>>> t = env.from_string('{{ x + y }}')
>>> result = t.render(x=4, y=2)
>>> print(result)
6
>>> print(type(result))
int
Rendering list syntax produces a list:
>>> t = env.from_string('[{% for item in data %}{{ item + 1 }},{% endfor %}]')
>>> result = t.render(data=range(5))
>>> print(result)
[1, 2, 3, 4, 5]
>>> print(type(result))
list
Rendering something that doesn't look like a Python literal produces a string:
>>> t = env.from_string('{{ x }} * {{ y }}')
>>> result = t.render(x=4, y=2)
>>> print(result)
4 * 2
>>> print(type(result))
str
Rendering a Python object produces that object as long as it is the only node:
>>> class Foo:
... def __init__(self, value):
... self.value = value
...
>>> result = env.from_string('{{ x }}').render(x=Foo(15))
>>> print(type(result).__name__)
Foo
>>> print(result.value)
15
API
---
.. autoclass:: NativeEnvironment([options])
.. autoclass:: NativeTemplate([options])
:members: render
-111
View File
@@ -1,111 +0,0 @@
Sandbox
=======
The Jinja sandbox can be used to render untrusted templates. Access to
attributes, method calls, operators, mutating data structures, and
string formatting can be intercepted and prohibited.
.. code-block:: pycon
>>> from jinja2.sandbox import SandboxedEnvironment
>>> env = SandboxedEnvironment()
>>> func = lambda: "Hello, Sandbox!"
>>> env.from_string("{{ func() }}").render(func=func)
'Hello, Sandbox!'
>>> env.from_string("{{ func.__code__.co_code }}").render(func=func)
Traceback (most recent call last):
...
SecurityError: access to attribute '__code__' of 'function' object is unsafe.
A sandboxed environment can be useful, for example, to allow users of an
internal reporting system to create custom emails. You would document
what data is available in the templates, then the user would write a
template using that information. Your code would generate the report
data and pass it to the user's sandboxed template to render.
Security Considerations
-----------------------
The sandbox alone is not a solution for perfect security. Keep these
things in mind when using the sandbox.
Templates can still raise errors when compiled or rendered. Your code
should attempt to catch errors instead of crashing.
It is possible to construct a relatively small template that renders to
a very large amount of output, which could correspond to a high use of
CPU or memory. You should run your application with limits on resources
such as CPU and memory to mitigate this.
Jinja only renders text, it does not understand, for example, JavaScript
code. Depending on how the rendered template will be used, you may need
to do other postprocessing to restrict the output.
Pass only the data that is relevant to the template. Avoid passing
global data, or objects with methods that have side effects. By default
the sandbox prevents private and internal attribute access. You can
override :meth:`~SandboxedEnvironment.is_safe_attribute` to further
restrict attributes access. Decorate methods with :func:`unsafe` to
prevent calling them from templates when passing objects as data. Use
:class:`ImmutableSandboxedEnvironment` to prevent modifying lists and
dictionaries.
API
---
.. module:: jinja2.sandbox
.. autoclass:: SandboxedEnvironment([options])
:members: is_safe_attribute, is_safe_callable, default_binop_table,
default_unop_table, intercepted_binops, intercepted_unops,
call_binop, call_unop
.. autoclass:: ImmutableSandboxedEnvironment([options])
.. autoexception:: SecurityError
.. autofunction:: unsafe
.. autofunction:: is_internal_attribute
.. autofunction:: modifies_known_mutable
Operator Intercepting
---------------------
For performance, Jinja outputs operators directly when compiling. This
means it's not possible to intercept operator behavior by overriding
:meth:`SandboxEnvironment.call <Environment.call>` by default, because
operator special methods are handled by the Python interpreter, and
might not correspond with exactly one method depending on the operator's
use.
The sandbox can instruct the compiler to output a function to intercept
certain operators instead. Override
:attr:`SandboxedEnvironment.intercepted_binops` and
:attr:`SandboxedEnvironment.intercepted_unops` with the operator symbols
you want to intercept. The compiler will replace the symbols with calls
to :meth:`SandboxedEnvironment.call_binop` and
:meth:`SandboxedEnvironment.call_unop` instead. The default
implementation of those methods will use
:attr:`SandboxedEnvironment.binop_table` and
:attr:`SandboxedEnvironment.unop_table` to translate operator symbols
into :mod:`operator` functions.
For example, the power (``**``) operator can be disabled:
.. code-block:: python
from jinja2.sandbox import SandboxedEnvironment
class MyEnvironment(SandboxedEnvironment):
intercepted_binops = frozenset(["**"])
def call_binop(self, context, operator, left, right):
if operator == "**":
return self.undefined("The power (**) operator is unavailable.")
return super().call_binop(self, context, operator, left, right)
-181
View File
@@ -1,181 +0,0 @@
Switching From Other Template Engines
=====================================
This is a brief guide on some of the differences between Jinja syntax
and other template languages. See :doc:`/templates` for a comprehensive
guide to Jinja syntax and features.
Django
------
If you have previously worked with Django templates, you should find
Jinja very familiar. Many of the syntax elements look and work the same.
However, Jinja provides some more syntax elements, and some work a bit
differently.
This section covers the template changes. The API, including extension
support, is fundamentally different so it won't be covered here.
Django supports using Jinja as its template engine, see
https://docs.djangoproject.com/en/stable/topics/templates/#support-for-template-engines.
Method Calls
~~~~~~~~~~~~
In Django, methods are called implicitly, without parentheses.
.. code-block:: django
{% for page in user.get_created_pages %}
...
{% endfor %}
In Jinja, using parentheses is required for calls, like in Python. This
allows you to pass variables to the method, which is not possible
in Django. This syntax is also used for calling macros.
.. code-block:: jinja
{% for page in user.get_created_pages() %}
...
{% endfor %}
Filter Arguments
~~~~~~~~~~~~~~~~
In Django, one literal value can be passed to a filter after a colon.
.. code-block:: django
{{ items|join:", " }}
In Jinja, filters can take any number of positional and keyword
arguments in parentheses, like function calls. Arguments can also be
variables instead of literal values.
.. code-block:: jinja
{{ items|join(", ") }}
Tests
~~~~~
In addition to filters, Jinja also has "tests" used with the ``is``
operator. This operator is not the same as the Python operator.
.. code-block:: jinja
{% if user.user_id is odd %}
{{ user.username|e }} is odd
{% else %}
hmm. {{ user.username|e }} looks pretty normal
{% endif %}
Loops
~~~~~
In Django, the special variable for the loop context is called
``forloop``, and the ``empty`` is used for no loop items.
.. code-block:: django
{% for item in items %}
{{ item }}
{% empty %}
No items!
{% endfor %}
In Jinja, the special variable for the loop context is called ``loop``,
and the ``else`` block is used for no loop items.
.. code-block:: jinja
{% for item in items %}
{{ loop.index}}. {{ item }}
{% else %}
No items!
{% endfor %}
Cycle
~~~~~
In Django, the ``{% cycle %}`` can be used in a for loop to alternate
between values per loop.
.. code-block:: django
{% for user in users %}
<li class="{% cycle 'odd' 'even' %}">{{ user }}</li>
{% endfor %}
In Jinja, the ``loop`` context has a ``cycle`` method.
.. code-block:: jinja
{% for user in users %}
<li class="{{ loop.cycle('odd', 'even') }}">{{ user }}</li>
{% endfor %}
A cycler can also be assigned to a variable and used outside or across
loops with the ``cycle()`` global function.
Mako
----
You can configure Jinja to look more like Mako:
.. code-block:: python
env = Environment(
block_start_string="<%",
block_end_string="%>",
variable_start_string="${",
variable_end_string="}",
comment_start_string="<%doc>",
commend_end_string="</%doc>",
line_statement_prefix="%",
line_comment_prefix="##",
)
With an environment configured like that, Jinja should be able to
interpret a small subset of Mako templates without any changes.
Jinja does not support embedded Python code, so you would have to move
that out of the template. You could either process the data with the
same code before rendering, or add a global function or filter to the
Jinja environment.
The syntax for defs (which are called macros in Jinja) and template
inheritance is different too.
The following Mako template:
.. code-block:: mako
<%inherit file="layout.html" />
<%def name="title()">Page Title</%def>
<ul>
% for item in list:
<li>${item}</li>
% endfor
</ul>
Looks like this in Jinja with the above configuration:
.. code-block:: jinja
<% extends "layout.html" %>
<% block title %>Page Title<% endblock %>
<% block body %>
<ul>
% for item in list:
<li>${item}</li>
% endfor
</ul>
<% endblock %>
-1949
View File
File diff suppressed because it is too large Load Diff
-100
View File
@@ -1,100 +0,0 @@
Tips and Tricks
===============
.. highlight:: html+jinja
This part of the documentation shows some tips and tricks for Jinja
templates.
.. _null-default-fallback:
Null-Default Fallback
---------------------
Jinja supports dynamic inheritance and does not distinguish between parent
and child template as long as no `extends` tag is visited. While this leads
to the surprising behavior that everything before the first `extends` tag
including whitespace is printed out instead of being ignored, it can be used
for a neat trick.
Usually child templates extend from one template that adds a basic HTML
skeleton. However it's possible to put the `extends` tag into an `if` tag to
only extend from the layout template if the `standalone` variable evaluates
to false which it does per default if it's not defined. Additionally a very
basic skeleton is added to the file so that if it's indeed rendered with
`standalone` set to `True` a very basic HTML skeleton is added::
{% if not standalone %}{% extends 'default.html' %}{% endif -%}
<!DOCTYPE html>
<title>{% block title %}The Page Title{% endblock %}</title>
<link rel="stylesheet" href="style.css" type="text/css">
{% block body %}
<p>This is the page body.</p>
{% endblock %}
Alternating Rows
----------------
If you want to have different styles for each row of a table or
list you can use the `cycle` method on the `loop` object::
<ul>
{% for row in rows %}
<li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
{% endfor %}
</ul>
`cycle` can take an unlimited number of strings. Each time this
tag is encountered the next item from the list is rendered.
Highlighting Active Menu Items
------------------------------
Often you want to have a navigation bar with an active navigation
item. This is really simple to achieve. Because assignments outside
of `block`\s in child templates are global and executed before the layout
template is evaluated it's possible to define the active menu item in the
child template::
{% extends "layout.html" %}
{% set active_page = "index" %}
The layout template can then access `active_page`. Additionally it makes
sense to define a default for that variable::
{% set navigation_bar = [
('/', 'index', 'Index'),
('/downloads/', 'downloads', 'Downloads'),
('/about/', 'about', 'About')
] -%}
{% set active_page = active_page|default('index') -%}
...
<ul id="navigation">
{% for href, id, caption in navigation_bar %}
<li{% if id == active_page %} class="active"{% endif %}>
<a href="{{ href|e }}">{{ caption|e }}</a></li>
{% endfor %}
</ul>
...
.. _accessing-the-parent-loop:
Accessing the parent Loop
-------------------------
The special `loop` variable always points to the innermost loop. If it's
desired to have access to an outer loop it's possible to alias it::
<table>
{% for row in table %}
<tr>
{% set rowloop = loop %}
{% for cell in row %}
<td id="cell-{{ rowloop.index }}-{{ loop.index }}">{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
-16
View File
@@ -1,16 +0,0 @@
from jinja2 import Environment
env = Environment(
line_statement_prefix="#", variable_start_string="${", variable_end_string="}"
)
print(
env.from_string(
"""\
<ul>
# for item in range(10)
<li class="${loop.cycle('odd', 'even')}">${item}</li>
# endfor
</ul>\
"""
).render()
)
-6
View File
@@ -1,6 +0,0 @@
from jinja2 import Environment
from jinja2.loaders import FileSystemLoader
env = Environment(loader=FileSystemLoader("templates"))
tmpl = env.get_template("broken.html")
print(tmpl.render(seq=[3, 2, 4, 5, 3, 2, 0, 2, 1]))
-13
View File
@@ -1,13 +0,0 @@
from jinja2 import Environment
from jinja2.loaders import DictLoader
env = Environment(
loader=DictLoader(
{
"a": "[A[{% block body %}{% endblock %}]]",
"b": "{% extends 'a' %}{% block body %}[B]{% endblock %}",
"c": "{% extends 'b' %}{% block body %}###{{ super() }}###{% endblock %}",
}
)
)
print(env.get_template("c").render())
-6
View File
@@ -1,6 +0,0 @@
{% from 'subbroken.html' import may_break %}
<ul>
{% for item in seq %}
<li>{{ may_break(item) }}</li>
{% endfor %}
</ul>
-3
View File
@@ -1,3 +0,0 @@
{% macro may_break(item) -%}
[{{ item / 0 }}]
{%- endmacro %}
-29
View File
@@ -1,29 +0,0 @@
from jinja2 import Environment
from jinja2.loaders import DictLoader
env = Environment(
loader=DictLoader(
{
"child.html": """\
{% extends default_layout or 'default.html' %}
{% include helpers = 'helpers.html' %}
{% macro get_the_answer() %}42{% endmacro %}
{% title = 'Hello World' %}
{% block body %}
{{ get_the_answer() }}
{{ helpers.conspirate() }}
{% endblock %}
""",
"default.html": """\
<!doctype html>
<title>{{ title }}</title>
{% block body %}{% endblock %}
""",
"helpers.html": """\
{% macro conspirate() %}23{% endmacro %}
""",
}
)
)
tmpl = env.get_template("child.html")
print(tmpl.render())
@@ -1,27 +0,0 @@
from jinja2 import Environment
env = Environment(
line_statement_prefix="%", variable_start_string="${", variable_end_string="}"
)
tmpl = env.from_string(
"""\
% macro foo()
${caller(42)}
% endmacro
<ul>
% for item in seq
<li>${item}</li>
% endfor
</ul>
% call(var) foo()
[${var}]
% endcall
% filter escape
<hello world>
% for item in [1, 2, 3]
- ${item}
% endfor
% endfilter
"""
)
print(tmpl.render(seq=range(10)))
-13
View File
@@ -1,13 +0,0 @@
from jinja2 import Environment
tmpl = Environment().from_string(
"""\
<ul>
{%- for item in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] if item % 2 == 0 %}
<li>{{ loop.index }} / {{ loop.length }}: {{ item }}</li>
{%- endfor %}
</ul>
if condition: {{ 1 if foo else 0 }}
"""
)
print(tmpl.render(foo=True))
-18
View File
@@ -1,18 +0,0 @@
from jinja2 import Environment
env = Environment(extensions=["jinja2.ext.i18n"])
env.globals["gettext"] = {"Hello %(user)s!": "Hallo %(user)s!"}.__getitem__
env.globals["ngettext"] = lambda s, p, n: {
"%(count)s user": "%(count)d Benutzer",
"%(count)s users": "%(count)d Benutzer",
}[s if n == 1 else p]
print(
env.from_string(
"""\
{% trans %}Hello {{ user }}!{% endtrans %}
{% trans count=users|count -%}
{{ count }} user{% pluralize %}{{ count }} users
{% endtrans %}
"""
).render(user="someone", users=[1, 2, 3])
)
View File
View File
View File
View File
View File
View File
-6
View File
@@ -1,6 +0,0 @@
-r docs.in
-r tests.in
-r typing.in
pip-compile-multi
pre-commit
tox
-60
View File
@@ -1,60 +0,0 @@
# SHA1:54b5b77ec8c7a0064ffa93b2fd16cb0130ba177c
#
# This file is autogenerated by pip-compile-multi
# To update, run:
#
# pip-compile-multi
#
-r docs.txt
-r tests.txt
-r typing.txt
cfgv==3.3.1
# via pre-commit
click==8.1.2
# via
# pip-compile-multi
# pip-tools
distlib==0.3.4
# via virtualenv
filelock==3.6.0
# via
# tox
# virtualenv
identify==2.5.0
# via pre-commit
nodeenv==1.6.0
# via pre-commit
pep517==0.12.0
# via pip-tools
pip-compile-multi==2.4.5
# via -r requirements/dev.in
pip-tools==6.6.0
# via pip-compile-multi
platformdirs==2.5.2
# via virtualenv
pre-commit==2.18.1
# via -r requirements/dev.in
pyyaml==6.0
# via pre-commit
six==1.16.0
# via
# tox
# virtualenv
toml==0.10.2
# via
# pre-commit
# tox
toposort==1.7
# via pip-compile-multi
tox==3.25.0
# via -r requirements/dev.in
virtualenv==20.14.1
# via
# pre-commit
# tox
wheel==0.37.1
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
# pip
# setuptools
-4
View File
@@ -1,4 +0,0 @@
Pallets-Sphinx-Themes
Sphinx
sphinx-issues
sphinxcontrib-log-cabinet
-65
View File
@@ -1,65 +0,0 @@
# SHA1:45c590f97fe95b8bdc755eef796e91adf5fbe4ea
#
# This file is autogenerated by pip-compile-multi
# To update, run:
#
# pip-compile-multi
#
alabaster==0.7.12
# via sphinx
babel==2.10.1
# via sphinx
certifi==2021.10.8
# via requests
charset-normalizer==2.0.12
# via requests
docutils==0.17.1
# via sphinx
idna==3.3
# via requests
imagesize==1.3.0
# via sphinx
jinja2==3.1.1
# via sphinx
markupsafe==2.1.1
# via jinja2
packaging==21.3
# via
# pallets-sphinx-themes
# sphinx
pallets-sphinx-themes==2.0.2
# via -r requirements/docs.in
pygments==2.12.0
# via sphinx
pyparsing==3.0.8
# via packaging
pytz==2022.1
# via babel
requests==2.27.1
# via sphinx
snowballstemmer==2.2.0
# via sphinx
sphinx==4.5.0
# via
# -r requirements/docs.in
# pallets-sphinx-themes
# sphinx-issues
# sphinxcontrib-log-cabinet
sphinx-issues==3.0.1
# via -r requirements/docs.in
sphinxcontrib-applehelp==1.0.2
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
sphinxcontrib-htmlhelp==2.0.0
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-log-cabinet==1.0.1
# via -r requirements/docs.in
sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
urllib3==1.26.9
# via requests
-1
View File
@@ -1 +0,0 @@
pytest
-23
View File
@@ -1,23 +0,0 @@
# SHA1:0eaa389e1fdb3a1917c0f987514bd561be5718ee
#
# This file is autogenerated by pip-compile-multi
# To update, run:
#
# pip-compile-multi
#
attrs==21.4.0
# via pytest
iniconfig==1.1.1
# via pytest
packaging==21.3
# via pytest
pluggy==1.0.0
# via pytest
py==1.11.0
# via pytest
pyparsing==3.0.8
# via packaging
pytest==7.1.2
# via -r requirements/tests.in
tomli==2.0.1
# via pytest
-1
View File
@@ -1 +0,0 @@
mypy
-15
View File
@@ -1,15 +0,0 @@
# SHA1:7983aaa01d64547827c20395d77e248c41b2572f
#
# This file is autogenerated by pip-compile-multi
# To update, run:
#
# pip-compile-multi
#
mypy==0.950
# via -r requirements/typing.in
mypy-extensions==0.4.3
# via mypy
tomli==2.0.1
# via mypy
typing-extensions==4.2.0
# via mypy
-74
View File
@@ -1,74 +0,0 @@
import itertools
import os
import re
import sys
def get_characters():
"""Find every Unicode character that is valid in a Python `identifier`_ but
is not matched by the regex ``\\w`` group.
``\\w`` matches some characters that aren't valid in identifiers, but
:meth:`str.isidentifier` will catch that later in lexing.
All start characters are valid continue characters, so we only test for
continue characters.
_identifier: https://docs.python.org/3/reference/lexical_analysis.html#identifiers
"""
for cp in range(sys.maxunicode + 1):
s = chr(cp)
if ("a" + s).isidentifier() and not re.match(r"\w", s):
yield s
def collapse_ranges(data):
"""Given a sorted list of unique characters, generate ranges representing
sequential code points.
Source: https://stackoverflow.com/a/4629241/400617
"""
for _, b in itertools.groupby(enumerate(data), lambda x: ord(x[1]) - x[0]):
b = list(b)
yield b[0][1], b[-1][1]
def build_pattern(ranges):
"""Output the regex pattern for ranges of characters.
One and two character ranges output the individual characters.
"""
out = []
for a, b in ranges:
if a == b: # single char
out.append(a)
elif ord(b) - ord(a) == 1: # two chars, range is redundant
out.append(a)
out.append(b)
else:
out.append(f"{a}-{b}")
return "".join(out)
def main():
"""Build the regex pattern and write it to
``jinja2/_identifier.py``.
"""
pattern = build_pattern(collapse_ranges(get_characters()))
filename = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "src", "jinja2", "_identifier.py")
)
with open(filename, "w", encoding="utf8") as f:
f.write("import re\n\n")
f.write("# generated by scripts/generate_identifier_pattern.py\n")
f.write("pattern = re.compile(\n")
f.write(f' r"[\\w{pattern}]+" # noqa: B950\n')
f.write(")\n")
if __name__ == "__main__":
main()
View File
-49
View File
@@ -1,49 +0,0 @@
from pathlib import Path
import pytest
from jinja2 import loaders
from jinja2.environment import Environment
@pytest.fixture
def env():
"""returns a new environment."""
return Environment()
@pytest.fixture
def dict_loader():
"""returns DictLoader"""
return loaders.DictLoader({"justdict.html": "FOO"})
@pytest.fixture
def package_loader():
"""returns PackageLoader initialized from templates"""
return loaders.PackageLoader("res", "templates")
@pytest.fixture
def filesystem_loader():
"""returns FileSystemLoader initialized to res/templates directory"""
here = Path(__file__).parent.resolve()
return loaders.FileSystemLoader(here / "res" / "templates")
@pytest.fixture
def function_loader():
"""returns a FunctionLoader"""
return loaders.FunctionLoader({"justfunction.html": "FOO"}.get)
@pytest.fixture
def choice_loader(dict_loader, package_loader):
"""returns a ChoiceLoader"""
return loaders.ChoiceLoader([dict_loader, package_loader])
@pytest.fixture
def prefix_loader(filesystem_loader, dict_loader):
"""returns a PrefixLoader"""
return loaders.PrefixLoader({"a": filesystem_loader, "b": dict_loader})
View File
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
Before
{{ fail() }}
After
-1
View File
@@ -1 +0,0 @@
FOO
-1
View File
@@ -1 +0,0 @@
文字化け
-4
View File
@@ -1,4 +0,0 @@
Foo
{% for item in broken %}
...
{% endif %}
-1
View File
@@ -1 +0,0 @@
BAR
-2
View File
@@ -1,2 +0,0 @@
Looks like the start of templates/foo/test.html
Tested by test_filesystem_loader_overlapping_names
-434
View File
@@ -1,434 +0,0 @@
import shutil
import tempfile
from pathlib import Path
import pytest
from jinja2 import ChainableUndefined
from jinja2 import DebugUndefined
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import is_undefined
from jinja2 import make_logging_undefined
from jinja2 import meta
from jinja2 import StrictUndefined
from jinja2 import Template
from jinja2 import TemplatesNotFound
from jinja2 import Undefined
from jinja2 import UndefinedError
from jinja2.compiler import CodeGenerator
from jinja2.runtime import Context
from jinja2.utils import Cycler
from jinja2.utils import pass_context
from jinja2.utils import pass_environment
from jinja2.utils import pass_eval_context
class TestExtendedAPI:
def test_item_and_attribute(self, env):
from jinja2.sandbox import SandboxedEnvironment
for env in Environment(), SandboxedEnvironment():
tmpl = env.from_string("{{ foo.items()|list }}")
assert tmpl.render(foo={"items": 42}) == "[('items', 42)]"
tmpl = env.from_string('{{ foo|attr("items")()|list }}')
assert tmpl.render(foo={"items": 42}) == "[('items', 42)]"
tmpl = env.from_string('{{ foo["items"] }}')
assert tmpl.render(foo={"items": 42}) == "42"
def test_finalize(self):
e = Environment(finalize=lambda v: "" if v is None else v)
t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}")
assert t.render(seq=(None, 1, "foo")) == "||1|foo"
def test_finalize_constant_expression(self):
e = Environment(finalize=lambda v: "" if v is None else v)
t = e.from_string("<{{ none }}>")
assert t.render() == "<>"
def test_no_finalize_template_data(self):
e = Environment(finalize=lambda v: type(v).__name__)
t = e.from_string("<{{ value }}>")
# If template data was finalized, it would print "strintstr".
assert t.render(value=123) == "<int>"
def test_context_finalize(self):
@pass_context
def finalize(context, value):
return value * context["scale"]
e = Environment(finalize=finalize)
t = e.from_string("{{ value }}")
assert t.render(value=5, scale=3) == "15"
def test_eval_finalize(self):
@pass_eval_context
def finalize(eval_ctx, value):
return str(eval_ctx.autoescape) + value
e = Environment(finalize=finalize, autoescape=True)
t = e.from_string("{{ value }}")
assert t.render(value="<script>") == "True&lt;script&gt;"
def test_env_autoescape(self):
@pass_environment
def finalize(env, value):
return " ".join(
(env.variable_start_string, repr(value), env.variable_end_string)
)
e = Environment(finalize=finalize)
t = e.from_string("{{ value }}")
assert t.render(value="hello") == "{{ 'hello' }}"
def test_cycler(self, env):
items = 1, 2, 3
c = Cycler(*items)
for item in items + items:
assert c.current == item
assert next(c) == item
next(c)
assert c.current == 2
c.reset()
assert c.current == 1
def test_expressions(self, env):
expr = env.compile_expression("foo")
assert expr() is None
assert expr(foo=42) == 42
expr2 = env.compile_expression("foo", undefined_to_none=False)
assert is_undefined(expr2())
expr = env.compile_expression("42 + foo")
assert expr(foo=42) == 84
def test_template_passthrough(self, env):
t = Template("Content")
assert env.get_template(t) is t
assert env.select_template([t]) is t
assert env.get_or_select_template([t]) is t
assert env.get_or_select_template(t) is t
def test_get_template_undefined(self, env):
"""Passing Undefined to get/select_template raises an
UndefinedError or shows the undefined message in the list.
"""
env.loader = DictLoader({})
t = Undefined(name="no_name_1")
with pytest.raises(UndefinedError):
env.get_template(t)
with pytest.raises(UndefinedError):
env.get_or_select_template(t)
with pytest.raises(UndefinedError):
env.select_template(t)
with pytest.raises(TemplatesNotFound) as exc_info:
env.select_template([t, "no_name_2"])
exc_message = str(exc_info.value)
assert "'no_name_1' is undefined" in exc_message
assert "no_name_2" in exc_message
def test_autoescape_autoselect(self, env):
def select_autoescape(name):
if name is None or "." not in name:
return False
return name.endswith(".html")
env = Environment(
autoescape=select_autoescape,
loader=DictLoader({"test.txt": "{{ foo }}", "test.html": "{{ foo }}"}),
)
t = env.get_template("test.txt")
assert t.render(foo="<foo>") == "<foo>"
t = env.get_template("test.html")
assert t.render(foo="<foo>") == "&lt;foo&gt;"
t = env.from_string("{{ foo }}")
assert t.render(foo="<foo>") == "<foo>"
def test_sandbox_max_range(self, env):
from jinja2.sandbox import SandboxedEnvironment, MAX_RANGE
env = SandboxedEnvironment()
t = env.from_string("{% for item in range(total) %}{{ item }}{% endfor %}")
with pytest.raises(OverflowError):
t.render(total=MAX_RANGE + 1)
class TestMeta:
def test_find_undeclared_variables(self, env):
ast = env.parse("{% set foo = 42 %}{{ bar + foo }}")
x = meta.find_undeclared_variables(ast)
assert x == {"bar"}
ast = env.parse(
"{% set foo = 42 %}{{ bar + foo }}"
"{% macro meh(x) %}{{ x }}{% endmacro %}"
"{% for item in seq %}{{ muh(item) + meh(seq) }}"
"{% endfor %}"
)
x = meta.find_undeclared_variables(ast)
assert x == {"bar", "seq", "muh"}
ast = env.parse("{% for x in range(5) %}{{ x }}{% endfor %}{{ foo }}")
x = meta.find_undeclared_variables(ast)
assert x == {"foo"}
def test_find_refererenced_templates(self, env):
ast = env.parse('{% extends "layout.html" %}{% include helper %}')
i = meta.find_referenced_templates(ast)
assert next(i) == "layout.html"
assert next(i) is None
assert list(i) == []
ast = env.parse(
'{% extends "layout.html" %}'
'{% from "test.html" import a, b as c %}'
'{% import "meh.html" as meh %}'
'{% include "muh.html" %}'
)
i = meta.find_referenced_templates(ast)
assert list(i) == ["layout.html", "test.html", "meh.html", "muh.html"]
def test_find_included_templates(self, env):
ast = env.parse('{% include ["foo.html", "bar.html"] %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ["foo.html", "bar.html"]
ast = env.parse('{% include ("foo.html", "bar.html") %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ["foo.html", "bar.html"]
ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ["foo.html", "bar.html", None]
ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ["foo.html", "bar.html", None]
class TestStreaming:
def test_basic_streaming(self, env):
t = env.from_string(
"<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
"{%- endfor %}</ul>"
)
stream = t.stream(seq=list(range(3)))
assert next(stream) == "<ul>"
assert "".join(stream) == "<li>1 - 0</li><li>2 - 1</li><li>3 - 2</li></ul>"
def test_buffered_streaming(self, env):
tmpl = env.from_string(
"<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
"{%- endfor %}</ul>"
)
stream = tmpl.stream(seq=list(range(3)))
stream.enable_buffering(size=3)
assert next(stream) == "<ul><li>1"
assert next(stream) == " - 0</li>"
def test_streaming_behavior(self, env):
tmpl = env.from_string("")
stream = tmpl.stream()
assert not stream.buffered
stream.enable_buffering(20)
assert stream.buffered
stream.disable_buffering()
assert not stream.buffered
def test_dump_stream(self, env):
tmp = Path(tempfile.mkdtemp())
try:
tmpl = env.from_string("\u2713")
stream = tmpl.stream()
stream.dump(str(tmp / "dump.txt"), "utf-8")
assert (tmp / "dump.txt").read_bytes() == b"\xe2\x9c\x93"
finally:
shutil.rmtree(tmp)
class TestUndefined:
def test_stopiteration_is_undefined(self):
def test():
raise StopIteration()
t = Template("A{{ test() }}B")
assert t.render(test=test) == "AB"
t = Template("A{{ test().missingattribute }}B")
pytest.raises(UndefinedError, t.render, test=test)
def test_undefined_and_special_attributes(self):
with pytest.raises(AttributeError):
Undefined("Foo").__dict__
def test_undefined_attribute_error(self):
# Django's LazyObject turns the __class__ attribute into a
# property that resolves the wrapped function. If that wrapped
# function raises an AttributeError, printing the repr of the
# object in the undefined message would cause a RecursionError.
class Error:
@property # type: ignore
def __class__(self):
raise AttributeError()
u = Undefined(obj=Error(), name="hello")
with pytest.raises(UndefinedError):
getattr(u, "recursion", None)
def test_logging_undefined(self):
_messages = []
class DebugLogger:
def warning(self, msg, *args):
_messages.append("W:" + msg % args)
def error(self, msg, *args):
_messages.append("E:" + msg % args)
logging_undefined = make_logging_undefined(DebugLogger())
env = Environment(undefined=logging_undefined)
assert env.from_string("{{ missing }}").render() == ""
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
assert env.from_string("{{ missing|list }}").render() == "[]"
assert env.from_string("{{ missing is not defined }}").render() == "True"
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
assert env.from_string("{{ not missing }}").render() == "True"
assert _messages == [
"W:Template variable warning: 'missing' is undefined",
"E:Template variable error: 'missing' is undefined",
"W:Template variable warning: 'missing' is undefined",
"W:Template variable warning: 'int object' has no attribute 'missing'",
"W:Template variable warning: 'missing' is undefined",
]
def test_default_undefined(self):
env = Environment(undefined=Undefined)
assert env.from_string("{{ missing }}").render() == ""
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
assert env.from_string("{{ missing|list }}").render() == "[]"
assert env.from_string("{{ missing is not defined }}").render() == "True"
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
assert env.from_string("{{ not missing }}").render() == "True"
pytest.raises(UndefinedError, env.from_string("{{ missing - 1}}").render)
assert env.from_string("{{ 'foo' in missing }}").render() == "False"
und1 = Undefined(name="x")
und2 = Undefined(name="y")
assert und1 == und2
assert und1 != 42
assert hash(und1) == hash(und2) == hash(Undefined())
with pytest.raises(AttributeError):
getattr(Undefined, "__slots__") # noqa: B009
def test_chainable_undefined(self):
env = Environment(undefined=ChainableUndefined)
# The following tests are copied from test_default_undefined
assert env.from_string("{{ missing }}").render() == ""
assert env.from_string("{{ missing|list }}").render() == "[]"
assert env.from_string("{{ missing is not defined }}").render() == "True"
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
assert env.from_string("{{ not missing }}").render() == "True"
pytest.raises(UndefinedError, env.from_string("{{ missing - 1}}").render)
with pytest.raises(AttributeError):
getattr(ChainableUndefined, "__slots__") # noqa: B009
# The following tests ensure subclass functionality works as expected
assert env.from_string('{{ missing.bar["baz"] }}').render() == ""
assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() == "foo"
assert (
env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(foo=42)
== "bar"
)
assert (
env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(
foo={"bar": 42}
)
== "baz"
)
def test_debug_undefined(self):
env = Environment(undefined=DebugUndefined)
assert env.from_string("{{ missing }}").render() == "{{ missing }}"
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
assert env.from_string("{{ missing|list }}").render() == "[]"
assert env.from_string("{{ missing is not defined }}").render() == "True"
assert (
env.from_string("{{ foo.missing }}").render(foo=42)
== "{{ no such element: int object['missing'] }}"
)
assert env.from_string("{{ not missing }}").render() == "True"
undefined_hint = "this is testing undefined hint of DebugUndefined"
assert (
str(DebugUndefined(hint=undefined_hint))
== f"{{{{ undefined value printed: {undefined_hint} }}}}"
)
with pytest.raises(AttributeError):
getattr(DebugUndefined, "__slots__") # noqa: B009
def test_strict_undefined(self):
env = Environment(undefined=StrictUndefined)
pytest.raises(UndefinedError, env.from_string("{{ missing }}").render)
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
pytest.raises(UndefinedError, env.from_string("{{ missing|list }}").render)
pytest.raises(UndefinedError, env.from_string("{{ 'foo' in missing }}").render)
assert env.from_string("{{ missing is not defined }}").render() == "True"
pytest.raises(
UndefinedError, env.from_string("{{ foo.missing }}").render, foo=42
)
pytest.raises(UndefinedError, env.from_string("{{ not missing }}").render)
assert (
env.from_string('{{ missing|default("default", true) }}').render()
== "default"
)
with pytest.raises(AttributeError):
getattr(StrictUndefined, "__slots__") # noqa: B009
assert env.from_string('{{ "foo" if false }}').render() == ""
def test_indexing_gives_undefined(self):
t = Template("{{ var[42].foo }}")
pytest.raises(UndefinedError, t.render, var=0)
def test_none_gives_proper_error(self):
with pytest.raises(UndefinedError, match="'None' has no attribute 'split'"):
Environment().getattr(None, "split")()
def test_object_repr(self):
with pytest.raises(
UndefinedError, match="'int object' has no attribute 'upper'"
):
Undefined(obj=42, name="upper")()
class TestLowLevel:
def test_custom_code_generator(self):
class CustomCodeGenerator(CodeGenerator):
def visit_Const(self, node, frame=None):
# This method is pure nonsense, but works fine for testing...
if node.value == "foo":
self.write(repr("bar"))
else:
super().visit_Const(node, frame)
class CustomEnvironment(Environment):
code_generator_class = CustomCodeGenerator
env = CustomEnvironment()
tmpl = env.from_string('{% set foo = "foo" %}{{ foo }}')
assert tmpl.render() == "bar"
def test_custom_context(self):
class CustomContext(Context):
def resolve_or_missing(self, key):
return "resolve-" + key
class CustomEnvironment(Environment):
context_class = CustomContext
env = CustomEnvironment()
tmpl = env.from_string("{{ foo }}")
assert tmpl.render() == "resolve-foo"
-660
View File
@@ -1,660 +0,0 @@
import asyncio
import pytest
from jinja2 import ChainableUndefined
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import Template
from jinja2.async_utils import auto_aiter
from jinja2.exceptions import TemplateNotFound
from jinja2.exceptions import TemplatesNotFound
from jinja2.exceptions import UndefinedError
from jinja2.nativetypes import NativeEnvironment
def test_basic_async():
t = Template(
"{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True
)
async def func():
return await t.render_async()
rv = asyncio.run(func())
assert rv == "[1][2][3]"
def test_await_on_calls():
t = Template("{{ async_func() + normal_func() }}", enable_async=True)
async def async_func():
return 42
def normal_func():
return 23
async def func():
return await t.render_async(async_func=async_func, normal_func=normal_func)
rv = asyncio.run(func())
assert rv == "65"
def test_await_on_calls_normal_render():
t = Template("{{ async_func() + normal_func() }}", enable_async=True)
async def async_func():
return 42
def normal_func():
return 23
rv = t.render(async_func=async_func, normal_func=normal_func)
assert rv == "65"
def test_await_and_macros():
t = Template(
"{% macro foo(x) %}[{{ x }}][{{ async_func() }}]{% endmacro %}{{ foo(42) }}",
enable_async=True,
)
async def async_func():
return 42
async def func():
return await t.render_async(async_func=async_func)
rv = asyncio.run(func())
assert rv == "[42][42]"
def test_async_blocks():
t = Template(
"{% block foo %}<Test>{% endblock %}{{ self.foo() }}",
enable_async=True,
autoescape=True,
)
async def func():
return await t.render_async()
rv = asyncio.run(func())
assert rv == "<Test><Test>"
def test_async_generate():
t = Template("{% for x in [1, 2, 3] %}{{ x }}{% endfor %}", enable_async=True)
rv = list(t.generate())
assert rv == ["1", "2", "3"]
def test_async_iteration_in_templates():
t = Template("{% for x in rng %}{{ x }}{% endfor %}", enable_async=True)
async def async_iterator():
for item in [1, 2, 3]:
yield item
rv = list(t.generate(rng=async_iterator()))
assert rv == ["1", "2", "3"]
def test_async_iteration_in_templates_extended():
t = Template(
"{% for x in rng %}{{ loop.index0 }}/{{ x }}{% endfor %}", enable_async=True
)
stream = t.generate(rng=auto_aiter(range(1, 4)))
assert next(stream) == "0"
assert "".join(stream) == "/11/22/3"
@pytest.fixture
def test_env_async():
env = Environment(
loader=DictLoader(
dict(
module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}",
header="[{{ foo }}|{{ 23 }}]",
o_printer="({{ o }})",
)
),
enable_async=True,
)
env.globals["bar"] = 23
return env
class TestAsyncImports:
def test_context_imports(self, test_env_async):
t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}')
assert t.render(foo=42) == "[|23]"
t = test_env_async.from_string(
'{% import "module" as m without context %}{{ m.test() }}'
)
assert t.render(foo=42) == "[|23]"
t = test_env_async.from_string(
'{% import "module" as m with context %}{{ m.test() }}'
)
assert t.render(foo=42) == "[42|23]"
t = test_env_async.from_string('{% from "module" import test %}{{ test() }}')
assert t.render(foo=42) == "[|23]"
t = test_env_async.from_string(
'{% from "module" import test without context %}{{ test() }}'
)
assert t.render(foo=42) == "[|23]"
t = test_env_async.from_string(
'{% from "module" import test with context %}{{ test() }}'
)
assert t.render(foo=42) == "[42|23]"
def test_trailing_comma(self, test_env_async):
test_env_async.from_string('{% from "foo" import bar, baz with context %}')
test_env_async.from_string('{% from "foo" import bar, baz, with context %}')
test_env_async.from_string('{% from "foo" import bar, with context %}')
test_env_async.from_string('{% from "foo" import bar, with, context %}')
test_env_async.from_string('{% from "foo" import bar, with with context %}')
def test_exports(self, test_env_async):
coro = test_env_async.from_string(
"""
{% macro toplevel() %}...{% endmacro %}
{% macro __private() %}...{% endmacro %}
{% set variable = 42 %}
{% for item in [1] %}
{% macro notthere() %}{% endmacro %}
{% endfor %}
"""
)._get_default_module_async()
m = asyncio.run(coro)
assert asyncio.run(m.toplevel()) == "..."
assert not hasattr(m, "__missing")
assert m.variable == 42
assert not hasattr(m, "notthere")
def test_import_with_globals(self, test_env_async):
t = test_env_async.from_string(
'{% import "module" as m %}{{ m.test() }}', globals={"foo": 42}
)
assert t.render() == "[42|23]"
t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}')
assert t.render() == "[|23]"
def test_import_with_globals_override(self, test_env_async):
t = test_env_async.from_string(
'{% set foo = 41 %}{% import "module" as m %}{{ m.test() }}',
globals={"foo": 42},
)
assert t.render() == "[42|23]"
def test_from_import_with_globals(self, test_env_async):
t = test_env_async.from_string(
'{% from "module" import test %}{{ test() }}',
globals={"foo": 42},
)
assert t.render() == "[42|23]"
class TestAsyncIncludes:
def test_context_include(self, test_env_async):
t = test_env_async.from_string('{% include "header" %}')
assert t.render(foo=42) == "[42|23]"
t = test_env_async.from_string('{% include "header" with context %}')
assert t.render(foo=42) == "[42|23]"
t = test_env_async.from_string('{% include "header" without context %}')
assert t.render(foo=42) == "[|23]"
def test_choice_includes(self, test_env_async):
t = test_env_async.from_string('{% include ["missing", "header"] %}')
assert t.render(foo=42) == "[42|23]"
t = test_env_async.from_string(
'{% include ["missing", "missing2"] ignore missing %}'
)
assert t.render(foo=42) == ""
t = test_env_async.from_string('{% include ["missing", "missing2"] %}')
pytest.raises(TemplateNotFound, t.render)
with pytest.raises(TemplatesNotFound) as e:
t.render()
assert e.value.templates == ["missing", "missing2"]
assert e.value.name == "missing2"
def test_includes(t, **ctx):
ctx["foo"] = 42
assert t.render(ctx) == "[42|23]"
t = test_env_async.from_string('{% include ["missing", "header"] %}')
test_includes(t)
t = test_env_async.from_string("{% include x %}")
test_includes(t, x=["missing", "header"])
t = test_env_async.from_string('{% include [x, "header"] %}')
test_includes(t, x="missing")
t = test_env_async.from_string("{% include x %}")
test_includes(t, x="header")
t = test_env_async.from_string("{% include x %}")
test_includes(t, x="header")
t = test_env_async.from_string("{% include [x] %}")
test_includes(t, x="header")
def test_include_ignoring_missing(self, test_env_async):
t = test_env_async.from_string('{% include "missing" %}')
pytest.raises(TemplateNotFound, t.render)
for extra in "", "with context", "without context":
t = test_env_async.from_string(
'{% include "missing" ignore missing ' + extra + " %}"
)
assert t.render() == ""
def test_context_include_with_overrides(self, test_env_async):
env = Environment(
loader=DictLoader(
dict(
main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
item="{{ item }}",
)
)
)
assert env.get_template("main").render() == "123"
def test_unoptimized_scopes(self, test_env_async):
t = test_env_async.from_string(
"""
{% macro outer(o) %}
{% macro inner() %}
{% include "o_printer" %}
{% endmacro %}
{{ inner() }}
{% endmacro %}
{{ outer("FOO") }}
"""
)
assert t.render().strip() == "(FOO)"
def test_unoptimized_scopes_autoescape(self):
env = Environment(
loader=DictLoader({"o_printer": "({{ o }})"}),
autoescape=True,
enable_async=True,
)
t = env.from_string(
"""
{% macro outer(o) %}
{% macro inner() %}
{% include "o_printer" %}
{% endmacro %}
{{ inner() }}
{% endmacro %}
{{ outer("FOO") }}
"""
)
assert t.render().strip() == "(FOO)"
class TestAsyncForLoop:
def test_simple(self, test_env_async):
tmpl = test_env_async.from_string("{% for item in seq %}{{ item }}{% endfor %}")
assert tmpl.render(seq=list(range(10))) == "0123456789"
def test_else(self, test_env_async):
tmpl = test_env_async.from_string(
"{% for item in seq %}XXX{% else %}...{% endfor %}"
)
assert tmpl.render() == "..."
def test_empty_blocks(self, test_env_async):
tmpl = test_env_async.from_string(
"<{% for item in seq %}{% else %}{% endfor %}>"
)
assert tmpl.render() == "<>"
@pytest.mark.parametrize(
"transform", [lambda x: x, iter, reversed, lambda x: (i for i in x), auto_aiter]
)
def test_context_vars(self, test_env_async, transform):
t = test_env_async.from_string(
"{% for item in seq %}{{ loop.index }}|{{ loop.index0 }}"
"|{{ loop.revindex }}|{{ loop.revindex0 }}|{{ loop.first }}"
"|{{ loop.last }}|{{ loop.length }}\n{% endfor %}"
)
out = t.render(seq=transform([42, 24]))
assert out == "1|0|2|1|True|False|2\n2|1|1|0|False|True|2\n"
def test_cycling(self, test_env_async):
tmpl = test_env_async.from_string(
"""{% for item in seq %}{{
loop.cycle('<1>', '<2>') }}{% endfor %}{%
for item in seq %}{{ loop.cycle(*through) }}{% endfor %}"""
)
output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>"))
assert output == "<1><2>" * 4
def test_lookaround(self, test_env_async):
tmpl = test_env_async.from_string(
"""{% for item in seq -%}
{{ loop.previtem|default('x') }}-{{ item }}-{{
loop.nextitem|default('x') }}|
{%- endfor %}"""
)
output = tmpl.render(seq=list(range(4)))
assert output == "x-0-1|0-1-2|1-2-3|2-3-x|"
def test_changed(self, test_env_async):
tmpl = test_env_async.from_string(
"""{% for item in seq -%}
{{ loop.changed(item) }},
{%- endfor %}"""
)
output = tmpl.render(seq=[None, None, 1, 2, 2, 3, 4, 4, 4])
assert output == "True,False,True,True,False,True,True,False,False,"
def test_scope(self, test_env_async):
tmpl = test_env_async.from_string("{% for item in seq %}{% endfor %}{{ item }}")
output = tmpl.render(seq=list(range(10)))
assert not output
def test_varlen(self, test_env_async):
def inner():
yield from range(5)
tmpl = test_env_async.from_string(
"{% for item in iter %}{{ item }}{% endfor %}"
)
output = tmpl.render(iter=inner())
assert output == "01234"
def test_noniter(self, test_env_async):
tmpl = test_env_async.from_string("{% for item in none %}...{% endfor %}")
pytest.raises(TypeError, tmpl.render)
def test_recursive(self, test_env_async):
tmpl = test_env_async.from_string(
"""{% for item in seq recursive -%}
[{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
{%- endfor %}"""
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[1<[1][2]>][2<[1][2]>][3<[a]>]"
)
def test_recursive_lookaround(self, test_env_async):
tmpl = test_env_async.from_string(
"""{% for item in seq recursive -%}
[{{ loop.previtem.a if loop.previtem is defined else 'x' }}.{{
item.a }}.{{ loop.nextitem.a if loop.nextitem is defined else 'x'
}}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
{%- endfor %}"""
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]"
)
def test_recursive_depth0(self, test_env_async):
tmpl = test_env_async.from_string(
"{% for item in seq recursive %}[{{ loop.depth0 }}:{{ item.a }}"
"{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}"
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]"
)
def test_recursive_depth(self, test_env_async):
tmpl = test_env_async.from_string(
"{% for item in seq recursive %}[{{ loop.depth }}:{{ item.a }}"
"{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}"
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]"
)
def test_looploop(self, test_env_async):
tmpl = test_env_async.from_string(
"""{% for row in table %}
{%- set rowloop = loop -%}
{% for cell in row -%}
[{{ rowloop.index }}|{{ loop.index }}]
{%- endfor %}
{%- endfor %}"""
)
assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]"
def test_reversed_bug(self, test_env_async):
tmpl = test_env_async.from_string(
"{% for i in items %}{{ i }}"
"{% if not loop.last %}"
",{% endif %}{% endfor %}"
)
assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3"
def test_loop_errors(self, test_env_async):
tmpl = test_env_async.from_string(
"""{% for item in [1] if loop.index
== 0 %}...{% endfor %}"""
)
pytest.raises(UndefinedError, tmpl.render)
tmpl = test_env_async.from_string(
"""{% for item in [] %}...{% else
%}{{ loop }}{% endfor %}"""
)
assert tmpl.render() == ""
def test_loop_filter(self, test_env_async):
tmpl = test_env_async.from_string(
"{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}"
)
assert tmpl.render() == "[0][2][4][6][8]"
tmpl = test_env_async.from_string(
"""
{%- for item in range(10) if item is even %}[{{
loop.index }}:{{ item }}]{% endfor %}"""
)
assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]"
def test_scoped_special_var(self, test_env_async):
t = test_env_async.from_string(
"{% for s in seq %}[{{ loop.first }}{% for c in s %}"
"|{{ loop.first }}{% endfor %}]{% endfor %}"
)
assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]"
def test_scoped_loop_var(self, test_env_async):
t = test_env_async.from_string(
"{% for x in seq %}{{ loop.first }}"
"{% for y in seq %}{% endfor %}{% endfor %}"
)
assert t.render(seq="ab") == "TrueFalse"
t = test_env_async.from_string(
"{% for x in seq %}{% for y in seq %}"
"{{ loop.first }}{% endfor %}{% endfor %}"
)
assert t.render(seq="ab") == "TrueFalseTrueFalse"
def test_recursive_empty_loop_iter(self, test_env_async):
t = test_env_async.from_string(
"""
{%- for item in foo recursive -%}{%- endfor -%}
"""
)
assert t.render(dict(foo=[])) == ""
def test_call_in_loop(self, test_env_async):
t = test_env_async.from_string(
"""
{%- macro do_something() -%}
[{{ caller() }}]
{%- endmacro %}
{%- for i in [1, 2, 3] %}
{%- call do_something() -%}
{{ i }}
{%- endcall %}
{%- endfor -%}
"""
)
assert t.render() == "[1][2][3]"
def test_scoping_bug(self, test_env_async):
t = test_env_async.from_string(
"""
{%- for item in foo %}...{{ item }}...{% endfor %}
{%- macro item(a) %}...{{ a }}...{% endmacro %}
{{- item(2) -}}
"""
)
assert t.render(foo=(1,)) == "...1......2..."
def test_unpacking(self, test_env_async):
tmpl = test_env_async.from_string(
"{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}"
)
assert tmpl.render() == "1|2|3"
def test_recursive_loop_filter(self, test_env_async):
t = test_env_async.from_string(
"""
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in [site.root] if page.url != this recursive %}
<url><loc>{{ page.url }}</loc></url>
{{- loop(page.children) }}
{%- endfor %}
</urlset>
"""
)
sm = t.render(
this="/foo",
site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}},
)
lines = [x.strip() for x in sm.splitlines() if x.strip()]
assert lines == [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
"<url><loc>/</loc></url>",
"<url><loc>/bar</loc></url>",
"</urlset>",
]
def test_nonrecursive_loop_filter(self, test_env_async):
t = test_env_async.from_string(
"""
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in items if page.url != this %}
<url><loc>{{ page.url }}</loc></url>
{%- endfor %}
</urlset>
"""
)
sm = t.render(
this="/foo", items=[{"url": "/"}, {"url": "/foo"}, {"url": "/bar"}]
)
lines = [x.strip() for x in sm.splitlines() if x.strip()]
assert lines == [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
"<url><loc>/</loc></url>",
"<url><loc>/bar</loc></url>",
"</urlset>",
]
def test_bare_async(self, test_env_async):
t = test_env_async.from_string('{% extends "header" %}')
assert t.render(foo=42) == "[42|23]"
def test_awaitable_property_slicing(self, test_env_async):
t = test_env_async.from_string("{% for x in a.b[:1] %}{{ x }}{% endfor %}")
assert t.render(a=dict(b=[1, 2, 3])) == "1"
def test_namespace_awaitable(test_env_async):
async def _test():
t = test_env_async.from_string(
'{% set ns = namespace(foo="Bar") %}{{ ns.foo }}'
)
actual = await t.render_async()
assert actual == "Bar"
asyncio.run(_test())
def test_chainable_undefined_aiter():
async def _test():
t = Template(
"{% for x in a['b']['c'] %}{{ x }}{% endfor %}",
enable_async=True,
undefined=ChainableUndefined,
)
rv = await t.render_async(a={})
assert rv == ""
asyncio.run(_test())
@pytest.fixture
def async_native_env():
return NativeEnvironment(enable_async=True)
def test_native_async(async_native_env):
async def _test():
t = async_native_env.from_string("{{ x }}")
rv = await t.render_async(x=23)
assert rv == 23
asyncio.run(_test())
def test_native_list_async(async_native_env):
async def _test():
t = async_native_env.from_string("{{ x }}")
rv = await t.render_async(x=list(range(3)))
assert rv == [0, 1, 2]
asyncio.run(_test())
def test_getitem_after_filter():
env = Environment(enable_async=True)
env.filters["add_each"] = lambda v, x: [i + x for i in v]
t = env.from_string("{{ (a|add_each(2))[1:] }}")
out = t.render(a=range(3))
assert out == "[3, 4]"
def test_getitem_after_call():
env = Environment(enable_async=True)
env.globals["add_each"] = lambda v, x: [i + x for i in v]
t = env.from_string("{{ add_each(a, 2)[1:] }}")
out = t.render(a=range(3))
assert out == "[3, 4]"
-273
View File
@@ -1,273 +0,0 @@
from collections import namedtuple
import pytest
from markupsafe import Markup
from jinja2 import Environment
from jinja2.async_utils import auto_aiter
async def make_aiter(iter):
for item in iter:
yield item
def mark_dualiter(parameter, factory):
def decorator(f):
return pytest.mark.parametrize(
parameter, [lambda: factory(), lambda: make_aiter(factory())]
)(f)
return decorator
@pytest.fixture
def env_async():
return Environment(enable_async=True)
@mark_dualiter("foo", lambda: range(10))
def test_first(env_async, foo):
tmpl = env_async.from_string("{{ foo()|first }}")
out = tmpl.render(foo=foo)
assert out == "0"
@mark_dualiter(
"items",
lambda: [
{"foo": 1, "bar": 2},
{"foo": 2, "bar": 3},
{"foo": 1, "bar": 1},
{"foo": 3, "bar": 4},
],
)
def test_groupby(env_async, items):
tmpl = env_async.from_string(
"""
{%- for grouper, list in items()|groupby('foo') -%}
{{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
{%- endfor %}"""
)
assert tmpl.render(items=items).split("|") == [
"1: 1, 2: 1, 1",
"2: 2, 3",
"3: 3, 4",
"",
]
@pytest.mark.parametrize(
("case_sensitive", "expect"),
[
(False, "a: 1, 3\nb: 2\n"),
(True, "A: 3\na: 1\nb: 2\n"),
],
)
def test_groupby_case(env_async, case_sensitive, expect):
tmpl = env_async.from_string(
"{% for k, vs in data|groupby('k', case_sensitive=cs) %}"
"{{ k }}: {{ vs|join(', ', attribute='v') }}\n"
"{% endfor %}"
)
out = tmpl.render(
data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}],
cs=case_sensitive,
)
assert out == expect
@mark_dualiter("items", lambda: [("a", 1), ("a", 2), ("b", 1)])
def test_groupby_tuple_index(env_async, items):
tmpl = env_async.from_string(
"""
{%- for grouper, list in items()|groupby(0) -%}
{{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
{%- endfor %}"""
)
assert tmpl.render(items=items) == "a:1:2|b:1|"
def make_articles():
Date = namedtuple("Date", "day,month,year")
Article = namedtuple("Article", "title,date")
return [
Article("aha", Date(1, 1, 1970)),
Article("interesting", Date(2, 1, 1970)),
Article("really?", Date(3, 1, 1970)),
Article("totally not", Date(1, 1, 1971)),
]
@mark_dualiter("articles", make_articles)
def test_groupby_multidot(env_async, articles):
tmpl = env_async.from_string(
"""
{%- for year, list in articles()|groupby('date.year') -%}
{{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
{%- endfor %}"""
)
assert tmpl.render(articles=articles).split("|") == [
"1970[aha][interesting][really?]",
"1971[totally not]",
"",
]
@mark_dualiter("int_items", lambda: [1, 2, 3])
def test_join_env_int(env_async, int_items):
tmpl = env_async.from_string('{{ items()|join("|") }}')
out = tmpl.render(items=int_items)
assert out == "1|2|3"
@mark_dualiter("string_items", lambda: ["<foo>", Markup("<span>foo</span>")])
def test_join_string_list(string_items):
env2 = Environment(autoescape=True, enable_async=True)
tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
assert tmpl.render(items=string_items) == "&lt;foo&gt;<span>foo</span>"
def make_users():
User = namedtuple("User", "username")
return map(User, ["foo", "bar"])
@mark_dualiter("users", make_users)
def test_join_attribute(env_async, users):
tmpl = env_async.from_string("""{{ users()|join(', ', 'username') }}""")
assert tmpl.render(users=users) == "foo, bar"
@mark_dualiter("items", lambda: [1, 2, 3, 4, 5])
def test_simple_reject(env_async, items):
tmpl = env_async.from_string('{{ items()|reject("odd")|join("|") }}')
assert tmpl.render(items=items) == "2|4"
@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5])
def test_bool_reject(env_async, items):
tmpl = env_async.from_string('{{ items()|reject|join("|") }}')
assert tmpl.render(items=items) == "None|False|0"
@mark_dualiter("items", lambda: [1, 2, 3, 4, 5])
def test_simple_select(env_async, items):
tmpl = env_async.from_string('{{ items()|select("odd")|join("|") }}')
assert tmpl.render(items=items) == "1|3|5"
@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5])
def test_bool_select(env_async, items):
tmpl = env_async.from_string('{{ items()|select|join("|") }}')
assert tmpl.render(items=items) == "1|2|3|4|5"
def make_users(): # type: ignore
User = namedtuple("User", "name,is_active")
return [
User("john", True),
User("jane", True),
User("mike", False),
]
@mark_dualiter("users", make_users)
def test_simple_select_attr(env_async, users):
tmpl = env_async.from_string(
'{{ users()|selectattr("is_active")|map(attribute="name")|join("|") }}'
)
assert tmpl.render(users=users) == "john|jane"
@mark_dualiter("items", lambda: list("123"))
def test_simple_map(env_async, items):
tmpl = env_async.from_string('{{ items()|map("int")|sum }}')
assert tmpl.render(items=items) == "6"
def test_map_sum(env_async): # async map + async filter
tmpl = env_async.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}')
assert tmpl.render() == "[3, 3, 15]"
@mark_dualiter("users", make_users)
def test_attribute_map(env_async, users):
tmpl = env_async.from_string('{{ users()|map(attribute="name")|join("|") }}')
assert tmpl.render(users=users) == "john|jane|mike"
def test_empty_map(env_async):
tmpl = env_async.from_string('{{ none|map("upper")|list }}')
assert tmpl.render() == "[]"
@mark_dualiter("items", lambda: [1, 2, 3, 4, 5, 6])
def test_sum(env_async, items):
tmpl = env_async.from_string("""{{ items()|sum }}""")
assert tmpl.render(items=items) == "21"
@mark_dualiter("items", lambda: [{"value": 23}, {"value": 1}, {"value": 18}])
def test_sum_attributes(env_async, items):
tmpl = env_async.from_string("""{{ items()|sum('value') }}""")
assert tmpl.render(items=items)
def test_sum_attributes_nested(env_async):
tmpl = env_async.from_string("""{{ values|sum('real.value') }}""")
assert (
tmpl.render(
values=[
{"real": {"value": 23}},
{"real": {"value": 1}},
{"real": {"value": 18}},
]
)
== "42"
)
def test_sum_attributes_tuple(env_async):
tmpl = env_async.from_string("""{{ values.items()|sum('1') }}""")
assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42"
@mark_dualiter("items", lambda: range(10))
def test_slice(env_async, items):
tmpl = env_async.from_string(
"{{ items()|slice(3)|list }}|{{ items()|slice(3, 'X')|list }}"
)
out = tmpl.render(items=items)
assert out == (
"[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
"[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]"
)
def test_custom_async_filter(env_async):
async def customfilter(val):
return str(val)
env_async.filters["customfilter"] = customfilter
tmpl = env_async.from_string("{{ 'static'|customfilter }} {{ arg|customfilter }}")
out = tmpl.render(arg="dynamic")
assert out == "static dynamic"
@mark_dualiter("items", lambda: range(10))
def test_custom_async_iteratable_filter(env_async, items):
async def customfilter(iterable):
items = []
async for item in auto_aiter(iterable):
items.append(str(item))
if len(items) == 3:
break
return ",".join(items)
env_async.filters["customfilter"] = customfilter
tmpl = env_async.from_string(
"{{ items()|customfilter }} .. {{ [3, 4, 5, 6]|customfilter }}"
)
out = tmpl.render(items=items)
assert out == "0,1,2 .. 3,4,5"
-77
View File
@@ -1,77 +0,0 @@
import pytest
from jinja2 import Environment
from jinja2.bccache import Bucket
from jinja2.bccache import FileSystemBytecodeCache
from jinja2.bccache import MemcachedBytecodeCache
from jinja2.exceptions import TemplateNotFound
@pytest.fixture
def env(package_loader, tmp_path):
bytecode_cache = FileSystemBytecodeCache(str(tmp_path))
return Environment(loader=package_loader, bytecode_cache=bytecode_cache)
class TestByteCodeCache:
def test_simple(self, env):
tmpl = env.get_template("test.html")
assert tmpl.render().strip() == "BAR"
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
class MockMemcached:
class Error(Exception):
pass
key = None
value = None
timeout = None
def get(self, key):
return self.value
def set(self, key, value, timeout=None):
self.key = key
self.value = value
self.timeout = timeout
def get_side_effect(self, key):
raise self.Error()
def set_side_effect(self, *args):
raise self.Error()
class TestMemcachedBytecodeCache:
def test_dump_load(self):
memcached = MockMemcached()
m = MemcachedBytecodeCache(memcached)
b = Bucket(None, "key", "")
b.code = "code"
m.dump_bytecode(b)
assert memcached.key == "jinja2/bytecode/key"
b = Bucket(None, "key", "")
m.load_bytecode(b)
assert b.code == "code"
def test_exception(self):
memcached = MockMemcached()
memcached.get = memcached.get_side_effect
memcached.set = memcached.set_side_effect
m = MemcachedBytecodeCache(memcached)
b = Bucket(None, "key", "")
b.code = "code"
m.dump_bytecode(b)
m.load_bytecode(b)
m.ignore_memcache_errors = False
with pytest.raises(MockMemcached.Error):
m.dump_bytecode(b)
with pytest.raises(MockMemcached.Error):
m.load_bytecode(b)
-28
View File
@@ -1,28 +0,0 @@
import os
import re
from jinja2.environment import Environment
from jinja2.loaders import DictLoader
def test_filters_deterministic(tmp_path):
src = "".join(f"{{{{ {i}|filter{i} }}}}" for i in range(10))
env = Environment(loader=DictLoader({"foo": src}))
env.filters.update(dict.fromkeys((f"filter{i}" for i in range(10)), lambda: None))
env.compile_templates(tmp_path, zip=None)
name = os.listdir(tmp_path)[0]
content = (tmp_path / name).read_text("utf8")
expect = [f"filters['filter{i}']" for i in range(10)]
found = re.findall(r"filters\['filter\d']", content)
assert found == expect
def test_import_as_with_context_deterministic(tmp_path):
src = "\n".join(f'{{% import "bar" as bar{i} with context %}}' for i in range(10))
env = Environment(loader=DictLoader({"foo": src}))
env.compile_templates(tmp_path, zip=None)
name = os.listdir(tmp_path)[0]
content = (tmp_path / name).read_text("utf8")
expect = [f"'bar{i}': " for i in range(10)]
found = re.findall(r"'bar\d': ", content)[:10]
assert found == expect
-595
View File
@@ -1,595 +0,0 @@
import pytest
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import TemplateRuntimeError
from jinja2 import TemplateSyntaxError
from jinja2 import UndefinedError
@pytest.fixture
def env_trim():
return Environment(trim_blocks=True)
class TestForLoop:
def test_simple(self, env):
tmpl = env.from_string("{% for item in seq %}{{ item }}{% endfor %}")
assert tmpl.render(seq=list(range(10))) == "0123456789"
def test_else(self, env):
tmpl = env.from_string("{% for item in seq %}XXX{% else %}...{% endfor %}")
assert tmpl.render() == "..."
def test_else_scoping_item(self, env):
tmpl = env.from_string("{% for item in [] %}{% else %}{{ item }}{% endfor %}")
assert tmpl.render(item=42) == "42"
def test_empty_blocks(self, env):
tmpl = env.from_string("<{% for item in seq %}{% else %}{% endfor %}>")
assert tmpl.render() == "<>"
def test_context_vars(self, env):
slist = [42, 24]
for seq in [slist, iter(slist), reversed(slist), (_ for _ in slist)]:
tmpl = env.from_string(
"""{% for item in seq -%}
{{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
loop.length }}###{% endfor %}"""
)
one, two, _ = tmpl.render(seq=seq).split("###")
(
one_index,
one_index0,
one_revindex,
one_revindex0,
one_first,
one_last,
one_length,
) = one.split("|")
(
two_index,
two_index0,
two_revindex,
two_revindex0,
two_first,
two_last,
two_length,
) = two.split("|")
assert int(one_index) == 1 and int(two_index) == 2
assert int(one_index0) == 0 and int(two_index0) == 1
assert int(one_revindex) == 2 and int(two_revindex) == 1
assert int(one_revindex0) == 1 and int(two_revindex0) == 0
assert one_first == "True" and two_first == "False"
assert one_last == "False" and two_last == "True"
assert one_length == two_length == "2"
def test_cycling(self, env):
tmpl = env.from_string(
"""{% for item in seq %}{{
loop.cycle('<1>', '<2>') }}{% endfor %}{%
for item in seq %}{{ loop.cycle(*through) }}{% endfor %}"""
)
output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>"))
assert output == "<1><2>" * 4
def test_lookaround(self, env):
tmpl = env.from_string(
"""{% for item in seq -%}
{{ loop.previtem|default('x') }}-{{ item }}-{{
loop.nextitem|default('x') }}|
{%- endfor %}"""
)
output = tmpl.render(seq=list(range(4)))
assert output == "x-0-1|0-1-2|1-2-3|2-3-x|"
def test_changed(self, env):
tmpl = env.from_string(
"""{% for item in seq -%}
{{ loop.changed(item) }},
{%- endfor %}"""
)
output = tmpl.render(seq=[None, None, 1, 2, 2, 3, 4, 4, 4])
assert output == "True,False,True,True,False,True,True,False,False,"
def test_scope(self, env):
tmpl = env.from_string("{% for item in seq %}{% endfor %}{{ item }}")
output = tmpl.render(seq=list(range(10)))
assert not output
def test_varlen(self, env):
tmpl = env.from_string("{% for item in iter %}{{ item }}{% endfor %}")
output = tmpl.render(iter=range(5))
assert output == "01234"
def test_noniter(self, env):
tmpl = env.from_string("{% for item in none %}...{% endfor %}")
pytest.raises(TypeError, tmpl.render)
def test_recursive(self, env):
tmpl = env.from_string(
"""{% for item in seq recursive -%}
[{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
{%- endfor %}"""
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[1<[1][2]>][2<[1][2]>][3<[a]>]"
)
def test_recursive_lookaround(self, env):
tmpl = env.from_string(
"""{% for item in seq recursive -%}
[{{ loop.previtem.a if loop.previtem is defined else 'x' }}.{{
item.a }}.{{ loop.nextitem.a if loop.nextitem is defined else 'x'
}}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
{%- endfor %}"""
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]"
)
def test_recursive_depth0(self, env):
tmpl = env.from_string(
"""{% for item in seq recursive -%}
[{{ loop.depth0 }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
{%- endfor %}"""
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]"
)
def test_recursive_depth(self, env):
tmpl = env.from_string(
"""{% for item in seq recursive -%}
[{{ loop.depth }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
{%- endfor %}"""
)
assert (
tmpl.render(
seq=[
dict(a=1, b=[dict(a=1), dict(a=2)]),
dict(a=2, b=[dict(a=1), dict(a=2)]),
dict(a=3, b=[dict(a="a")]),
]
)
== "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]"
)
def test_looploop(self, env):
tmpl = env.from_string(
"""{% for row in table %}
{%- set rowloop = loop -%}
{% for cell in row -%}
[{{ rowloop.index }}|{{ loop.index }}]
{%- endfor %}
{%- endfor %}"""
)
assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]"
def test_reversed_bug(self, env):
tmpl = env.from_string(
"{% for i in items %}{{ i }}"
"{% if not loop.last %}"
",{% endif %}{% endfor %}"
)
assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3"
def test_loop_errors(self, env):
tmpl = env.from_string(
"""{% for item in [1] if loop.index
== 0 %}...{% endfor %}"""
)
pytest.raises(UndefinedError, tmpl.render)
tmpl = env.from_string(
"""{% for item in [] %}...{% else
%}{{ loop }}{% endfor %}"""
)
assert tmpl.render() == ""
def test_loop_filter(self, env):
tmpl = env.from_string(
"{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}"
)
assert tmpl.render() == "[0][2][4][6][8]"
tmpl = env.from_string(
"""
{%- for item in range(10) if item is even %}[{{
loop.index }}:{{ item }}]{% endfor %}"""
)
assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]"
def test_loop_unassignable(self, env):
pytest.raises(
TemplateSyntaxError, env.from_string, "{% for loop in seq %}...{% endfor %}"
)
def test_scoped_special_var(self, env):
t = env.from_string(
"{% for s in seq %}[{{ loop.first }}{% for c in s %}"
"|{{ loop.first }}{% endfor %}]{% endfor %}"
)
assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]"
def test_scoped_loop_var(self, env):
t = env.from_string(
"{% for x in seq %}{{ loop.first }}"
"{% for y in seq %}{% endfor %}{% endfor %}"
)
assert t.render(seq="ab") == "TrueFalse"
t = env.from_string(
"{% for x in seq %}{% for y in seq %}"
"{{ loop.first }}{% endfor %}{% endfor %}"
)
assert t.render(seq="ab") == "TrueFalseTrueFalse"
def test_recursive_empty_loop_iter(self, env):
t = env.from_string(
"""
{%- for item in foo recursive -%}{%- endfor -%}
"""
)
assert t.render(dict(foo=[])) == ""
def test_call_in_loop(self, env):
t = env.from_string(
"""
{%- macro do_something() -%}
[{{ caller() }}]
{%- endmacro %}
{%- for i in [1, 2, 3] %}
{%- call do_something() -%}
{{ i }}
{%- endcall %}
{%- endfor -%}
"""
)
assert t.render() == "[1][2][3]"
def test_scoping_bug(self, env):
t = env.from_string(
"""
{%- for item in foo %}...{{ item }}...{% endfor %}
{%- macro item(a) %}...{{ a }}...{% endmacro %}
{{- item(2) -}}
"""
)
assert t.render(foo=(1,)) == "...1......2..."
def test_unpacking(self, env):
tmpl = env.from_string(
"{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}"
)
assert tmpl.render() == "1|2|3"
def test_intended_scoping_with_set(self, env):
tmpl = env.from_string(
"{% for item in seq %}{{ x }}{% set x = item %}{{ x }}{% endfor %}"
)
assert tmpl.render(x=0, seq=[1, 2, 3]) == "010203"
tmpl = env.from_string(
"{% set x = 9 %}{% for item in seq %}{{ x }}"
"{% set x = item %}{{ x }}{% endfor %}"
)
assert tmpl.render(x=0, seq=[1, 2, 3]) == "919293"
class TestIfCondition:
def test_simple(self, env):
tmpl = env.from_string("""{% if true %}...{% endif %}""")
assert tmpl.render() == "..."
def test_elif(self, env):
tmpl = env.from_string(
"""{% if false %}XXX{% elif true
%}...{% else %}XXX{% endif %}"""
)
assert tmpl.render() == "..."
def test_elif_deep(self, env):
elifs = "\n".join(f"{{% elif a == {i} %}}{i}" for i in range(1, 1000))
tmpl = env.from_string(f"{{% if a == 0 %}}0{elifs}{{% else %}}x{{% endif %}}")
for x in (0, 10, 999):
assert tmpl.render(a=x).strip() == str(x)
assert tmpl.render(a=1000).strip() == "x"
def test_else(self, env):
tmpl = env.from_string("{% if false %}XXX{% else %}...{% endif %}")
assert tmpl.render() == "..."
def test_empty(self, env):
tmpl = env.from_string("[{% if true %}{% else %}{% endif %}]")
assert tmpl.render() == "[]"
def test_complete(self, env):
tmpl = env.from_string(
"{% if a %}A{% elif b %}B{% elif c == d %}C{% else %}D{% endif %}"
)
assert tmpl.render(a=0, b=False, c=42, d=42.0) == "C"
def test_no_scope(self, env):
tmpl = env.from_string("{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}")
assert tmpl.render(a=True) == "1"
tmpl = env.from_string("{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}")
assert tmpl.render() == "1"
class TestMacros:
def test_simple(self, env_trim):
tmpl = env_trim.from_string(
"""\
{% macro say_hello(name) %}Hello {{ name }}!{% endmacro %}
{{ say_hello('Peter') }}"""
)
assert tmpl.render() == "Hello Peter!"
def test_scoping(self, env_trim):
tmpl = env_trim.from_string(
"""\
{% macro level1(data1) %}
{% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %}
{{ level2('bar') }}{% endmacro %}
{{ level1('foo') }}"""
)
assert tmpl.render() == "foo|bar"
def test_arguments(self, env_trim):
tmpl = env_trim.from_string(
"""\
{% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %}
{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}"""
)
assert tmpl.render() == "||c|d|a||c|d|a|b|c|d|1|2|3|d"
def test_arguments_defaults_nonsense(self, env_trim):
pytest.raises(
TemplateSyntaxError,
env_trim.from_string,
"""\
{% macro m(a, b=1, c) %}a={{ a }}, b={{ b }}, c={{ c }}{% endmacro %}""",
)
def test_caller_defaults_nonsense(self, env_trim):
pytest.raises(
TemplateSyntaxError,
env_trim.from_string,
"""\
{% macro a() %}{{ caller() }}{% endmacro %}
{% call(x, y=1, z) a() %}{% endcall %}""",
)
def test_varargs(self, env_trim):
tmpl = env_trim.from_string(
"""\
{% macro test() %}{{ varargs|join('|') }}{% endmacro %}\
{{ test(1, 2, 3) }}"""
)
assert tmpl.render() == "1|2|3"
def test_simple_call(self, env_trim):
tmpl = env_trim.from_string(
"""\
{% macro test() %}[[{{ caller() }}]]{% endmacro %}\
{% call test() %}data{% endcall %}"""
)
assert tmpl.render() == "[[data]]"
def test_complex_call(self, env_trim):
tmpl = env_trim.from_string(
"""\
{% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\
{% call(data) test() %}{{ data }}{% endcall %}"""
)
assert tmpl.render() == "[[data]]"
def test_caller_undefined(self, env_trim):
tmpl = env_trim.from_string(
"""\
{% set caller = 42 %}\
{% macro test() %}{{ caller is not defined }}{% endmacro %}\
{{ test() }}"""
)
assert tmpl.render() == "True"
def test_include(self, env_trim):
env_trim = Environment(
loader=DictLoader(
{"include": "{% macro test(foo) %}[{{ foo }}]{% endmacro %}"}
)
)
tmpl = env_trim.from_string('{% from "include" import test %}{{ test("foo") }}')
assert tmpl.render() == "[foo]"
def test_macro_api(self, env_trim):
tmpl = env_trim.from_string(
"{% macro foo(a, b) %}{% endmacro %}"
"{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}"
"{% macro baz() %}{{ caller() }}{% endmacro %}"
)
assert tmpl.module.foo.arguments == ("a", "b")
assert tmpl.module.foo.name == "foo"
assert not tmpl.module.foo.caller
assert not tmpl.module.foo.catch_kwargs
assert not tmpl.module.foo.catch_varargs
assert tmpl.module.bar.arguments == ()
assert not tmpl.module.bar.caller
assert tmpl.module.bar.catch_kwargs
assert tmpl.module.bar.catch_varargs
assert tmpl.module.baz.caller
def test_callself(self, env_trim):
tmpl = env_trim.from_string(
"{% macro foo(x) %}{{ x }}{% if x > 1 %}|"
"{{ foo(x - 1) }}{% endif %}{% endmacro %}"
"{{ foo(5) }}"
)
assert tmpl.render() == "5|4|3|2|1"
def test_macro_defaults_self_ref(self, env):
tmpl = env.from_string(
"""
{%- set x = 42 %}
{%- macro m(a, b=x, x=23) %}{{ a }}|{{ b }}|{{ x }}{% endmacro -%}
"""
)
assert tmpl.module.m(1) == "1||23"
assert tmpl.module.m(1, 2) == "1|2|23"
assert tmpl.module.m(1, 2, 3) == "1|2|3"
assert tmpl.module.m(1, x=7) == "1|7|7"
class TestSet:
def test_normal(self, env_trim):
tmpl = env_trim.from_string("{% set foo = 1 %}{{ foo }}")
assert tmpl.render() == "1"
assert tmpl.module.foo == 1
def test_block(self, env_trim):
tmpl = env_trim.from_string("{% set foo %}42{% endset %}{{ foo }}")
assert tmpl.render() == "42"
assert tmpl.module.foo == "42"
def test_block_escaping(self):
env = Environment(autoescape=True)
tmpl = env.from_string(
"{% set foo %}<em>{{ test }}</em>{% endset %}foo: {{ foo }}"
)
assert tmpl.render(test="<unsafe>") == "foo: <em>&lt;unsafe&gt;</em>"
def test_set_invalid(self, env_trim):
pytest.raises(
TemplateSyntaxError, env_trim.from_string, "{% set foo['bar'] = 1 %}"
)
tmpl = env_trim.from_string("{% set foo.bar = 1 %}")
exc_info = pytest.raises(TemplateRuntimeError, tmpl.render, foo={})
assert "non-namespace object" in exc_info.value.message
def test_namespace_redefined(self, env_trim):
tmpl = env_trim.from_string("{% set ns = namespace() %}{% set ns.bar = 'hi' %}")
exc_info = pytest.raises(TemplateRuntimeError, tmpl.render, namespace=dict)
assert "non-namespace object" in exc_info.value.message
def test_namespace(self, env_trim):
tmpl = env_trim.from_string(
"{% set ns = namespace() %}{% set ns.bar = '42' %}{{ ns.bar }}"
)
assert tmpl.render() == "42"
def test_namespace_block(self, env_trim):
tmpl = env_trim.from_string(
"{% set ns = namespace() %}{% set ns.bar %}42{% endset %}{{ ns.bar }}"
)
assert tmpl.render() == "42"
def test_init_namespace(self, env_trim):
tmpl = env_trim.from_string(
"{% set ns = namespace(d, self=37) %}"
"{% set ns.b = 42 %}"
"{{ ns.a }}|{{ ns.self }}|{{ ns.b }}"
)
assert tmpl.render(d={"a": 13}) == "13|37|42"
def test_namespace_loop(self, env_trim):
tmpl = env_trim.from_string(
"{% set ns = namespace(found=false) %}"
"{% for x in range(4) %}"
"{% if x == v %}"
"{% set ns.found = true %}"
"{% endif %}"
"{% endfor %}"
"{{ ns.found }}"
)
assert tmpl.render(v=3) == "True"
assert tmpl.render(v=4) == "False"
def test_namespace_macro(self, env_trim):
tmpl = env_trim.from_string(
"{% set ns = namespace() %}"
"{% set ns.a = 13 %}"
"{% macro magic(x) %}"
"{% set x.b = 37 %}"
"{% endmacro %}"
"{{ magic(ns) }}"
"{{ ns.a }}|{{ ns.b }}"
)
assert tmpl.render() == "13|37"
def test_block_escaping_filtered(self):
env = Environment(autoescape=True)
tmpl = env.from_string(
"{% set foo | trim %}<em>{{ test }}</em> {% endset %}foo: {{ foo }}"
)
assert tmpl.render(test="<unsafe>") == "foo: <em>&lt;unsafe&gt;</em>"
def test_block_filtered(self, env_trim):
tmpl = env_trim.from_string(
"{% set foo | trim | length | string %} 42 {% endset %}{{ foo }}"
)
assert tmpl.render() == "2"
assert tmpl.module.foo == "2"
def test_block_filtered_set(self, env_trim):
def _myfilter(val, arg):
assert arg == " xxx "
return val
env_trim.filters["myfilter"] = _myfilter
tmpl = env_trim.from_string(
'{% set a = " xxx " %}'
"{% set foo | myfilter(a) | trim | length | string %}"
' {% set b = " yy " %} 42 {{ a }}{{ b }} '
"{% endset %}"
"{{ foo }}"
)
assert tmpl.render() == "11"
assert tmpl.module.foo == "11"
class TestWith:
def test_with(self, env):
tmpl = env.from_string(
"""\
{% with a=42, b=23 -%}
{{ a }} = {{ b }}
{% endwith -%}
{{ a }} = {{ b }}\
"""
)
assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] == [
"42 = 23",
"1 = 2",
]
def test_with_argument_scoping(self, env):
tmpl = env.from_string(
"""\
{%- with a=1, b=2, c=b, d=e, e=5 -%}
{{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }}
{%- endwith -%}
"""
)
assert tmpl.render(b=3, e=4) == "1|2|3|4|5"
-117
View File
@@ -1,117 +0,0 @@
import pickle
import re
from traceback import format_exception
import pytest
from jinja2 import ChoiceLoader
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import TemplateSyntaxError
@pytest.fixture
def fs_env(filesystem_loader):
"""returns a new environment."""
return Environment(loader=filesystem_loader)
class TestDebug:
def assert_traceback_matches(self, callback, expected_tb):
with pytest.raises(Exception) as exc_info:
callback()
tb = format_exception(exc_info.type, exc_info.value, exc_info.tb)
m = re.search(expected_tb.strip(), "".join(tb))
assert (
m is not None
), f"Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}"
def test_runtime_error(self, fs_env):
def test():
tmpl.render(fail=lambda: 1 / 0)
tmpl = fs_env.get_template("broken.html")
self.assert_traceback_matches(
test,
r"""
File ".*?broken.html", line 2, in (top-level template code|<module>)
\{\{ fail\(\) \}\}(
\^{12})?
File ".*debug?.pyc?", line \d+, in <lambda>
tmpl\.render\(fail=lambda: 1 / 0\)(
~~\^~~)?
ZeroDivisionError: (int(eger)? )?division (or modulo )?by zero
""",
)
def test_syntax_error(self, fs_env):
# The trailing .*? is for PyPy 2 and 3, which don't seem to
# clear the exception's original traceback, leaving the syntax
# error in the middle of other compiler frames.
self.assert_traceback_matches(
lambda: fs_env.get_template("syntaxerror.html"),
"""(?sm)
File ".*?syntaxerror.html", line 4, in (template|<module>)
\\{% endif %\\}.*?
(jinja2\\.exceptions\\.)?TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja \
was looking for the following tags: 'endfor' or 'else'. The innermost block that needs \
to be closed is 'for'.
""",
)
def test_regular_syntax_error(self, fs_env):
def test():
raise TemplateSyntaxError("wtf", 42)
self.assert_traceback_matches(
test,
r"""
File ".*debug.pyc?", line \d+, in test
raise TemplateSyntaxError\("wtf", 42\)(
\^{36})?
(jinja2\.exceptions\.)?TemplateSyntaxError: wtf
line 42""",
)
def test_pickleable_syntax_error(self, fs_env):
original = TemplateSyntaxError("bad template", 42, "test", "test.txt")
unpickled = pickle.loads(pickle.dumps(original))
assert str(original) == str(unpickled)
assert original.name == unpickled.name
def test_include_syntax_error_source(self, filesystem_loader):
e = Environment(
loader=ChoiceLoader(
[
filesystem_loader,
DictLoader({"inc": "a\n{% include 'syntaxerror.html' %}\nb"}),
]
)
)
t = e.get_template("inc")
with pytest.raises(TemplateSyntaxError) as exc_info:
t.render()
assert exc_info.value.source is not None
def test_local_extraction(self):
from jinja2.debug import get_template_locals
from jinja2.runtime import missing
locals = get_template_locals(
{
"l_0_foo": 42,
"l_1_foo": 23,
"l_2_foo": 13,
"l_0_bar": 99,
"l_1_bar": missing,
"l_0_baz": missing,
}
)
assert locals == {"foo": 13, "bar": 99}
def test_get_corresponding_lineno_traceback(self, fs_env):
tmpl = fs_env.get_template("test.html")
assert tmpl.get_corresponding_lineno(1) == 1
-726
View File
@@ -1,726 +0,0 @@
import re
from io import BytesIO
import pytest
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import nodes
from jinja2 import pass_context
from jinja2.exceptions import TemplateAssertionError
from jinja2.ext import Extension
from jinja2.lexer import count_newlines
from jinja2.lexer import Token
importable_object = 23
_gettext_re = re.compile(r"_\((.*?)\)", re.DOTALL)
i18n_templates = {
"default.html": '<title>{{ page_title|default(_("missing")) }}</title>'
"{% block body %}{% endblock %}",
"child.html": '{% extends "default.html" %}{% block body %}'
"{% trans %}watch out{% endtrans %}{% endblock %}",
"plural.html": "{% trans user_count %}One user online{% pluralize %}"
"{{ user_count }} users online{% endtrans %}",
"plural2.html": "{% trans user_count=get_user_count() %}{{ user_count }}s"
"{% pluralize %}{{ user_count }}p{% endtrans %}",
"stringformat.html": '{{ _("User: %(num)s")|format(num=user_count) }}',
}
newstyle_i18n_templates = {
"default.html": '<title>{{ page_title|default(_("missing")) }}</title>'
"{% block body %}{% endblock %}",
"child.html": '{% extends "default.html" %}{% block body %}'
"{% trans %}watch out{% endtrans %}{% endblock %}",
"plural.html": "{% trans user_count %}One user online{% pluralize %}"
"{{ user_count }} users online{% endtrans %}",
"stringformat.html": '{{ _("User: %(num)s", num=user_count) }}',
"ngettext.html": '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
"ngettext_long.html": "{% trans num=apples %}{{ num }} apple{% pluralize %}"
"{{ num }} apples{% endtrans %}",
"pgettext.html": '{{ pgettext("fruit", "Apple") }}',
"npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s apples",'
" apples) }}",
"pgettext_block": "{% trans 'fruit' num=apples %}Apple{% endtrans %}",
"npgettext_block": "{% trans 'fruit' num=apples %}{{ num }} apple"
"{% pluralize %}{{ num }} apples{% endtrans %}",
"transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}",
"transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}",
"transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}",
"novars.html": "{% trans %}%(hello)s{% endtrans %}",
"vars.html": "{% trans %}{{ foo }}%(foo)s{% endtrans %}",
"explicitvars.html": '{% trans foo="42" %}%(foo)s{% endtrans %}',
}
languages = {
"de": {
"missing": "fehlend",
"watch out": "pass auf",
"One user online": "Ein Benutzer online",
"%(user_count)s users online": "%(user_count)s Benutzer online",
"User: %(num)s": "Benutzer: %(num)s",
"User: %(count)s": "Benutzer: %(count)s",
"Apple": {None: "Apfel", "fruit": "Apple"},
"%(num)s apple": {None: "%(num)s Apfel", "fruit": "%(num)s Apple"},
"%(num)s apples": {None: "%(num)s Äpfel", "fruit": "%(num)s Apples"},
}
}
def _get_with_context(value, ctx=None):
if isinstance(value, dict):
return value.get(ctx, value)
return value
@pass_context
def gettext(context, string):
language = context.get("LANGUAGE", "en")
value = languages.get(language, {}).get(string, string)
return _get_with_context(value)
@pass_context
def ngettext(context, s, p, n):
language = context.get("LANGUAGE", "en")
if n != 1:
value = languages.get(language, {}).get(p, p)
return _get_with_context(value)
value = languages.get(language, {}).get(s, s)
return _get_with_context(value)
@pass_context
def pgettext(context, c, s):
language = context.get("LANGUAGE", "en")
value = languages.get(language, {}).get(s, s)
return _get_with_context(value, c)
@pass_context
def npgettext(context, c, s, p, n):
language = context.get("LANGUAGE", "en")
if n != 1:
value = languages.get(language, {}).get(p, p)
return _get_with_context(value, c)
value = languages.get(language, {}).get(s, s)
return _get_with_context(value, c)
i18n_env = Environment(
loader=DictLoader(i18n_templates), extensions=["jinja2.ext.i18n"]
)
i18n_env.globals.update(
{
"_": gettext,
"gettext": gettext,
"ngettext": ngettext,
"pgettext": pgettext,
"npgettext": npgettext,
}
)
i18n_env_trimmed = Environment(extensions=["jinja2.ext.i18n"])
i18n_env_trimmed.policies["ext.i18n.trimmed"] = True
i18n_env_trimmed.globals.update(
{
"_": gettext,
"gettext": gettext,
"ngettext": ngettext,
"pgettext": pgettext,
"npgettext": npgettext,
}
)
newstyle_i18n_env = Environment(
loader=DictLoader(newstyle_i18n_templates), extensions=["jinja2.ext.i18n"]
)
newstyle_i18n_env.install_gettext_callables( # type: ignore
gettext, ngettext, newstyle=True, pgettext=pgettext, npgettext=npgettext
)
class ExampleExtension(Extension):
tags = {"test"}
ext_attr = 42
context_reference_node_cls = nodes.ContextReference
def parse(self, parser):
return nodes.Output(
[
self.call_method(
"_dump",
[
nodes.EnvironmentAttribute("sandboxed"),
self.attr("ext_attr"),
nodes.ImportedName(__name__ + ".importable_object"),
self.context_reference_node_cls(),
],
)
]
).set_lineno(next(parser.stream).lineno)
def _dump(self, sandboxed, ext_attr, imported_object, context):
return (
f"{sandboxed}|{ext_attr}|{imported_object}|{context.blocks}"
f"|{context.get('test_var')}"
)
class DerivedExampleExtension(ExampleExtension):
context_reference_node_cls = nodes.DerivedContextReference # type: ignore
class PreprocessorExtension(Extension):
def preprocess(self, source, name, filename=None):
return source.replace("[[TEST]]", "({{ foo }})")
class StreamFilterExtension(Extension):
def filter_stream(self, stream):
for token in stream:
if token.type == "data":
yield from self.interpolate(token)
else:
yield token
def interpolate(self, token):
pos = 0
end = len(token.value)
lineno = token.lineno
while True:
match = _gettext_re.search(token.value, pos)
if match is None:
break
value = token.value[pos : match.start()]
if value:
yield Token(lineno, "data", value)
lineno += count_newlines(token.value)
yield Token(lineno, "variable_begin", None)
yield Token(lineno, "name", "gettext")
yield Token(lineno, "lparen", None)
yield Token(lineno, "string", match.group(1))
yield Token(lineno, "rparen", None)
yield Token(lineno, "variable_end", None)
pos = match.end()
if pos < end:
yield Token(lineno, "data", token.value[pos:])
class TestExtensions:
def test_extend_late(self):
env = Environment()
t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
assert t.render() == "&lt;test&gt;"
def test_loop_controls(self):
env = Environment(extensions=["jinja2.ext.loopcontrols"])
tmpl = env.from_string(
"""
{%- for item in [1, 2, 3, 4] %}
{%- if item % 2 == 0 %}{% continue %}{% endif -%}
{{ item }}
{%- endfor %}"""
)
assert tmpl.render() == "13"
tmpl = env.from_string(
"""
{%- for item in [1, 2, 3, 4] %}
{%- if item > 2 %}{% break %}{% endif -%}
{{ item }}
{%- endfor %}"""
)
assert tmpl.render() == "12"
def test_do(self):
env = Environment(extensions=["jinja2.ext.do"])
tmpl = env.from_string(
"""
{%- set items = [] %}
{%- for char in "foo" %}
{%- do items.append(loop.index0 ~ char) %}
{%- endfor %}{{ items|join(', ') }}"""
)
assert tmpl.render() == "0f, 1o, 2o"
def test_extension_nodes(self):
env = Environment(extensions=[ExampleExtension])
tmpl = env.from_string("{% test %}")
assert tmpl.render() == "False|42|23|{}|None"
def test_contextreference_node_passes_context(self):
env = Environment(extensions=[ExampleExtension])
tmpl = env.from_string('{% set test_var="test_content" %}{% test %}')
assert tmpl.render() == "False|42|23|{}|test_content"
def test_contextreference_node_can_pass_locals(self):
env = Environment(extensions=[DerivedExampleExtension])
tmpl = env.from_string(
'{% for test_var in ["test_content"] %}{% test %}{% endfor %}'
)
assert tmpl.render() == "False|42|23|{}|test_content"
def test_identifier(self):
assert ExampleExtension.identifier == __name__ + ".ExampleExtension"
def test_rebinding(self):
original = Environment(extensions=[ExampleExtension])
overlay = original.overlay()
for env in original, overlay:
for ext in env.extensions.values():
assert ext.environment is env
def test_preprocessor_extension(self):
env = Environment(extensions=[PreprocessorExtension])
tmpl = env.from_string("{[[TEST]]}")
assert tmpl.render(foo=42) == "{(42)}"
def test_streamfilter_extension(self):
env = Environment(extensions=[StreamFilterExtension])
env.globals["gettext"] = lambda x: x.upper()
tmpl = env.from_string("Foo _(bar) Baz")
out = tmpl.render()
assert out == "Foo BAR Baz"
def test_extension_ordering(self):
class T1(Extension):
priority = 1
class T2(Extension):
priority = 2
env = Environment(extensions=[T1, T2])
ext = list(env.iter_extensions())
assert ext[0].__class__ is T1
assert ext[1].__class__ is T2
def test_debug(self):
env = Environment(extensions=["jinja2.ext.debug"])
t = env.from_string("Hello\n{% debug %}\nGoodbye")
out = t.render()
for value in ("context", "cycler", "filters", "abs", "tests", "!="):
assert f"'{value}'" in out
class TestInternationalization:
def test_trans(self):
tmpl = i18n_env.get_template("child.html")
assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
def test_trans_plural(self):
tmpl = i18n_env.get_template("plural.html")
assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online"
assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online"
def test_trans_plural_with_functions(self):
tmpl = i18n_env.get_template("plural2.html")
def get_user_count():
get_user_count.called += 1
return 1
get_user_count.called = 0
assert tmpl.render(LANGUAGE="de", get_user_count=get_user_count) == "1s"
assert get_user_count.called == 1
def test_complex_plural(self):
tmpl = i18n_env.from_string(
"{% trans foo=42, count=2 %}{{ count }} item{% "
"pluralize count %}{{ count }} items{% endtrans %}"
)
assert tmpl.render() == "2 items"
pytest.raises(
TemplateAssertionError,
i18n_env.from_string,
"{% trans foo %}...{% pluralize bar %}...{% endtrans %}",
)
def test_trans_stringformatting(self):
tmpl = i18n_env.get_template("stringformat.html")
assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5"
def test_trimmed(self):
tmpl = i18n_env.from_string(
"{%- trans trimmed %} hello\n world {% endtrans -%}"
)
assert tmpl.render() == "hello world"
def test_trimmed_policy(self):
s = "{%- trans %} hello\n world {% endtrans -%}"
tmpl = i18n_env.from_string(s)
trimmed_tmpl = i18n_env_trimmed.from_string(s)
assert tmpl.render() == " hello\n world "
assert trimmed_tmpl.render() == "hello world"
def test_trimmed_policy_override(self):
tmpl = i18n_env_trimmed.from_string(
"{%- trans notrimmed %} hello\n world {% endtrans -%}"
)
assert tmpl.render() == " hello\n world "
def test_trimmed_vars(self):
tmpl = i18n_env.from_string(
'{%- trans trimmed x="world" %} hello\n {{ x }} {% endtrans -%}'
)
assert tmpl.render() == "hello world"
def test_trimmed_varname_trimmed(self):
# unlikely variable name, but when used as a variable
# it should not enable trimming
tmpl = i18n_env.from_string(
"{%- trans trimmed = 'world' %} hello\n {{ trimmed }} {% endtrans -%}"
)
assert tmpl.render() == " hello\n world "
def test_extract(self):
from jinja2.ext import babel_extract
source = BytesIO(
b"""
{{ gettext('Hello World') }}
{% trans %}Hello World{% endtrans %}
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
"""
)
assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
(2, "gettext", "Hello World", []),
(3, "gettext", "Hello World", []),
(4, "ngettext", ("%(users)s user", "%(users)s users", None), []),
]
def test_extract_trimmed(self):
from jinja2.ext import babel_extract
source = BytesIO(
b"""
{{ gettext(' Hello \n World') }}
{% trans trimmed %} Hello \n World{% endtrans %}
{% trans trimmed %}{{ users }} \n user
{%- pluralize %}{{ users }} \n users{% endtrans %}
"""
)
assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
(2, "gettext", " Hello \n World", []),
(4, "gettext", "Hello World", []),
(6, "ngettext", ("%(users)s user", "%(users)s users", None), []),
]
def test_extract_trimmed_option(self):
from jinja2.ext import babel_extract
source = BytesIO(
b"""
{{ gettext(' Hello \n World') }}
{% trans %} Hello \n World{% endtrans %}
{% trans %}{{ users }} \n user
{%- pluralize %}{{ users }} \n users{% endtrans %}
"""
)
opts = {"trimmed": "true"}
assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], opts)) == [
(2, "gettext", " Hello \n World", []),
(4, "gettext", "Hello World", []),
(6, "ngettext", ("%(users)s user", "%(users)s users", None), []),
]
def test_comment_extract(self):
from jinja2.ext import babel_extract
source = BytesIO(
b"""
{# trans first #}
{{ gettext('Hello World') }}
{% trans %}Hello World{% endtrans %}{# trans second #}
{#: third #}
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
"""
)
assert list(
babel_extract(source, ("gettext", "ngettext", "_"), ["trans", ":"], {})
) == [
(3, "gettext", "Hello World", ["first"]),
(4, "gettext", "Hello World", ["second"]),
(6, "ngettext", ("%(users)s user", "%(users)s users", None), ["third"]),
]
def test_extract_context(self):
from jinja2.ext import babel_extract
source = BytesIO(
b"""
{{ pgettext("babel", "Hello World") }}
{{ npgettext("babel", "%(users)s user", "%(users)s users", users) }}
"""
)
assert list(babel_extract(source, ("pgettext", "npgettext", "_"), [], {})) == [
(2, "pgettext", ("babel", "Hello World"), []),
(3, "npgettext", ("babel", "%(users)s user", "%(users)s users", None), []),
]
class TestScope:
def test_basic_scope_behavior(self):
# This is what the old with statement compiled down to
class ScopeExt(Extension):
tags = {"scope"}
def parse(self, parser):
node = nodes.Scope(lineno=next(parser.stream).lineno)
assignments = []
while parser.stream.current.type != "block_end":
lineno = parser.stream.current.lineno
if assignments:
parser.stream.expect("comma")
target = parser.parse_assign_target()
parser.stream.expect("assign")
expr = parser.parse_expression()
assignments.append(nodes.Assign(target, expr, lineno=lineno))
node.body = assignments + list(
parser.parse_statements(("name:endscope",), drop_needle=True)
)
return node
env = Environment(extensions=[ScopeExt])
tmpl = env.from_string(
"""\
{%- scope a=1, b=2, c=b, d=e, e=5 -%}
{{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }}
{%- endscope -%}
"""
)
assert tmpl.render(b=3, e=4) == "1|2|2|4|5"
class TestNewstyleInternationalization:
def test_trans(self):
tmpl = newstyle_i18n_env.get_template("child.html")
assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
def test_trans_plural(self):
tmpl = newstyle_i18n_env.get_template("plural.html")
assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online"
assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online"
def test_complex_plural(self):
tmpl = newstyle_i18n_env.from_string(
"{% trans foo=42, count=2 %}{{ count }} item{% "
"pluralize count %}{{ count }} items{% endtrans %}"
)
assert tmpl.render() == "2 items"
pytest.raises(
TemplateAssertionError,
i18n_env.from_string,
"{% trans foo %}...{% pluralize bar %}...{% endtrans %}",
)
def test_trans_stringformatting(self):
tmpl = newstyle_i18n_env.get_template("stringformat.html")
assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5"
def test_newstyle_plural(self):
tmpl = newstyle_i18n_env.get_template("ngettext.html")
assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apfel"
assert tmpl.render(LANGUAGE="de", apples=5) == "5 Äpfel"
def test_autoescape_support(self):
env = Environment(extensions=["jinja2.ext.i18n"])
env.install_gettext_callables(
lambda x: "<strong>Wert: %(name)s</strong>",
lambda s, p, n: s,
newstyle=True,
)
t = env.from_string(
'{% autoescape ae %}{{ gettext("foo", name='
'"<test>") }}{% endautoescape %}'
)
assert t.render(ae=True) == "<strong>Wert: &lt;test&gt;</strong>"
assert t.render(ae=False) == "<strong>Wert: <test></strong>"
def test_autoescape_macros(self):
env = Environment(autoescape=False)
template = (
"{% macro m() %}<html>{% endmacro %}"
"{% autoescape true %}{{ m() }}{% endautoescape %}"
)
assert env.from_string(template).render() == "<html>"
def test_num_used_twice(self):
tmpl = newstyle_i18n_env.get_template("ngettext_long.html")
assert tmpl.render(apples=5, LANGUAGE="de") == "5 Äpfel"
def test_num_called_num(self):
source = newstyle_i18n_env.compile(
"""
{% trans num=3 %}{{ num }} apple{% pluralize
%}{{ num }} apples{% endtrans %}
""",
raw=True,
)
# quite hacky, but the only way to properly test that. The idea is
# that the generated code does not pass num twice (although that
# would work) for better performance. This only works on the
# newstyle gettext of course
assert (
re.search(r"u?'%\(num\)s apple', u?'%\(num\)s apples', 3", source)
is not None
)
def test_trans_vars(self):
t1 = newstyle_i18n_env.get_template("transvars1.html")
t2 = newstyle_i18n_env.get_template("transvars2.html")
t3 = newstyle_i18n_env.get_template("transvars3.html")
assert t1.render(num=1, LANGUAGE="de") == "Benutzer: 1"
assert t2.render(count=23, LANGUAGE="de") == "Benutzer: 23"
assert t3.render(num=42, LANGUAGE="de") == "Benutzer: 42"
def test_novars_vars_escaping(self):
t = newstyle_i18n_env.get_template("novars.html")
assert t.render() == "%(hello)s"
t = newstyle_i18n_env.get_template("vars.html")
assert t.render(foo="42") == "42%(foo)s"
t = newstyle_i18n_env.get_template("explicitvars.html")
assert t.render() == "%(foo)s"
def test_context(self):
tmpl = newstyle_i18n_env.get_template("pgettext.html")
assert tmpl.render(LANGUAGE="de") == "Apple"
def test_context_plural(self):
tmpl = newstyle_i18n_env.get_template("npgettext.html")
assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple"
assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples"
def test_context_block(self):
tmpl = newstyle_i18n_env.get_template("pgettext_block")
assert tmpl.render(LANGUAGE="de") == "Apple"
def test_context_plural_block(self):
tmpl = newstyle_i18n_env.get_template("npgettext_block")
assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple"
assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples"
class TestAutoEscape:
def test_scoped_setting(self):
env = Environment(autoescape=True)
tmpl = env.from_string(
"""
{{ "<HelloWorld>" }}
{% autoescape false %}
{{ "<HelloWorld>" }}
{% endautoescape %}
{{ "<HelloWorld>" }}
"""
)
assert tmpl.render().split() == [
"&lt;HelloWorld&gt;",
"<HelloWorld>",
"&lt;HelloWorld&gt;",
]
env = Environment(autoescape=False)
tmpl = env.from_string(
"""
{{ "<HelloWorld>" }}
{% autoescape true %}
{{ "<HelloWorld>" }}
{% endautoescape %}
{{ "<HelloWorld>" }}
"""
)
assert tmpl.render().split() == [
"<HelloWorld>",
"&lt;HelloWorld&gt;",
"<HelloWorld>",
]
def test_nonvolatile(self):
env = Environment(autoescape=True)
tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
assert tmpl.render() == ' foo="&lt;test&gt;"'
tmpl = env.from_string(
'{% autoescape false %}{{ {"foo": "<test>"}'
"|xmlattr|escape }}{% endautoescape %}"
)
assert tmpl.render() == " foo=&#34;&amp;lt;test&amp;gt;&#34;"
def test_volatile(self):
env = Environment(autoescape=True)
tmpl = env.from_string(
'{% autoescape foo %}{{ {"foo": "<test>"}'
"|xmlattr|escape }}{% endautoescape %}"
)
assert tmpl.render(foo=False) == " foo=&#34;&amp;lt;test&amp;gt;&#34;"
assert tmpl.render(foo=True) == ' foo="&lt;test&gt;"'
def test_scoping(self):
env = Environment()
tmpl = env.from_string(
'{% autoescape true %}{% set x = "<x>" %}{{ x }}'
'{% endautoescape %}{{ x }}{{ "<y>" }}'
)
assert tmpl.render(x=1) == "&lt;x&gt;1<y>"
def test_volatile_scoping(self):
env = Environment()
tmplsource = """
{% autoescape val %}
{% macro foo(x) %}
[{{ x }}]
{% endmacro %}
{{ foo().__class__.__name__ }}
{% endautoescape %}
{{ '<testing>' }}
"""
tmpl = env.from_string(tmplsource)
assert tmpl.render(val=True).split()[0] == "Markup"
assert tmpl.render(val=False).split()[0] == "str"
# looking at the source we should see <testing> there in raw
# (and then escaped as well)
env = Environment()
pysource = env.compile(tmplsource, raw=True)
assert "<testing>\\n" in pysource
env = Environment(autoescape=True)
pysource = env.compile(tmplsource, raw=True)
assert "&lt;testing&gt;\\n" in pysource
def test_overlay_scopes(self):
class MagicScopeExtension(Extension):
tags = {"overlay"}
def parse(self, parser):
node = nodes.OverlayScope(lineno=next(parser.stream).lineno)
node.body = list(
parser.parse_statements(("name:endoverlay",), drop_needle=True)
)
node.context = self.call_method("get_scope")
return node
def get_scope(self):
return {"x": [1, 2, 3]}
env = Environment(extensions=[MagicScopeExtension])
tmpl = env.from_string(
"""
{{- x }}|{% set z = 99 %}
{%- overlay %}
{{- y }}|{{ z }}|{% for item in x %}[{{ item }}]{% endfor %}
{%- endoverlay %}|
{{- x -}}
"""
)
assert tmpl.render(x=42, y=23) == "42|23|99|[1][2][3]|42"
-873
View File
@@ -1,873 +0,0 @@
import random
from collections import namedtuple
import pytest
from markupsafe import Markup
from jinja2 import Environment
from jinja2 import StrictUndefined
from jinja2 import TemplateRuntimeError
from jinja2 import UndefinedError
from jinja2.exceptions import TemplateAssertionError
class Magic:
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
class Magic2:
def __init__(self, value1, value2):
self.value1 = value1
self.value2 = value2
def __str__(self):
return f"({self.value1},{self.value2})"
class TestFilter:
def test_filter_calling(self, env):
rv = env.call_filter("sum", [1, 2, 3])
assert rv == 6
def test_capitalize(self, env):
tmpl = env.from_string('{{ "foo bar"|capitalize }}')
assert tmpl.render() == "Foo bar"
def test_center(self, env):
tmpl = env.from_string('{{ "foo"|center(9) }}')
assert tmpl.render() == " foo "
def test_default(self, env):
tmpl = env.from_string(
"{{ missing|default('no') }}|{{ false|default('no') }}|"
"{{ false|default('no', true) }}|{{ given|default('no') }}"
)
assert tmpl.render(given="yes") == "no|False|no|yes"
@pytest.mark.parametrize(
"args,expect",
(
("", "[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]"),
("true", "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]"),
('by="value"', "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]"),
("reverse=true", "[('c', 2), ('b', 1), ('AB', 3), ('aa', 0)]"),
),
)
def test_dictsort(self, env, args, expect):
t = env.from_string(f"{{{{ foo|dictsort({args}) }}}}")
out = t.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
assert out == expect
def test_batch(self, env):
tmpl = env.from_string("{{ foo|batch(3)|list }}|{{ foo|batch(3, 'X')|list }}")
out = tmpl.render(foo=list(range(10)))
assert out == (
"[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
"[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]"
)
def test_slice(self, env):
tmpl = env.from_string("{{ foo|slice(3)|list }}|{{ foo|slice(3, 'X')|list }}")
out = tmpl.render(foo=list(range(10)))
assert out == (
"[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
"[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]"
)
def test_escape(self, env):
tmpl = env.from_string("""{{ '<">&'|escape }}""")
out = tmpl.render()
assert out == "&lt;&#34;&gt;&amp;"
@pytest.mark.parametrize(
("chars", "expect"), [(None, "..stays.."), (".", " ..stays"), (" .", "stays")]
)
def test_trim(self, env, chars, expect):
tmpl = env.from_string("{{ foo|trim(chars) }}")
out = tmpl.render(foo=" ..stays..", chars=chars)
assert out == expect
def test_striptags(self, env):
tmpl = env.from_string("""{{ foo|striptags }}""")
out = tmpl.render(
foo=' <p>just a small \n <a href="#">'
"example</a> link</p>\n<p>to a webpage</p> "
"<!-- <p>and some commented stuff</p> -->"
)
assert out == "just a small example link to a webpage"
def test_filesizeformat(self, env):
tmpl = env.from_string(
"{{ 100|filesizeformat }}|"
"{{ 1000|filesizeformat }}|"
"{{ 1000000|filesizeformat }}|"
"{{ 1000000000|filesizeformat }}|"
"{{ 1000000000000|filesizeformat }}|"
"{{ 100|filesizeformat(true) }}|"
"{{ 1000|filesizeformat(true) }}|"
"{{ 1000000|filesizeformat(true) }}|"
"{{ 1000000000|filesizeformat(true) }}|"
"{{ 1000000000000|filesizeformat(true) }}"
)
out = tmpl.render()
assert out == (
"100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|"
"1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB"
)
def test_filesizeformat_issue59(self, env):
tmpl = env.from_string(
"{{ 300|filesizeformat }}|"
"{{ 3000|filesizeformat }}|"
"{{ 3000000|filesizeformat }}|"
"{{ 3000000000|filesizeformat }}|"
"{{ 3000000000000|filesizeformat }}|"
"{{ 300|filesizeformat(true) }}|"
"{{ 3000|filesizeformat(true) }}|"
"{{ 3000000|filesizeformat(true) }}"
)
out = tmpl.render()
assert out == (
"300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|2.9 KiB|2.9 MiB"
)
def test_first(self, env):
tmpl = env.from_string("{{ foo|first }}")
out = tmpl.render(foo=list(range(10)))
assert out == "0"
@pytest.mark.parametrize(
("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32"))
)
def test_float(self, env, value, expect):
t = env.from_string("{{ value|float }}")
assert t.render(value=value) == expect
def test_float_default(self, env):
t = env.from_string("{{ value|float(default=1.0) }}")
assert t.render(value="abc") == "1.0"
def test_format(self, env):
tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}")
out = tmpl.render()
assert out == "a|b"
@staticmethod
def _test_indent_multiline_template(env, markup=False):
text = "\n".join(["", "foo bar", '"baz"', ""])
if markup:
text = Markup(text)
t = env.from_string("{{ foo|indent(2, false, false) }}")
assert t.render(foo=text) == '\n foo bar\n "baz"\n'
t = env.from_string("{{ foo|indent(2, false, true) }}")
assert t.render(foo=text) == '\n foo bar\n "baz"\n '
t = env.from_string("{{ foo|indent(2, true, false) }}")
assert t.render(foo=text) == ' \n foo bar\n "baz"\n'
t = env.from_string("{{ foo|indent(2, true, true) }}")
assert t.render(foo=text) == ' \n foo bar\n "baz"\n '
def test_indent(self, env):
self._test_indent_multiline_template(env)
t = env.from_string('{{ "jinja"|indent }}')
assert t.render() == "jinja"
t = env.from_string('{{ "jinja"|indent(first=true) }}')
assert t.render() == " jinja"
t = env.from_string('{{ "jinja"|indent(blank=true) }}')
assert t.render() == "jinja"
def test_indent_markup_input(self, env):
"""
Tests cases where the filter input is a Markup type
"""
self._test_indent_multiline_template(env, markup=True)
def test_indent_width_string(self, env):
t = env.from_string("{{ 'jinja\nflask'|indent(width='>>> ', first=True) }}")
assert t.render() == ">>> jinja\n>>> flask"
@pytest.mark.parametrize(
("value", "expect"),
(
("42", "42"),
("abc", "0"),
("32.32", "32"),
("12345678901234567890", "12345678901234567890"),
),
)
def test_int(self, env, value, expect):
t = env.from_string("{{ value|int }}")
assert t.render(value=value) == expect
@pytest.mark.parametrize(
("value", "base", "expect"),
(("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0")),
)
def test_int_base(self, env, value, base, expect):
t = env.from_string("{{ value|int(base=base) }}")
assert t.render(value=value, base=base) == expect
def test_int_default(self, env):
t = env.from_string("{{ value|int(default=1) }}")
assert t.render(value="abc") == "1"
def test_int_special_method(self, env):
class IntIsh:
def __int__(self):
return 42
t = env.from_string("{{ value|int }}")
assert t.render(value=IntIsh()) == "42"
def test_join(self, env):
tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
out = tmpl.render()
assert out == "1|2|3"
env2 = Environment(autoescape=True)
tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
assert tmpl.render() == "&lt;foo&gt;<span>foo</span>"
def test_join_attribute(self, env):
User = namedtuple("User", "username")
tmpl = env.from_string("""{{ users|join(', ', 'username') }}""")
assert tmpl.render(users=map(User, ["foo", "bar"])) == "foo, bar"
def test_last(self, env):
tmpl = env.from_string("""{{ foo|last }}""")
out = tmpl.render(foo=list(range(10)))
assert out == "9"
def test_length(self, env):
tmpl = env.from_string("""{{ "hello world"|length }}""")
out = tmpl.render()
assert out == "11"
def test_lower(self, env):
tmpl = env.from_string("""{{ "FOO"|lower }}""")
out = tmpl.render()
assert out == "foo"
def test_items(self, env):
d = {i: c for i, c in enumerate("abc")}
tmpl = env.from_string("""{{ d|items|list }}""")
out = tmpl.render(d=d)
assert out == "[(0, 'a'), (1, 'b'), (2, 'c')]"
def test_items_undefined(self, env):
tmpl = env.from_string("""{{ d|items|list }}""")
out = tmpl.render()
assert out == "[]"
def test_pprint(self, env):
from pprint import pformat
tmpl = env.from_string("""{{ data|pprint }}""")
data = list(range(1000))
assert tmpl.render(data=data) == pformat(data)
def test_random(self, env, request):
# restore the random state when the test ends
state = random.getstate()
request.addfinalizer(lambda: random.setstate(state))
# generate the random values from a known seed
random.seed("jinja")
expected = [random.choice("1234567890") for _ in range(10)]
# check that the random sequence is generated again by a template
# ensures that filter result is not constant folded
random.seed("jinja")
t = env.from_string('{{ "1234567890"|random }}')
for value in expected:
assert t.render() == value
def test_reverse(self, env):
tmpl = env.from_string(
"{{ 'foobar'|reverse|join }}|{{ [1, 2, 3]|reverse|list }}"
)
assert tmpl.render() == "raboof|[3, 2, 1]"
def test_string(self, env):
x = [1, 2, 3, 4, 5]
tmpl = env.from_string("""{{ obj|string }}""")
assert tmpl.render(obj=x) == str(x)
def test_title(self, env):
tmpl = env.from_string("""{{ "foo bar"|title }}""")
assert tmpl.render() == "Foo Bar"
tmpl = env.from_string("""{{ "foo's bar"|title }}""")
assert tmpl.render() == "Foo's Bar"
tmpl = env.from_string("""{{ "foo bar"|title }}""")
assert tmpl.render() == "Foo Bar"
tmpl = env.from_string("""{{ "f bar f"|title }}""")
assert tmpl.render() == "F Bar F"
tmpl = env.from_string("""{{ "foo-bar"|title }}""")
assert tmpl.render() == "Foo-Bar"
tmpl = env.from_string("""{{ "foo\tbar"|title }}""")
assert tmpl.render() == "Foo\tBar"
tmpl = env.from_string("""{{ "FOO\tBAR"|title }}""")
assert tmpl.render() == "Foo\tBar"
tmpl = env.from_string("""{{ "foo (bar)"|title }}""")
assert tmpl.render() == "Foo (Bar)"
tmpl = env.from_string("""{{ "foo {bar}"|title }}""")
assert tmpl.render() == "Foo {Bar}"
tmpl = env.from_string("""{{ "foo [bar]"|title }}""")
assert tmpl.render() == "Foo [Bar]"
tmpl = env.from_string("""{{ "foo <bar>"|title }}""")
assert tmpl.render() == "Foo <Bar>"
class Foo:
def __str__(self):
return "foo-bar"
tmpl = env.from_string("""{{ data|title }}""")
out = tmpl.render(data=Foo())
assert out == "Foo-Bar"
def test_truncate(self, env):
tmpl = env.from_string(
'{{ data|truncate(15, true, ">>>") }}|'
'{{ data|truncate(15, false, ">>>") }}|'
"{{ smalldata|truncate(15) }}"
)
out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar")
assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar"
def test_truncate_very_short(self, env):
tmpl = env.from_string(
'{{ "foo bar baz"|truncate(9) }}|{{ "foo bar baz"|truncate(9, true) }}'
)
out = tmpl.render()
assert out == "foo bar baz|foo bar baz"
def test_truncate_end_length(self, env):
tmpl = env.from_string('{{ "Joel is a slug"|truncate(7, true) }}')
out = tmpl.render()
assert out == "Joel..."
def test_upper(self, env):
tmpl = env.from_string('{{ "foo"|upper }}')
assert tmpl.render() == "FOO"
def test_urlize(self, env):
tmpl = env.from_string('{{ "foo example.org bar"|urlize }}')
assert tmpl.render() == (
'foo <a href="https://example.org" rel="noopener">' "example.org</a> bar"
)
tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
assert tmpl.render() == (
'foo <a href="http://www.example.com/" rel="noopener">'
"http://www.example.com/</a> bar"
)
tmpl = env.from_string('{{ "foo mailto:email@example.com bar"|urlize }}')
assert tmpl.render() == (
'foo <a href="mailto:email@example.com">email@example.com</a> bar'
)
tmpl = env.from_string('{{ "foo email@example.com bar"|urlize }}')
assert tmpl.render() == (
'foo <a href="mailto:email@example.com">email@example.com</a> bar'
)
def test_urlize_rel_policy(self):
env = Environment()
env.policies["urlize.rel"] = None
tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
assert tmpl.render() == (
'foo <a href="http://www.example.com/">http://www.example.com/</a> bar'
)
def test_urlize_target_parameter(self, env):
tmpl = env.from_string(
'{{ "foo http://www.example.com/ bar"|urlize(target="_blank") }}'
)
assert (
tmpl.render()
== 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">'
"http://www.example.com/</a> bar"
)
def test_urlize_extra_schemes_parameter(self, env):
tmpl = env.from_string(
'{{ "foo tel:+1-514-555-1234 ftp://localhost bar"|'
'urlize(extra_schemes=["tel:", "ftp:"]) }}'
)
assert tmpl.render() == (
'foo <a href="tel:+1-514-555-1234" rel="noopener">'
'tel:+1-514-555-1234</a> <a href="ftp://localhost" rel="noopener">'
"ftp://localhost</a> bar"
)
def test_wordcount(self, env):
tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
assert tmpl.render() == "3"
strict_env = Environment(undefined=StrictUndefined)
t = strict_env.from_string("{{ s|wordcount }}")
with pytest.raises(UndefinedError):
t.render()
def test_block(self, env):
tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}")
assert tmpl.render() == "&lt;hehe&gt;"
def test_chaining(self, env):
tmpl = env.from_string("""{{ ['<foo>', '<bar>']|first|upper|escape }}""")
assert tmpl.render() == "&lt;FOO&gt;"
def test_sum(self, env):
tmpl = env.from_string("""{{ [1, 2, 3, 4, 5, 6]|sum }}""")
assert tmpl.render() == "21"
def test_sum_attributes(self, env):
tmpl = env.from_string("""{{ values|sum('value') }}""")
assert tmpl.render(values=[{"value": 23}, {"value": 1}, {"value": 18}]) == "42"
def test_sum_attributes_nested(self, env):
tmpl = env.from_string("""{{ values|sum('real.value') }}""")
assert (
tmpl.render(
values=[
{"real": {"value": 23}},
{"real": {"value": 1}},
{"real": {"value": 18}},
]
)
== "42"
)
def test_sum_attributes_tuple(self, env):
tmpl = env.from_string("""{{ values.items()|sum('1') }}""")
assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42"
def test_abs(self, env):
tmpl = env.from_string("""{{ -1|abs }}|{{ 1|abs }}""")
assert tmpl.render() == "1|1", tmpl.render()
def test_round_positive(self, env):
tmpl = env.from_string(
"{{ 2.7|round }}|{{ 2.1|round }}|"
"{{ 2.1234|round(3, 'floor') }}|"
"{{ 2.1|round(0, 'ceil') }}"
)
assert tmpl.render() == "3.0|2.0|2.123|3.0", tmpl.render()
def test_round_negative(self, env):
tmpl = env.from_string(
"{{ 21.3|round(-1)}}|"
"{{ 21.3|round(-1, 'ceil')}}|"
"{{ 21.3|round(-1, 'floor')}}"
)
assert tmpl.render() == "20.0|30.0|20.0", tmpl.render()
def test_xmlattr(self, env):
tmpl = env.from_string(
"{{ {'foo': 42, 'bar': 23, 'fish': none, "
"'spam': missing, 'blub:blub': '<?>'}|xmlattr }}"
)
out = tmpl.render().split()
assert len(out) == 3
assert 'foo="42"' in out
assert 'bar="23"' in out
assert 'blub:blub="&lt;?&gt;"' in out
def test_sort1(self, env):
tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}")
assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]"
def test_sort2(self, env):
tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
assert tmpl.render() == "AbcD"
def test_sort3(self, env):
tmpl = env.from_string("""{{ ['foo', 'Bar', 'blah']|sort }}""")
assert tmpl.render() == "['Bar', 'blah', 'foo']"
def test_sort4(self, env):
tmpl = env.from_string("""{{ items|sort(attribute='value')|join }}""")
assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == "1234"
def test_sort5(self, env):
tmpl = env.from_string("""{{ items|sort(attribute='value.0')|join }}""")
assert tmpl.render(items=map(Magic, [[3], [2], [4], [1]])) == "[1][2][3][4]"
def test_sort6(self, env):
tmpl = env.from_string("""{{ items|sort(attribute='value1,value2')|join }}""")
assert (
tmpl.render(
items=map(
lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
)
)
== "(2,1)(2,2)(2,5)(3,1)"
)
def test_sort7(self, env):
tmpl = env.from_string("""{{ items|sort(attribute='value2,value1')|join }}""")
assert (
tmpl.render(
items=map(
lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
)
)
== "(2,1)(3,1)(2,2)(2,5)"
)
def test_sort8(self, env):
tmpl = env.from_string(
"""{{ items|sort(attribute='value1.0,value2.0')|join }}"""
)
assert (
tmpl.render(
items=map(
lambda x: Magic2(x[0], x[1]),
[([3], [1]), ([2], [2]), ([2], [1]), ([2], [5])],
)
)
== "([2],[1])([2],[2])([2],[5])([3],[1])"
)
def test_unique(self, env):
t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}')
assert t.render() == "bA"
def test_unique_case_sensitive(self, env):
t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique(true)) }}')
assert t.render() == "bAa"
def test_unique_attribute(self, env):
t = env.from_string("{{ items|unique(attribute='value')|join }}")
assert t.render(items=map(Magic, [3, 2, 4, 1, 2])) == "3241"
@pytest.mark.parametrize(
"source,expect",
(
('{{ ["a", "B"]|min }}', "a"),
('{{ ["a", "B"]|min(case_sensitive=true) }}', "B"),
("{{ []|min }}", ""),
('{{ ["a", "B"]|max }}', "B"),
('{{ ["a", "B"]|max(case_sensitive=true) }}', "a"),
("{{ []|max }}", ""),
),
)
def test_min_max(self, env, source, expect):
t = env.from_string(source)
assert t.render() == expect
@pytest.mark.parametrize(("name", "expect"), [("min", "1"), ("max", "9")])
def test_min_max_attribute(self, env, name, expect):
t = env.from_string("{{ items|" + name + '(attribute="value") }}')
assert t.render(items=map(Magic, [5, 1, 9])) == expect
def test_groupby(self, env):
tmpl = env.from_string(
"""
{%- for grouper, list in [{'foo': 1, 'bar': 2},
{'foo': 2, 'bar': 3},
{'foo': 1, 'bar': 1},
{'foo': 3, 'bar': 4}]|groupby('foo') -%}
{{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
{%- endfor %}"""
)
assert tmpl.render().split("|") == ["1: 1, 2: 1, 1", "2: 2, 3", "3: 3, 4", ""]
def test_groupby_tuple_index(self, env):
tmpl = env.from_string(
"""
{%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
{{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
{%- endfor %}"""
)
assert tmpl.render() == "a:1:2|b:1|"
def test_groupby_multidot(self, env):
Date = namedtuple("Date", "day,month,year")
Article = namedtuple("Article", "title,date")
articles = [
Article("aha", Date(1, 1, 1970)),
Article("interesting", Date(2, 1, 1970)),
Article("really?", Date(3, 1, 1970)),
Article("totally not", Date(1, 1, 1971)),
]
tmpl = env.from_string(
"""
{%- for year, list in articles|groupby('date.year') -%}
{{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
{%- endfor %}"""
)
assert tmpl.render(articles=articles).split("|") == [
"1970[aha][interesting][really?]",
"1971[totally not]",
"",
]
def test_groupby_default(self, env):
tmpl = env.from_string(
"{% for city, items in users|groupby('city', default='NY') %}"
"{{ city }}: {{ items|map(attribute='name')|join(', ') }}\n"
"{% endfor %}"
)
out = tmpl.render(
users=[
{"name": "emma", "city": "NY"},
{"name": "smith", "city": "WA"},
{"name": "john"},
]
)
assert out == "NY: emma, john\nWA: smith\n"
@pytest.mark.parametrize(
("case_sensitive", "expect"),
[
(False, "a: 1, 3\nb: 2\n"),
(True, "A: 3\na: 1\nb: 2\n"),
],
)
def test_groupby_case(self, env, case_sensitive, expect):
tmpl = env.from_string(
"{% for k, vs in data|groupby('k', case_sensitive=cs) %}"
"{{ k }}: {{ vs|join(', ', attribute='v') }}\n"
"{% endfor %}"
)
out = tmpl.render(
data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}],
cs=case_sensitive,
)
assert out == expect
def test_filtertag(self, env):
tmpl = env.from_string(
"{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}"
)
assert tmpl.render() == "fooBAR"
def test_replace(self, env):
env = Environment()
tmpl = env.from_string('{{ string|replace("o", 42) }}')
assert tmpl.render(string="<foo>") == "<f4242>"
env = Environment(autoescape=True)
tmpl = env.from_string('{{ string|replace("o", 42) }}')
assert tmpl.render(string="<foo>") == "&lt;f4242&gt;"
tmpl = env.from_string('{{ string|replace("<", 42) }}')
assert tmpl.render(string="<foo>") == "42foo&gt;"
tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
assert tmpl.render(string=Markup("foo")) == "f&gt;x&lt;&gt;x&lt;"
def test_forceescape(self, env):
tmpl = env.from_string("{{ x|forceescape }}")
assert tmpl.render(x=Markup("<div />")) == "&lt;div /&gt;"
def test_safe(self, env):
env = Environment(autoescape=True)
tmpl = env.from_string('{{ "<div>foo</div>"|safe }}')
assert tmpl.render() == "<div>foo</div>"
tmpl = env.from_string('{{ "<div>foo</div>" }}')
assert tmpl.render() == "&lt;div&gt;foo&lt;/div&gt;"
@pytest.mark.parametrize(
("value", "expect"),
[
("Hello, world!", "Hello%2C%20world%21"),
("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"),
({"f": 1}, "f=1"),
([("f", 1), ("z", 2)], "f=1&amp;z=2"),
({"\u203d": 1}, "%E2%80%BD=1"),
({0: 1}, "0=1"),
([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"),
("a b/c", "a%20b/c"),
],
)
def test_urlencode(self, value, expect):
e = Environment(autoescape=True)
t = e.from_string("{{ value|urlencode }}")
assert t.render(value=value) == expect
def test_simple_map(self, env):
env = Environment()
tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}')
assert tmpl.render() == "6"
def test_map_sum(self, env):
tmpl = env.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}')
assert tmpl.render() == "[3, 3, 15]"
def test_attribute_map(self, env):
User = namedtuple("User", "name")
env = Environment()
users = [
User("john"),
User("jane"),
User("mike"),
]
tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
assert tmpl.render(users=users) == "john|jane|mike"
def test_empty_map(self, env):
env = Environment()
tmpl = env.from_string('{{ none|map("upper")|list }}')
assert tmpl.render() == "[]"
def test_map_default(self, env):
Fullname = namedtuple("Fullname", "firstname,lastname")
Firstname = namedtuple("Firstname", "firstname")
env = Environment()
tmpl = env.from_string(
'{{ users|map(attribute="lastname", default="smith")|join(", ") }}'
)
test_list = env.from_string(
'{{ users|map(attribute="lastname", default=["smith","x"])|join(", ") }}'
)
test_str = env.from_string(
'{{ users|map(attribute="lastname", default="")|join(", ") }}'
)
users = [
Fullname("john", "lennon"),
Fullname("jane", "edwards"),
Fullname("jon", None),
Firstname("mike"),
]
assert tmpl.render(users=users) == "lennon, edwards, None, smith"
assert test_list.render(users=users) == "lennon, edwards, None, ['smith', 'x']"
assert test_str.render(users=users) == "lennon, edwards, None, "
def test_simple_select(self, env):
env = Environment()
tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')
assert tmpl.render() == "1|3|5"
def test_bool_select(self, env):
env = Environment()
tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}')
assert tmpl.render() == "1|2|3|4|5"
def test_simple_reject(self, env):
env = Environment()
tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}')
assert tmpl.render() == "2|4"
def test_bool_reject(self, env):
env = Environment()
tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}')
assert tmpl.render() == "None|False|0"
def test_simple_select_attr(self, env):
User = namedtuple("User", "name,is_active")
env = Environment()
users = [
User("john", True),
User("jane", True),
User("mike", False),
]
tmpl = env.from_string(
'{{ users|selectattr("is_active")|map(attribute="name")|join("|") }}'
)
assert tmpl.render(users=users) == "john|jane"
def test_simple_reject_attr(self, env):
User = namedtuple("User", "name,is_active")
env = Environment()
users = [
User("john", True),
User("jane", True),
User("mike", False),
]
tmpl = env.from_string(
'{{ users|rejectattr("is_active")|map(attribute="name")|join("|") }}'
)
assert tmpl.render(users=users) == "mike"
def test_func_select_attr(self, env):
User = namedtuple("User", "id,name")
env = Environment()
users = [
User(1, "john"),
User(2, "jane"),
User(3, "mike"),
]
tmpl = env.from_string(
'{{ users|selectattr("id", "odd")|map(attribute="name")|join("|") }}'
)
assert tmpl.render(users=users) == "john|mike"
def test_func_reject_attr(self, env):
User = namedtuple("User", "id,name")
env = Environment()
users = [
User(1, "john"),
User(2, "jane"),
User(3, "mike"),
]
tmpl = env.from_string(
'{{ users|rejectattr("id", "odd")|map(attribute="name")|join("|") }}'
)
assert tmpl.render(users=users) == "jane"
def test_json_dump(self):
env = Environment(autoescape=True)
t = env.from_string("{{ x|tojson }}")
assert t.render(x={"foo": "bar"}) == '{"foo": "bar"}'
assert t.render(x="\"ba&r'") == r'"\"ba\u0026r\u0027"'
assert t.render(x="<bar>") == r'"\u003cbar\u003e"'
def my_dumps(value, **options):
assert options == {"foo": "bar"}
return "42"
env.policies["json.dumps_function"] = my_dumps
env.policies["json.dumps_kwargs"] = {"foo": "bar"}
assert t.render(x=23) == "42"
def test_wordwrap(self, env):
env.newline_sequence = "\n"
t = env.from_string("{{ s|wordwrap(20) }}")
result = t.render(s="Hello!\nThis is Jinja saying something.")
assert result == "Hello!\nThis is Jinja saying\nsomething."
def test_filter_undefined(self, env):
with pytest.raises(TemplateAssertionError, match="No filter named 'f'"):
env.from_string("{{ var|f }}")
def test_filter_undefined_in_if(self, env):
t = env.from_string("{%- if x is defined -%}{{ x|f }}{%- else -%}x{% endif %}")
assert t.render() == "x"
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
t.render(x=42)
def test_filter_undefined_in_elif(self, env):
t = env.from_string(
"{%- if x is defined -%}{{ x }}{%- elif y is defined -%}"
"{{ y|f }}{%- else -%}foo{%- endif -%}"
)
assert t.render() == "foo"
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
t.render(y=42)
def test_filter_undefined_in_else(self, env):
t = env.from_string(
"{%- if x is not defined -%}foo{%- else -%}{{ x|f }}{%- endif -%}"
)
assert t.render() == "foo"
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
t.render(x=42)
def test_filter_undefined_in_nested_if(self, env):
t = env.from_string(
"{%- if x is not defined -%}foo{%- else -%}{%- if y "
"is defined -%}{{ y|f }}{%- endif -%}{{ x }}{%- endif -%}"
)
assert t.render() == "foo"
assert t.render(x=42) == "42"
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
t.render(x=24, y=42)
def test_filter_undefined_in_condexpr(self, env):
t1 = env.from_string("{{ x|f if x is defined else 'foo' }}")
t2 = env.from_string("{{ 'foo' if x is not defined else x|f }}")
assert t1.render() == t2.render() == "foo"
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
t1.render(x=42)
t2.render(x=42)
-290
View File
@@ -1,290 +0,0 @@
from jinja2 import nodes
from jinja2.idtracking import symbols_for_node
def test_basics():
for_loop = nodes.For(
nodes.Name("foo", "store"),
nodes.Name("seq", "load"),
[nodes.Output([nodes.Name("foo", "load")])],
[],
None,
False,
)
tmpl = nodes.Template(
[nodes.Assign(nodes.Name("foo", "store"), nodes.Name("bar", "load")), for_loop]
)
sym = symbols_for_node(tmpl)
assert sym.refs == {
"foo": "l_0_foo",
"bar": "l_0_bar",
"seq": "l_0_seq",
}
assert sym.loads == {
"l_0_foo": ("undefined", None),
"l_0_bar": ("resolve", "bar"),
"l_0_seq": ("resolve", "seq"),
}
sym = symbols_for_node(for_loop, sym)
assert sym.refs == {
"foo": "l_1_foo",
}
assert sym.loads == {
"l_1_foo": ("param", None),
}
def test_complex():
title_block = nodes.Block(
"title", [nodes.Output([nodes.TemplateData("Page Title")])], False, False
)
render_title_macro = nodes.Macro(
"render_title",
[nodes.Name("title", "param")],
[],
[
nodes.Output(
[
nodes.TemplateData('\n <div class="title">\n <h1>'),
nodes.Name("title", "load"),
nodes.TemplateData("</h1>\n <p>"),
nodes.Name("subtitle", "load"),
nodes.TemplateData("</p>\n "),
]
),
nodes.Assign(
nodes.Name("subtitle", "store"), nodes.Const("something else")
),
nodes.Output(
[
nodes.TemplateData("\n <p>"),
nodes.Name("subtitle", "load"),
nodes.TemplateData("</p>\n </div>\n"),
nodes.If(
nodes.Name("something", "load"),
[
nodes.Assign(
nodes.Name("title_upper", "store"),
nodes.Filter(
nodes.Name("title", "load"),
"upper",
[],
[],
None,
None,
),
),
nodes.Output(
[
nodes.Name("title_upper", "load"),
nodes.Call(
nodes.Name("render_title", "load"),
[nodes.Const("Aha")],
[],
None,
None,
),
]
),
],
[],
[],
),
]
),
],
)
for_loop = nodes.For(
nodes.Name("item", "store"),
nodes.Name("seq", "load"),
[
nodes.Output(
[
nodes.TemplateData("\n <li>"),
nodes.Name("item", "load"),
nodes.TemplateData("</li>\n <span>"),
]
),
nodes.Include(nodes.Const("helper.html"), True, False),
nodes.Output([nodes.TemplateData("</span>\n ")]),
],
[],
None,
False,
)
body_block = nodes.Block(
"body",
[
nodes.Output(
[
nodes.TemplateData("\n "),
nodes.Call(
nodes.Name("render_title", "load"),
[nodes.Name("item", "load")],
[],
None,
None,
),
nodes.TemplateData("\n <ul>\n "),
]
),
for_loop,
nodes.Output([nodes.TemplateData("\n </ul>\n")]),
],
False,
False,
)
tmpl = nodes.Template(
[
nodes.Extends(nodes.Const("layout.html")),
title_block,
render_title_macro,
body_block,
]
)
tmpl_sym = symbols_for_node(tmpl)
assert tmpl_sym.refs == {
"render_title": "l_0_render_title",
}
assert tmpl_sym.loads == {
"l_0_render_title": ("undefined", None),
}
assert tmpl_sym.stores == {"render_title"}
assert tmpl_sym.dump_stores() == {
"render_title": "l_0_render_title",
}
macro_sym = symbols_for_node(render_title_macro, tmpl_sym)
assert macro_sym.refs == {
"subtitle": "l_1_subtitle",
"something": "l_1_something",
"title": "l_1_title",
"title_upper": "l_1_title_upper",
}
assert macro_sym.loads == {
"l_1_subtitle": ("resolve", "subtitle"),
"l_1_something": ("resolve", "something"),
"l_1_title": ("param", None),
"l_1_title_upper": ("resolve", "title_upper"),
}
assert macro_sym.stores == {"title", "title_upper", "subtitle"}
assert macro_sym.find_ref("render_title") == "l_0_render_title"
assert macro_sym.dump_stores() == {
"title": "l_1_title",
"title_upper": "l_1_title_upper",
"subtitle": "l_1_subtitle",
"render_title": "l_0_render_title",
}
body_sym = symbols_for_node(body_block)
assert body_sym.refs == {
"item": "l_0_item",
"seq": "l_0_seq",
"render_title": "l_0_render_title",
}
assert body_sym.loads == {
"l_0_item": ("resolve", "item"),
"l_0_seq": ("resolve", "seq"),
"l_0_render_title": ("resolve", "render_title"),
}
assert body_sym.stores == set()
for_sym = symbols_for_node(for_loop, body_sym)
assert for_sym.refs == {
"item": "l_1_item",
}
assert for_sym.loads == {
"l_1_item": ("param", None),
}
assert for_sym.stores == {"item"}
assert for_sym.dump_stores() == {
"item": "l_1_item",
}
def test_if_branching_stores():
tmpl = nodes.Template(
[
nodes.If(
nodes.Name("expression", "load"),
[nodes.Assign(nodes.Name("variable", "store"), nodes.Const(42))],
[],
[],
)
]
)
sym = symbols_for_node(tmpl)
assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"}
assert sym.stores == {"variable"}
assert sym.loads == {
"l_0_variable": ("resolve", "variable"),
"l_0_expression": ("resolve", "expression"),
}
assert sym.dump_stores() == {
"variable": "l_0_variable",
}
def test_if_branching_stores_undefined():
tmpl = nodes.Template(
[
nodes.Assign(nodes.Name("variable", "store"), nodes.Const(23)),
nodes.If(
nodes.Name("expression", "load"),
[nodes.Assign(nodes.Name("variable", "store"), nodes.Const(42))],
[],
[],
),
]
)
sym = symbols_for_node(tmpl)
assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"}
assert sym.stores == {"variable"}
assert sym.loads == {
"l_0_variable": ("undefined", None),
"l_0_expression": ("resolve", "expression"),
}
assert sym.dump_stores() == {
"variable": "l_0_variable",
}
def test_if_branching_multi_scope():
for_loop = nodes.For(
nodes.Name("item", "store"),
nodes.Name("seq", "load"),
[
nodes.If(
nodes.Name("expression", "load"),
[nodes.Assign(nodes.Name("x", "store"), nodes.Const(42))],
[],
[],
),
nodes.Include(nodes.Const("helper.html"), True, False),
],
[],
None,
False,
)
tmpl = nodes.Template(
[nodes.Assign(nodes.Name("x", "store"), nodes.Const(23)), for_loop]
)
tmpl_sym = symbols_for_node(tmpl)
for_sym = symbols_for_node(for_loop, tmpl_sym)
assert for_sym.stores == {"item", "x"}
assert for_sym.loads == {
"l_1_x": ("alias", "l_0_x"),
"l_1_item": ("param", None),
"l_1_expression": ("resolve", "expression"),
}
-205
View File
@@ -1,205 +0,0 @@
import pytest
from jinja2.environment import Environment
from jinja2.exceptions import TemplateNotFound
from jinja2.exceptions import TemplatesNotFound
from jinja2.exceptions import TemplateSyntaxError
from jinja2.exceptions import UndefinedError
from jinja2.loaders import DictLoader
@pytest.fixture
def test_env():
env = Environment(
loader=DictLoader(
dict(
module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}",
header="[{{ foo }}|{{ 23 }}]",
o_printer="({{ o }})",
)
)
)
env.globals["bar"] = 23
return env
class TestImports:
def test_context_imports(self, test_env):
t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
assert t.render(foo=42) == "[|23]"
t = test_env.from_string(
'{% import "module" as m without context %}{{ m.test() }}'
)
assert t.render(foo=42) == "[|23]"
t = test_env.from_string(
'{% import "module" as m with context %}{{ m.test() }}'
)
assert t.render(foo=42) == "[42|23]"
t = test_env.from_string('{% from "module" import test %}{{ test() }}')
assert t.render(foo=42) == "[|23]"
t = test_env.from_string(
'{% from "module" import test without context %}{{ test() }}'
)
assert t.render(foo=42) == "[|23]"
t = test_env.from_string(
'{% from "module" import test with context %}{{ test() }}'
)
assert t.render(foo=42) == "[42|23]"
def test_import_needs_name(self, test_env):
test_env.from_string('{% from "foo" import bar %}')
test_env.from_string('{% from "foo" import bar, baz %}')
with pytest.raises(TemplateSyntaxError):
test_env.from_string('{% from "foo" import %}')
def test_no_trailing_comma(self, test_env):
with pytest.raises(TemplateSyntaxError):
test_env.from_string('{% from "foo" import bar, %}')
with pytest.raises(TemplateSyntaxError):
test_env.from_string('{% from "foo" import bar,, %}')
with pytest.raises(TemplateSyntaxError):
test_env.from_string('{% from "foo" import, %}')
def test_trailing_comma_with_context(self, test_env):
test_env.from_string('{% from "foo" import bar, baz with context %}')
test_env.from_string('{% from "foo" import bar, baz, with context %}')
test_env.from_string('{% from "foo" import bar, with context %}')
test_env.from_string('{% from "foo" import bar, with, context %}')
test_env.from_string('{% from "foo" import bar, with with context %}')
with pytest.raises(TemplateSyntaxError):
test_env.from_string('{% from "foo" import bar,, with context %}')
with pytest.raises(TemplateSyntaxError):
test_env.from_string('{% from "foo" import bar with context, %}')
def test_exports(self, test_env):
m = test_env.from_string(
"""
{% macro toplevel() %}...{% endmacro %}
{% macro __private() %}...{% endmacro %}
{% set variable = 42 %}
{% for item in [1] %}
{% macro notthere() %}{% endmacro %}
{% endfor %}
"""
).module
assert m.toplevel() == "..."
assert not hasattr(m, "__missing")
assert m.variable == 42
assert not hasattr(m, "notthere")
def test_not_exported(self, test_env):
t = test_env.from_string("{% from 'module' import nothing %}{{ nothing() }}")
with pytest.raises(UndefinedError, match="does not export the requested name"):
t.render()
def test_import_with_globals(self, test_env):
t = test_env.from_string(
'{% import "module" as m %}{{ m.test() }}', globals={"foo": 42}
)
assert t.render() == "[42|23]"
t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
assert t.render() == "[|23]"
def test_import_with_globals_override(self, test_env):
t = test_env.from_string(
'{% set foo = 41 %}{% import "module" as m %}{{ m.test() }}',
globals={"foo": 42},
)
assert t.render() == "[42|23]"
def test_from_import_with_globals(self, test_env):
t = test_env.from_string(
'{% from "module" import test %}{{ test() }}',
globals={"foo": 42},
)
assert t.render() == "[42|23]"
class TestIncludes:
def test_context_include(self, test_env):
t = test_env.from_string('{% include "header" %}')
assert t.render(foo=42) == "[42|23]"
t = test_env.from_string('{% include "header" with context %}')
assert t.render(foo=42) == "[42|23]"
t = test_env.from_string('{% include "header" without context %}')
assert t.render(foo=42) == "[|23]"
def test_choice_includes(self, test_env):
t = test_env.from_string('{% include ["missing", "header"] %}')
assert t.render(foo=42) == "[42|23]"
t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}')
assert t.render(foo=42) == ""
t = test_env.from_string('{% include ["missing", "missing2"] %}')
pytest.raises(TemplateNotFound, t.render)
with pytest.raises(TemplatesNotFound) as e:
t.render()
assert e.value.templates == ["missing", "missing2"]
assert e.value.name == "missing2"
def test_includes(t, **ctx):
ctx["foo"] = 42
assert t.render(ctx) == "[42|23]"
t = test_env.from_string('{% include ["missing", "header"] %}')
test_includes(t)
t = test_env.from_string("{% include x %}")
test_includes(t, x=["missing", "header"])
t = test_env.from_string('{% include [x, "header"] %}')
test_includes(t, x="missing")
t = test_env.from_string("{% include x %}")
test_includes(t, x="header")
t = test_env.from_string("{% include [x] %}")
test_includes(t, x="header")
def test_include_ignoring_missing(self, test_env):
t = test_env.from_string('{% include "missing" %}')
pytest.raises(TemplateNotFound, t.render)
for extra in "", "with context", "without context":
t = test_env.from_string(
'{% include "missing" ignore missing ' + extra + " %}"
)
assert t.render() == ""
def test_context_include_with_overrides(self, test_env):
env = Environment(
loader=DictLoader(
dict(
main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
item="{{ item }}",
)
)
)
assert env.get_template("main").render() == "123"
def test_unoptimized_scopes(self, test_env):
t = test_env.from_string(
"""
{% macro outer(o) %}
{% macro inner() %}
{% include "o_printer" %}
{% endmacro %}
{{ inner() }}
{% endmacro %}
{{ outer("FOO") }}
"""
)
assert t.render().strip() == "(FOO)"
def test_import_from_with_context(self):
env = Environment(
loader=DictLoader({"a": "{% macro x() %}{{ foobar }}{% endmacro %}"})
)
t = env.from_string(
"{% set foobar = 42 %}{% from 'a' import x with context %}{{ x() }}"
)
assert t.render() == "42"
-405
View File
@@ -1,405 +0,0 @@
import pytest
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import TemplateRuntimeError
from jinja2 import TemplateSyntaxError
LAYOUTTEMPLATE = """\
|{% block block1 %}block 1 from layout{% endblock %}
|{% block block2 %}block 2 from layout{% endblock %}
|{% block block3 %}
{% block block4 %}nested block 4 from layout{% endblock %}
{% endblock %}|"""
LEVEL1TEMPLATE = """\
{% extends "layout" %}
{% block block1 %}block 1 from level1{% endblock %}"""
LEVEL2TEMPLATE = """\
{% extends "level1" %}
{% block block2 %}{% block block5 %}nested block 5 from level2{%
endblock %}{% endblock %}"""
LEVEL3TEMPLATE = """\
{% extends "level2" %}
{% block block5 %}block 5 from level3{% endblock %}
{% block block4 %}block 4 from level3{% endblock %}
"""
LEVEL4TEMPLATE = """\
{% extends "level3" %}
{% block block3 %}block 3 from level4{% endblock %}
"""
WORKINGTEMPLATE = """\
{% extends "layout" %}
{% block block1 %}
{% if false %}
{% block block2 %}
this should work
{% endblock %}
{% endif %}
{% endblock %}
"""
DOUBLEEXTENDS = """\
{% extends "layout" %}
{% extends "layout" %}
{% block block1 %}
{% if false %}
{% block block2 %}
this should work
{% endblock %}
{% endif %}
{% endblock %}
"""
@pytest.fixture
def env():
return Environment(
loader=DictLoader(
{
"layout": LAYOUTTEMPLATE,
"level1": LEVEL1TEMPLATE,
"level2": LEVEL2TEMPLATE,
"level3": LEVEL3TEMPLATE,
"level4": LEVEL4TEMPLATE,
"working": WORKINGTEMPLATE,
"doublee": DOUBLEEXTENDS,
}
),
trim_blocks=True,
)
class TestInheritance:
def test_layout(self, env):
tmpl = env.get_template("layout")
assert tmpl.render() == (
"|block 1 from layout|block 2 from layout|nested block 4 from layout|"
)
def test_level1(self, env):
tmpl = env.get_template("level1")
assert tmpl.render() == (
"|block 1 from level1|block 2 from layout|nested block 4 from layout|"
)
def test_level2(self, env):
tmpl = env.get_template("level2")
assert tmpl.render() == (
"|block 1 from level1|nested block 5 from "
"level2|nested block 4 from layout|"
)
def test_level3(self, env):
tmpl = env.get_template("level3")
assert tmpl.render() == (
"|block 1 from level1|block 5 from level3|block 4 from level3|"
)
def test_level4(self, env):
tmpl = env.get_template("level4")
assert tmpl.render() == (
"|block 1 from level1|block 5 from level3|block 3 from level4|"
)
def test_super(self, env):
env = Environment(
loader=DictLoader(
{
"a": "{% block intro %}INTRO{% endblock %}|"
"BEFORE|{% block data %}INNER{% endblock %}|AFTER",
"b": '{% extends "a" %}{% block data %}({{ '
"super() }}){% endblock %}",
"c": '{% extends "b" %}{% block intro %}--{{ '
"super() }}--{% endblock %}\n{% block data "
"%}[{{ super() }}]{% endblock %}",
}
)
)
tmpl = env.get_template("c")
assert tmpl.render() == "--INTRO--|BEFORE|[(INNER)]|AFTER"
def test_working(self, env):
env.get_template("working")
def test_reuse_blocks(self, env):
tmpl = env.from_string(
"{{ self.foo() }}|{% block foo %}42{% endblock %}|{{ self.foo() }}"
)
assert tmpl.render() == "42|42|42"
def test_preserve_blocks(self, env):
env = Environment(
loader=DictLoader(
{
"a": "{% if false %}{% block x %}A{% endblock %}"
"{% endif %}{{ self.x() }}",
"b": '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}',
}
)
)
tmpl = env.get_template("b")
assert tmpl.render() == "BA"
def test_dynamic_inheritance(self, env):
env = Environment(
loader=DictLoader(
{
"default1": "DEFAULT1{% block x %}{% endblock %}",
"default2": "DEFAULT2{% block x %}{% endblock %}",
"child": "{% extends default %}{% block x %}CHILD{% endblock %}",
}
)
)
tmpl = env.get_template("child")
for m in range(1, 3):
assert tmpl.render(default=f"default{m}") == f"DEFAULT{m}CHILD"
def test_multi_inheritance(self, env):
env = Environment(
loader=DictLoader(
{
"default1": "DEFAULT1{% block x %}{% endblock %}",
"default2": "DEFAULT2{% block x %}{% endblock %}",
"child": (
"{% if default %}{% extends default %}{% else %}"
"{% extends 'default1' %}{% endif %}"
"{% block x %}CHILD{% endblock %}"
),
}
)
)
tmpl = env.get_template("child")
assert tmpl.render(default="default2") == "DEFAULT2CHILD"
assert tmpl.render(default="default1") == "DEFAULT1CHILD"
assert tmpl.render() == "DEFAULT1CHILD"
def test_scoped_block(self, env):
env = Environment(
loader=DictLoader(
{
"default.html": "{% for item in seq %}[{% block item scoped %}"
"{% endblock %}]{% endfor %}"
}
)
)
t = env.from_string(
"{% extends 'default.html' %}{% block item %}{{ item }}{% endblock %}"
)
assert t.render(seq=list(range(5))) == "[0][1][2][3][4]"
def test_super_in_scoped_block(self, env):
env = Environment(
loader=DictLoader(
{
"default.html": "{% for item in seq %}[{% block item scoped %}"
"{{ item }}{% endblock %}]{% endfor %}"
}
)
)
t = env.from_string(
'{% extends "default.html" %}{% block item %}'
"{{ super() }}|{{ item * 2 }}{% endblock %}"
)
assert t.render(seq=list(range(5))) == "[0|0][1|2][2|4][3|6][4|8]"
def test_scoped_block_after_inheritance(self, env):
env = Environment(
loader=DictLoader(
{
"layout.html": """
{% block useless %}{% endblock %}
""",
"index.html": """
{%- extends 'layout.html' %}
{% from 'helpers.html' import foo with context %}
{% block useless %}
{% for x in [1, 2, 3] %}
{% block testing scoped %}
{{ foo(x) }}
{% endblock %}
{% endfor %}
{% endblock %}
""",
"helpers.html": """
{% macro foo(x) %}{{ the_foo + x }}{% endmacro %}
""",
}
)
)
rv = env.get_template("index.html").render(the_foo=42).split()
assert rv == ["43", "44", "45"]
def test_level1_required(self, env):
env = Environment(
loader=DictLoader(
{
"default": "{% block x required %}{# comment #}\n {% endblock %}",
"level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}",
}
)
)
rv = env.get_template("level1").render()
assert rv == "[1]"
def test_level2_required(self, env):
env = Environment(
loader=DictLoader(
{
"default": "{% block x required %}{% endblock %}",
"level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}",
"level2": "{% extends 'default' %}{% block x %}[2]{% endblock %}",
}
)
)
rv1 = env.get_template("level1").render()
rv2 = env.get_template("level2").render()
assert rv1 == "[1]"
assert rv2 == "[2]"
def test_level3_required(self, env):
env = Environment(
loader=DictLoader(
{
"default": "{% block x required %}{% endblock %}",
"level1": "{% extends 'default' %}",
"level2": "{% extends 'level1' %}{% block x %}[2]{% endblock %}",
"level3": "{% extends 'level2' %}",
}
)
)
t1 = env.get_template("level1")
t2 = env.get_template("level2")
t3 = env.get_template("level3")
with pytest.raises(TemplateRuntimeError, match="Required block 'x' not found"):
assert t1.render()
assert t2.render() == "[2]"
assert t3.render() == "[2]"
def test_invalid_required(self, env):
env = Environment(
loader=DictLoader(
{
"default": "{% block x required %}data {# #}{% endblock %}",
"default1": "{% block x required %}{% block y %}"
"{% endblock %} {% endblock %}",
"default2": "{% block x required %}{% if true %}"
"{% endif %} {% endblock %}",
"level1": "{% if default %}{% extends default %}"
"{% else %}{% extends 'default' %}{% endif %}"
"{%- block x %}CHILD{% endblock %}",
}
)
)
t = env.get_template("level1")
with pytest.raises(
TemplateSyntaxError,
match="Required blocks can only contain comments or whitespace",
):
assert t.render(default="default")
assert t.render(default="default2")
assert t.render(default="default3")
def test_required_with_scope(self, env):
env = Environment(
loader=DictLoader(
{
"default1": "{% for item in seq %}[{% block item scoped required %}"
"{% endblock %}]{% endfor %}",
"child1": "{% extends 'default1' %}{% block item %}"
"{{ item }}{% endblock %}",
"default2": "{% for item in seq %}[{% block item required scoped %}"
"{% endblock %}]{% endfor %}",
"child2": "{% extends 'default2' %}{% block item %}"
"{{ item }}{% endblock %}",
}
)
)
t1 = env.get_template("child1")
t2 = env.get_template("child2")
assert t1.render(seq=list(range(3))) == "[0][1][2]"
# scoped must come before required
with pytest.raises(TemplateSyntaxError):
t2.render(seq=list(range(3)))
def test_duplicate_required_or_scoped(self, env):
env = Environment(
loader=DictLoader(
{
"default1": "{% for item in seq %}[{% block item "
"scoped scoped %}}{{% endblock %}}]{{% endfor %}}",
"default2": "{% for item in seq %}[{% block item "
"required required %}}{{% endblock %}}]{{% endfor %}}",
"child": "{% if default %}{% extends default %}{% else %}"
"{% extends 'default1' %}{% endif %}{%- block x %}"
"CHILD{% endblock %}",
}
)
)
tmpl = env.get_template("child")
with pytest.raises(TemplateSyntaxError):
tmpl.render(default="default1", seq=list(range(3)))
tmpl.render(default="default2", seq=list(range(3)))
class TestBugFix:
def test_fixed_macro_scoping_bug(self, env):
assert (
Environment(
loader=DictLoader(
{
"test.html": """\
{% extends 'details.html' %}
{% macro my_macro() %}
my_macro
{% endmacro %}
{% block inner_box %}
{{ my_macro() }}
{% endblock %}
""",
"details.html": """\
{% extends 'standard.html' %}
{% macro my_macro() %}
my_macro
{% endmacro %}
{% block content %}
{% block outer_box %}
outer_box
{% block inner_box %}
inner_box
{% endblock %}
{% endblock %}
{% endblock %}
""",
"standard.html": """
{% block content %}&nbsp;{% endblock %}
""",
}
)
)
.get_template("test.html")
.render()
.split()
== ["outer_box", "my_macro"]
)
def test_double_extends(self, env):
"""Ensures that a template with more than 1 {% extends ... %} usage
raises a ``TemplateError``.
"""
with pytest.raises(TemplateRuntimeError, match="extended multiple times"):
env.get_template("doublee").render()
File diff suppressed because it is too large Load Diff
-412
View File
@@ -1,412 +0,0 @@
import importlib.abc
import importlib.machinery
import importlib.util
import os
import platform
import shutil
import sys
import tempfile
import time
import weakref
from pathlib import Path
import pytest
from jinja2 import Environment
from jinja2 import loaders
from jinja2 import PackageLoader
from jinja2.exceptions import TemplateNotFound
from jinja2.loaders import split_template_path
class TestLoaders:
def test_dict_loader(self, dict_loader):
env = Environment(loader=dict_loader)
tmpl = env.get_template("justdict.html")
assert tmpl.render().strip() == "FOO"
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
def test_package_loader(self, package_loader):
env = Environment(loader=package_loader)
tmpl = env.get_template("test.html")
assert tmpl.render().strip() == "BAR"
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
def test_filesystem_loader_overlapping_names(self, filesystem_loader):
t2_dir = Path(filesystem_loader.searchpath[0]) / ".." / "templates2"
# Make "foo" show up before "foo/test.html".
filesystem_loader.searchpath.insert(0, t2_dir)
e = Environment(loader=filesystem_loader)
e.get_template("foo")
# This would raise NotADirectoryError if "t2/foo" wasn't skipped.
e.get_template("foo/test.html")
def test_choice_loader(self, choice_loader):
env = Environment(loader=choice_loader)
tmpl = env.get_template("justdict.html")
assert tmpl.render().strip() == "FOO"
tmpl = env.get_template("test.html")
assert tmpl.render().strip() == "BAR"
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
def test_function_loader(self, function_loader):
env = Environment(loader=function_loader)
tmpl = env.get_template("justfunction.html")
assert tmpl.render().strip() == "FOO"
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
def test_prefix_loader(self, prefix_loader):
env = Environment(loader=prefix_loader)
tmpl = env.get_template("a/test.html")
assert tmpl.render().strip() == "BAR"
tmpl = env.get_template("b/justdict.html")
assert tmpl.render().strip() == "FOO"
pytest.raises(TemplateNotFound, env.get_template, "missing")
def test_caching(self):
changed = False
class TestLoader(loaders.BaseLoader):
def get_source(self, environment, template):
return "foo", None, lambda: not changed
env = Environment(loader=TestLoader(), cache_size=-1)
tmpl = env.get_template("template")
assert tmpl is env.get_template("template")
changed = True
assert tmpl is not env.get_template("template")
changed = False
def test_no_cache(self):
mapping = {"foo": "one"}
env = Environment(loader=loaders.DictLoader(mapping), cache_size=0)
assert env.get_template("foo") is not env.get_template("foo")
def test_limited_size_cache(self):
mapping = {"one": "foo", "two": "bar", "three": "baz"}
loader = loaders.DictLoader(mapping)
env = Environment(loader=loader, cache_size=2)
t1 = env.get_template("one")
t2 = env.get_template("two")
assert t2 is env.get_template("two")
assert t1 is env.get_template("one")
env.get_template("three")
loader_ref = weakref.ref(loader)
assert (loader_ref, "one") in env.cache
assert (loader_ref, "two") not in env.cache
assert (loader_ref, "three") in env.cache
def test_cache_loader_change(self):
loader1 = loaders.DictLoader({"foo": "one"})
loader2 = loaders.DictLoader({"foo": "two"})
env = Environment(loader=loader1, cache_size=2)
assert env.get_template("foo").render() == "one"
env.loader = loader2
assert env.get_template("foo").render() == "two"
def test_dict_loader_cache_invalidates(self):
mapping = {"foo": "one"}
env = Environment(loader=loaders.DictLoader(mapping))
assert env.get_template("foo").render() == "one"
mapping["foo"] = "two"
assert env.get_template("foo").render() == "two"
def test_split_template_path(self):
assert split_template_path("foo/bar") == ["foo", "bar"]
assert split_template_path("./foo/bar") == ["foo", "bar"]
pytest.raises(TemplateNotFound, split_template_path, "../foo")
class TestFileSystemLoader:
searchpath = (Path(__file__) / ".." / "res" / "templates").resolve()
@staticmethod
def _test_common(env):
tmpl = env.get_template("test.html")
assert tmpl.render().strip() == "BAR"
tmpl = env.get_template("foo/test.html")
assert tmpl.render().strip() == "FOO"
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
def test_searchpath_as_str(self):
filesystem_loader = loaders.FileSystemLoader(str(self.searchpath))
env = Environment(loader=filesystem_loader)
self._test_common(env)
def test_searchpath_as_pathlib(self):
filesystem_loader = loaders.FileSystemLoader(self.searchpath)
env = Environment(loader=filesystem_loader)
self._test_common(env)
def test_searchpath_as_list_including_pathlib(self):
filesystem_loader = loaders.FileSystemLoader(
["/tmp/templates", self.searchpath]
)
env = Environment(loader=filesystem_loader)
self._test_common(env)
def test_caches_template_based_on_mtime(self):
filesystem_loader = loaders.FileSystemLoader(self.searchpath)
env = Environment(loader=filesystem_loader)
tmpl1 = env.get_template("test.html")
tmpl2 = env.get_template("test.html")
assert tmpl1 is tmpl2
os.utime(self.searchpath / "test.html", (time.time(), time.time()))
tmpl3 = env.get_template("test.html")
assert tmpl1 is not tmpl3
@pytest.mark.parametrize(
("encoding", "expect"),
[
("utf-8", "文字化け"),
("iso-8859-1", "æ\x96\x87\xe5\xad\x97\xe5\x8c\x96\xe3\x81\x91"),
],
)
def test_uses_specified_encoding(self, encoding, expect):
loader = loaders.FileSystemLoader(self.searchpath, encoding=encoding)
e = Environment(loader=loader)
t = e.get_template("mojibake.txt")
assert t.render() == expect
def test_filename_normpath(self):
"""Nested template names should only contain ``os.sep`` in the
loaded filename.
"""
loader = loaders.FileSystemLoader(self.searchpath)
e = Environment(loader=loader)
t = e.get_template("foo/test.html")
assert t.filename == str(self.searchpath / "foo" / "test.html")
class TestModuleLoader:
archive = None
def compile_down(self, prefix_loader, zip="deflated"):
log = []
self.reg_env = Environment(loader=prefix_loader)
if zip is not None:
fd, self.archive = tempfile.mkstemp(suffix=".zip")
os.close(fd)
else:
self.archive = tempfile.mkdtemp()
self.reg_env.compile_templates(self.archive, zip=zip, log_function=log.append)
self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
return "".join(log)
def teardown(self):
if hasattr(self, "mod_env"):
if os.path.isfile(self.archive):
os.remove(self.archive)
else:
shutil.rmtree(self.archive)
self.archive = None
def test_log(self, prefix_loader):
log = self.compile_down(prefix_loader)
assert (
'Compiled "a/foo/test.html" as '
"tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a" in log
)
assert "Finished compiling templates" in log
assert (
'Could not compile "a/syntaxerror.html": '
"Encountered unknown tag 'endif'" in log
)
def _test_common(self):
tmpl1 = self.reg_env.get_template("a/test.html")
tmpl2 = self.mod_env.get_template("a/test.html")
assert tmpl1.render() == tmpl2.render()
tmpl1 = self.reg_env.get_template("b/justdict.html")
tmpl2 = self.mod_env.get_template("b/justdict.html")
assert tmpl1.render() == tmpl2.render()
def test_deflated_zip_compile(self, prefix_loader):
self.compile_down(prefix_loader, zip="deflated")
self._test_common()
def test_stored_zip_compile(self, prefix_loader):
self.compile_down(prefix_loader, zip="stored")
self._test_common()
def test_filesystem_compile(self, prefix_loader):
self.compile_down(prefix_loader, zip=None)
self._test_common()
def test_weak_references(self, prefix_loader):
self.compile_down(prefix_loader)
self.mod_env.get_template("a/test.html")
key = loaders.ModuleLoader.get_template_key("a/test.html")
name = self.mod_env.loader.module.__name__
assert hasattr(self.mod_env.loader.module, key)
assert name in sys.modules
# unset all, ensure the module is gone from sys.modules
self.mod_env = None
try:
import gc
gc.collect()
except BaseException:
pass
assert name not in sys.modules
def test_choice_loader(self, prefix_loader):
self.compile_down(prefix_loader)
self.mod_env.loader = loaders.ChoiceLoader(
[self.mod_env.loader, loaders.DictLoader({"DICT_SOURCE": "DICT_TEMPLATE"})]
)
tmpl1 = self.mod_env.get_template("a/test.html")
assert tmpl1.render() == "BAR"
tmpl2 = self.mod_env.get_template("DICT_SOURCE")
assert tmpl2.render() == "DICT_TEMPLATE"
def test_prefix_loader(self, prefix_loader):
self.compile_down(prefix_loader)
self.mod_env.loader = loaders.PrefixLoader(
{
"MOD": self.mod_env.loader,
"DICT": loaders.DictLoader({"test.html": "DICT_TEMPLATE"}),
}
)
tmpl1 = self.mod_env.get_template("MOD/a/test.html")
assert tmpl1.render() == "BAR"
tmpl2 = self.mod_env.get_template("DICT/test.html")
assert tmpl2.render() == "DICT_TEMPLATE"
def test_path_as_pathlib(self, prefix_loader):
self.compile_down(prefix_loader)
mod_path = self.mod_env.loader.module.__path__[0]
mod_loader = loaders.ModuleLoader(Path(mod_path))
self.mod_env = Environment(loader=mod_loader)
self._test_common()
def test_supports_pathlib_in_list_of_paths(self, prefix_loader):
self.compile_down(prefix_loader)
mod_path = self.mod_env.loader.module.__path__[0]
mod_loader = loaders.ModuleLoader([Path(mod_path), "/tmp/templates"])
self.mod_env = Environment(loader=mod_loader)
self._test_common()
@pytest.fixture()
def package_dir_loader(monkeypatch):
monkeypatch.syspath_prepend(Path(__file__).parent)
return PackageLoader("res")
@pytest.mark.parametrize(
("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")]
)
def test_package_dir_source(package_dir_loader, template, expect):
source, name, up_to_date = package_dir_loader.get_source(None, template)
assert source.rstrip() == expect
assert name.endswith(os.path.join(*split_template_path(template)))
assert up_to_date()
def test_package_dir_list(package_dir_loader):
templates = package_dir_loader.list_templates()
assert "foo/test.html" in templates
assert "test.html" in templates
@pytest.fixture()
def package_file_loader(monkeypatch):
monkeypatch.syspath_prepend(Path(__file__).parent / "res")
return PackageLoader("__init__")
@pytest.mark.parametrize(
("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")]
)
def test_package_file_source(package_file_loader, template, expect):
source, name, up_to_date = package_file_loader.get_source(None, template)
assert source.rstrip() == expect
assert name.endswith(os.path.join(*split_template_path(template)))
assert up_to_date()
def test_package_file_list(package_file_loader):
templates = package_file_loader.list_templates()
assert "foo/test.html" in templates
assert "test.html" in templates
@pytest.fixture()
def package_zip_loader(monkeypatch):
package_zip = (Path(__file__) / ".." / "res" / "package.zip").resolve()
monkeypatch.syspath_prepend(package_zip)
return PackageLoader("t_pack")
@pytest.mark.parametrize(
("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")]
)
def test_package_zip_source(package_zip_loader, template, expect):
source, name, up_to_date = package_zip_loader.get_source(None, template)
assert source.rstrip() == expect
assert name.endswith(os.path.join(*split_template_path(template)))
assert up_to_date is None
@pytest.mark.xfail(
platform.python_implementation() == "PyPy",
reason="PyPy's zipimporter doesn't have a '_files' attribute.",
raises=TypeError,
)
def test_package_zip_list(package_zip_loader):
assert package_zip_loader.list_templates() == ["foo/test.html", "test.html"]
@pytest.mark.parametrize("package_path", ["", ".", "./"])
def test_package_zip_omit_curdir(package_zip_loader, package_path):
"""PackageLoader should not add or include "." or "./" in the root
path, it is invalid in zip paths.
"""
loader = PackageLoader("t_pack", package_path)
assert loader.package_path == ""
source, _, _ = loader.get_source(None, "templates/foo/test.html")
assert source.rstrip() == "FOO"
def test_pep_451_import_hook():
class ImportHook(importlib.abc.MetaPathFinder, importlib.abc.Loader):
def find_spec(self, name, path=None, target=None):
if name != "res":
return None
spec = importlib.machinery.PathFinder.find_spec(name)
return importlib.util.spec_from_file_location(
name,
spec.origin,
loader=self,
submodule_search_locations=spec.submodule_search_locations,
)
def create_module(self, spec):
return None # default behaviour is fine
def exec_module(self, module):
return None # we need this to satisfy the interface, it's wrong
# ensure we restore `sys.meta_path` after putting in our loader
before = sys.meta_path[:]
try:
sys.meta_path.insert(0, ImportHook())
package_loader = PackageLoader("res")
assert "test.html" in package_loader.list_templates()
finally:
sys.meta_path[:] = before
-162
View File
@@ -1,162 +0,0 @@
import math
import pytest
from jinja2.exceptions import UndefinedError
from jinja2.nativetypes import NativeEnvironment
from jinja2.nativetypes import NativeTemplate
from jinja2.runtime import Undefined
@pytest.fixture
def env():
return NativeEnvironment()
def test_is_defined_native_return(env):
t = env.from_string("{{ missing is defined }}")
assert not t.render()
def test_undefined_native_return(env):
t = env.from_string("{{ missing }}")
assert isinstance(t.render(), Undefined)
def test_adding_undefined_native_return(env):
t = env.from_string("{{ 3 + missing }}")
with pytest.raises(UndefinedError):
t.render()
def test_cast_int(env):
t = env.from_string("{{ value|int }}")
result = t.render(value="3")
assert isinstance(result, int)
assert result == 3
def test_list_add(env):
t = env.from_string("{{ a + b }}")
result = t.render(a=["a", "b"], b=["c", "d"])
assert isinstance(result, list)
assert result == ["a", "b", "c", "d"]
def test_multi_expression_add(env):
t = env.from_string("{{ a }} + {{ b }}")
result = t.render(a=["a", "b"], b=["c", "d"])
assert not isinstance(result, list)
assert result == "['a', 'b'] + ['c', 'd']"
def test_loops(env):
t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
result = t.render(value=["a", "b", "c", "d"])
assert isinstance(result, str)
assert result == "abcd"
def test_loops_with_ints(env):
t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
result = t.render(value=[1, 2, 3, 4])
assert isinstance(result, int)
assert result == 1234
def test_loop_look_alike(env):
t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
result = t.render(value=[1])
assert isinstance(result, int)
assert result == 1
@pytest.mark.parametrize(
("source", "expect"),
(
("{{ value }}", True),
("{{ value }}", False),
("{{ 1 == 1 }}", True),
("{{ 2 + 2 == 5 }}", False),
("{{ None is none }}", True),
("{{ '' == None }}", False),
),
)
def test_booleans(env, source, expect):
t = env.from_string(source)
result = t.render(value=expect)
assert isinstance(result, bool)
assert result is expect
def test_variable_dunder(env):
t = env.from_string("{{ x.__class__ }}")
result = t.render(x=True)
assert isinstance(result, type)
def test_constant_dunder(env):
t = env.from_string("{{ true.__class__ }}")
result = t.render()
assert isinstance(result, type)
def test_constant_dunder_to_string(env):
t = env.from_string("{{ true.__class__|string }}")
result = t.render()
assert not isinstance(result, type)
assert result in {"<type 'bool'>", "<class 'bool'>"}
def test_string_literal_var(env):
t = env.from_string("[{{ 'all' }}]")
result = t.render()
assert isinstance(result, str)
assert result == "[all]"
def test_string_top_level(env):
t = env.from_string("'Jinja'")
result = t.render()
assert result == "Jinja"
def test_tuple_of_variable_strings(env):
t = env.from_string("'{{ a }}', 'data', '{{ b }}', b'{{ c }}'")
result = t.render(a=1, b=2, c="bytes")
assert isinstance(result, tuple)
assert result == ("1", "data", "2", b"bytes")
def test_concat_strings_with_quotes(env):
t = env.from_string("--host='{{ host }}' --user \"{{ user }}\"")
result = t.render(host="localhost", user="Jinja")
assert result == "--host='localhost' --user \"Jinja\""
def test_no_intermediate_eval(env):
t = env.from_string("0.000{{ a }}")
result = t.render(a=7)
assert isinstance(result, float)
# If intermediate eval happened, 0.000 would render 0.0, then 7
# would be appended, resulting in 0.07.
assert math.isclose(result, 0.0007)
def test_spontaneous_env():
t = NativeTemplate("{{ true }}")
assert isinstance(t.environment, NativeEnvironment)
def test_leading_spaces(env):
t = env.from_string(" {{ True }}")
result = t.render()
assert result == " True"
def test_macro(env):
t = env.from_string("{%- macro x() -%}{{- [1,2] -}}{%- endmacro -%}{{- x()[1] -}}")
result = t.render()
assert result == 2
assert isinstance(result, int)
-3
View File
@@ -1,3 +0,0 @@
def test_template_hash(env):
template = env.parse("hash test")
hash(template)
-6
View File
@@ -1,6 +0,0 @@
import pickle
def test_environment(env):
env = pickle.loads(pickle.dumps(env))
assert env.from_string("x={{ x }}").render(x=42) == "x=42"
-744
View File
@@ -1,744 +0,0 @@
import pytest
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import PrefixLoader
from jinja2 import Template
from jinja2 import TemplateAssertionError
from jinja2 import TemplateNotFound
from jinja2 import TemplateSyntaxError
from jinja2.utils import pass_context
class TestCorner:
def test_assigned_scoping(self, env):
t = env.from_string(
"""
{%- for item in (1, 2, 3, 4) -%}
[{{ item }}]
{%- endfor %}
{{- item -}}
"""
)
assert t.render(item=42) == "[1][2][3][4]42"
t = env.from_string(
"""
{%- for item in (1, 2, 3, 4) -%}
[{{ item }}]
{%- endfor %}
{%- set item = 42 %}
{{- item -}}
"""
)
assert t.render() == "[1][2][3][4]42"
t = env.from_string(
"""
{%- set item = 42 %}
{%- for item in (1, 2, 3, 4) -%}
[{{ item }}]
{%- endfor %}
{{- item -}}
"""
)
assert t.render() == "[1][2][3][4]42"
def test_closure_scoping(self, env):
t = env.from_string(
"""
{%- set wrapper = "<FOO>" %}
{%- for item in (1, 2, 3, 4) %}
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
{{- wrapper() }}
{%- endfor %}
{{- wrapper -}}
"""
)
assert t.render() == "[1][2][3][4]<FOO>"
t = env.from_string(
"""
{%- for item in (1, 2, 3, 4) %}
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
{{- wrapper() }}
{%- endfor %}
{%- set wrapper = "<FOO>" %}
{{- wrapper -}}
"""
)
assert t.render() == "[1][2][3][4]<FOO>"
t = env.from_string(
"""
{%- for item in (1, 2, 3, 4) %}
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
{{- wrapper() }}
{%- endfor %}
{{- wrapper -}}
"""
)
assert t.render(wrapper=23) == "[1][2][3][4]23"
class TestBug:
def test_keyword_folding(self, env):
env = Environment()
env.filters["testing"] = lambda value, some: value + some
assert (
env.from_string("{{ 'test'|testing(some='stuff') }}").render()
== "teststuff"
)
def test_extends_output_bugs(self, env):
env = Environment(
loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"})
)
t = env.from_string(
'{% if expr %}{% extends "parent.html" %}{% endif %}'
"[[{% block title %}title{% endblock %}]]"
"{% for item in [1, 2, 3] %}({{ item }}){% endfor %}"
)
assert t.render(expr=False) == "[[title]](1)(2)(3)"
assert t.render(expr=True) == "((title))"
def test_urlize_filter_escaping(self, env):
tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
assert (
tmpl.render() == '<a href="http://www.example.org/&lt;foo" rel="noopener">'
"http://www.example.org/&lt;foo</a>"
)
def test_urlize_filter_closing_punctuation(self, env):
tmpl = env.from_string(
'{{ "(see http://www.example.org/?page=subj_<desc.h>)"|urlize }}'
)
assert tmpl.render() == (
'(see <a href="http://www.example.org/?page=subj_&lt;desc.h&gt;" '
'rel="noopener">http://www.example.org/?page=subj_&lt;desc.h&gt;</a>)'
)
def test_loop_call_loop(self, env):
tmpl = env.from_string(
"""
{% macro test() %}
{{ caller() }}
{% endmacro %}
{% for num1 in range(5) %}
{% call test() %}
{% for num2 in range(10) %}
{{ loop.index }}
{% endfor %}
{% endcall %}
{% endfor %}
"""
)
assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5
def test_weird_inline_comment(self, env):
env = Environment(line_statement_prefix="%")
pytest.raises(
TemplateSyntaxError,
env.from_string,
"% for item in seq {# missing #}\n...% endfor",
)
def test_old_macro_loop_scoping_bug(self, env):
tmpl = env.from_string(
"{% for i in (1, 2) %}{{ i }}{% endfor %}"
"{% macro i() %}3{% endmacro %}{{ i() }}"
)
assert tmpl.render() == "123"
def test_partial_conditional_assignments(self, env):
tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}")
assert tmpl.render(a=23) == "23"
assert tmpl.render(b=True) == "42"
def test_stacked_locals_scoping_bug(self, env):
env = Environment(line_statement_prefix="#")
t = env.from_string(
"""\
# for j in [1, 2]:
# set x = 1
# for i in [1, 2]:
# print x
# if i % 2 == 0:
# set x = x + 1
# endif
# endfor
# endfor
# if a
# print 'A'
# elif b
# print 'B'
# elif c == d
# print 'C'
# else
# print 'D'
# endif
"""
)
assert t.render(a=0, b=False, c=42, d=42.0) == "1111C"
def test_stacked_locals_scoping_bug_twoframe(self, env):
t = Template(
"""
{% set x = 1 %}
{% for item in foo %}
{% if item == 1 %}
{% set x = 2 %}
{% endif %}
{% endfor %}
{{ x }}
"""
)
rv = t.render(foo=[1]).strip()
assert rv == "1"
def test_call_with_args(self, env):
t = Template(
"""{% macro dump_users(users) -%}
<ul>
{%- for user in users -%}
<li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
{%- endfor -%}
</ul>
{%- endmacro -%}
{% call(user) dump_users(list_of_user) -%}
<dl>
<dl>Realname</dl>
<dd>{{ user.realname|e }}</dd>
<dl>Description</dl>
<dd>{{ user.description }}</dd>
</dl>
{% endcall %}"""
)
assert [
x.strip()
for x in t.render(
list_of_user=[
{
"username": "apo",
"realname": "something else",
"description": "test",
}
]
).splitlines()
] == [
"<ul><li><p>apo</p><dl>",
"<dl>Realname</dl>",
"<dd>something else</dd>",
"<dl>Description</dl>",
"<dd>test</dd>",
"</dl>",
"</li></ul>",
]
def test_empty_if_condition_fails(self, env):
pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}")
pytest.raises(
TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}"
)
pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}")
def test_recursive_loop_compile(self, env):
Template(
"""
{% for p in foo recursive%}
{{p.bar}}
{% for f in p.fields recursive%}
{{f.baz}}
{{p.bar}}
{% if f.rec %}
{{ loop(f.sub) }}
{% endif %}
{% endfor %}
{% endfor %}
"""
)
Template(
"""
{% for p in foo%}
{{p.bar}}
{% for f in p.fields recursive%}
{{f.baz}}
{{p.bar}}
{% if f.rec %}
{{ loop(f.sub) }}
{% endif %}
{% endfor %}
{% endfor %}
"""
)
def test_else_loop_bug(self, env):
t = Template(
"""
{% for x in y %}
{{ loop.index0 }}
{% else %}
{% for i in range(3) %}{{ i }}{% endfor %}
{% endfor %}
"""
)
assert t.render(y=[]).strip() == "012"
def test_correct_prefix_loader_name(self, env):
env = Environment(loader=PrefixLoader({"foo": DictLoader({})}))
with pytest.raises(TemplateNotFound) as e:
env.get_template("foo/bar.html")
assert e.value.name == "foo/bar.html"
def test_pass_context_callable_class(self, env):
class CallableClass:
@pass_context
def __call__(self, ctx):
return ctx.resolve("hello")
tpl = Template("""{{ callableclass() }}""")
output = tpl.render(callableclass=CallableClass(), hello="TEST")
expected = "TEST"
assert output == expected
def test_block_set_with_extends(self):
env = Environment(
loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"})
)
t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}')
assert t.render() == "[42]"
def test_nested_for_else(self, env):
tmpl = env.from_string(
"{% for x in y %}{{ loop.index0 }}{% else %}"
"{% for i in range(3) %}{{ i }}{% endfor %}"
"{% endfor %}"
)
assert tmpl.render() == "012"
def test_macro_var_bug(self, env):
tmpl = env.from_string(
"""
{% set i = 1 %}
{% macro test() %}
{% for i in range(0, 10) %}{{ i }}{% endfor %}
{% endmacro %}{{ test() }}
"""
)
assert tmpl.render().strip() == "0123456789"
def test_macro_var_bug_advanced(self, env):
tmpl = env.from_string(
"""
{% macro outer() %}
{% set i = 1 %}
{% macro test() %}
{% for i in range(0, 10) %}{{ i }}{% endfor %}
{% endmacro %}{{ test() }}
{% endmacro %}{{ outer() }}
"""
)
assert tmpl.render().strip() == "0123456789"
def test_callable_defaults(self):
env = Environment()
env.globals["get_int"] = lambda: 42
t = env.from_string(
"""
{% macro test(a, b, c=get_int()) -%}
{{ a + b + c }}
{%- endmacro %}
{{ test(1, 2) }}|{{ test(1, 2, 3) }}
"""
)
assert t.render().strip() == "45|6"
def test_macro_escaping(self):
env = Environment(autoescape=lambda x: False)
template = "{% macro m() %}<html>{% endmacro %}"
template += "{% autoescape true %}{{ m() }}{% endautoescape %}"
assert env.from_string(template).render()
def test_macro_scoping(self, env):
tmpl = env.from_string(
"""
{% set n=[1,2,3,4,5] %}
{% for n in [[1,2,3], [3,4,5], [5,6,7]] %}
{% macro x(l) %}
{{ l.pop() }}
{% if l %}{{ x(l) }}{% endif %}
{% endmacro %}
{{ x(n) }}
{% endfor %}
"""
)
assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5]
def test_scopes_and_blocks(self):
env = Environment(
loader=DictLoader(
{
"a.html": """
{%- set foo = 'bar' -%}
{% include 'x.html' -%}
""",
"b.html": """
{%- set foo = 'bar' -%}
{% block test %}{% include 'x.html' %}{% endblock -%}
""",
"c.html": """
{%- set foo = 'bar' -%}
{% block test %}{% set foo = foo
%}{% include 'x.html' %}{% endblock -%}
""",
"x.html": """{{ foo }}|{{ test }}""",
}
)
)
a = env.get_template("a.html")
b = env.get_template("b.html")
c = env.get_template("c.html")
assert a.render(test="x").strip() == "bar|x"
assert b.render(test="x").strip() == "bar|x"
assert c.render(test="x").strip() == "bar|x"
def test_scopes_and_include(self):
env = Environment(
loader=DictLoader(
{
"include.html": "{{ var }}",
"base.html": '{% include "include.html" %}',
"child.html": '{% extends "base.html" %}{% set var = 42 %}',
}
)
)
t = env.get_template("child.html")
assert t.render() == "42"
def test_caller_scoping(self, env):
t = env.from_string(
"""
{% macro detail(icon, value) -%}
{% if value -%}
<p><span class="fa fa-fw fa-{{ icon }}"></span>
{%- if caller is undefined -%}
{{ value }}
{%- else -%}
{{ caller(value, *varargs) }}
{%- endif -%}</p>
{%- endif %}
{%- endmacro %}
{% macro link_detail(icon, value, href) -%}
{% call(value, href) detail(icon, value, href) -%}
<a href="{{ href }}">{{ value }}</a>
{%- endcall %}
{%- endmacro %}
"""
)
assert t.module.link_detail("circle", "Index", "/") == (
'<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>'
)
def test_variable_reuse(self, env):
t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}")
assert t.render(x={"y": [0, 1, 2]}) == "012"
t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}")
assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2"
t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}")
assert t.render(x={"y": [0, 1, 2]}) == "012"
def test_double_caller(self, env):
t = env.from_string(
"{% macro x(caller=none) %}[{% if caller %}"
"{{ caller() }}{% endif %}]{% endmacro %}"
"{{ x() }}{% call x() %}aha!{% endcall %}"
)
assert t.render() == "[][aha!]"
def test_double_caller_no_default(self, env):
with pytest.raises(TemplateAssertionError) as exc_info:
env.from_string(
"{% macro x(caller) %}[{% if caller %}"
"{{ caller() }}{% endif %}]{% endmacro %}"
)
assert exc_info.match(
r'"caller" argument must be omitted or ' r"be given a default"
)
t = env.from_string(
"{% macro x(caller=none) %}[{% if caller %}"
"{{ caller() }}{% endif %}]{% endmacro %}"
)
with pytest.raises(TypeError) as exc_info:
t.module.x(None, caller=lambda: 42)
assert exc_info.match(
r"\'x\' was invoked with two values for the " r"special caller argument"
)
def test_macro_blocks(self, env):
t = env.from_string(
"{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}"
)
assert t.render() == "x"
def test_scoped_block(self, env):
t = env.from_string(
"{% set x = 1 %}{% with x = 2 %}{% block y scoped %}"
"{{ x }}{% endblock %}{% endwith %}"
)
assert t.render() == "2"
def test_recursive_loop_filter(self, env):
t = env.from_string(
"""
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in [site.root] if page.url != this recursive %}
<url><loc>{{ page.url }}</loc></url>
{{- loop(page.children) }}
{%- endfor %}
</urlset>
"""
)
sm = t.render(
this="/foo",
site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}},
)
lines = [x.strip() for x in sm.splitlines() if x.strip()]
assert lines == [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
"<url><loc>/</loc></url>",
"<url><loc>/bar</loc></url>",
"</urlset>",
]
def test_empty_if(self, env):
t = env.from_string("{% if foo %}{% else %}42{% endif %}")
assert t.render(foo=False) == "42"
def test_subproperty_if(self, env):
t = env.from_string(
"{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}"
)
assert (
t.render(
object1={"subproperty1": "value"}, object2={"subproperty2": "value"}
)
== "42"
)
def test_set_and_include(self):
env = Environment(
loader=DictLoader(
{
"inc": "bar",
"main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}',
}
)
)
assert env.get_template("main").render() == "foobar"
def test_loop_include(self):
env = Environment(
loader=DictLoader(
{
"inc": "{{ i }}",
"main": '{% for i in [1, 2, 3] %}{% include "inc" %}{% endfor %}',
}
)
)
assert env.get_template("main").render() == "123"
def test_grouper_repr(self):
from jinja2.filters import _GroupTuple
t = _GroupTuple("foo", [1, 2])
assert t.grouper == "foo"
assert t.list == [1, 2]
assert repr(t) == "('foo', [1, 2])"
assert str(t) == "('foo', [1, 2])"
def test_custom_context(self, env):
from jinja2.runtime import Context
class MyContext(Context):
pass
class MyEnvironment(Environment):
context_class = MyContext
loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'})
env = MyEnvironment(loader=loader)
assert env.get_template("test").render(foobar="test") == "test"
def test_recursive_loop_bug(self, env):
tmpl = env.from_string(
"{%- for value in values recursive %}1{% else %}0{% endfor -%}"
)
assert tmpl.render(values=[]) == "0"
def test_markup_and_chainable_undefined(self):
from markupsafe import Markup
from jinja2.runtime import ChainableUndefined
assert str(Markup(ChainableUndefined())) == ""
def test_scoped_block_loop_vars(self, env):
tmpl = env.from_string(
"""\
Start
{% for i in ["foo", "bar"] -%}
{% block body scoped -%}
{{ loop.index }}) {{ i }}{% if loop.last %} last{% endif -%}
{%- endblock %}
{% endfor -%}
End"""
)
assert tmpl.render() == "Start\n1) foo\n2) bar last\nEnd"
def test_pass_context_loop_vars(self, env):
@pass_context
def test(ctx):
return f"{ctx['i']}{ctx['j']}"
tmpl = env.from_string(
"""\
{% set i = 42 %}
{%- for idx in range(2) -%}
{{ i }}{{ j }}
{% set i = idx -%}
{%- set j = loop.index -%}
{{ test() }}
{{ i }}{{ j }}
{% endfor -%}
{{ i }}{{ j }}"""
)
tmpl.globals["test"] = test
assert tmpl.render() == "42\n01\n01\n42\n12\n12\n42"
def test_pass_context_scoped_loop_vars(self, env):
@pass_context
def test(ctx):
return f"{ctx['i']}"
tmpl = env.from_string(
"""\
{% set i = 42 %}
{%- for idx in range(2) -%}
{{ i }}
{%- set i = loop.index0 -%}
{% block body scoped %}
{{ test() }}
{% endblock -%}
{% endfor -%}
{{ i }}"""
)
tmpl.globals["test"] = test
assert tmpl.render() == "42\n0\n42\n1\n42"
def test_pass_context_in_blocks(self, env):
@pass_context
def test(ctx):
return f"{ctx['i']}"
tmpl = env.from_string(
"""\
{%- set i = 42 -%}
{{ i }}
{% block body -%}
{% set i = 24 -%}
{{ test() }}
{% endblock -%}
{{ i }}"""
)
tmpl.globals["test"] = test
assert tmpl.render() == "42\n24\n42"
def test_pass_context_block_and_loop(self, env):
@pass_context
def test(ctx):
return f"{ctx['i']}"
tmpl = env.from_string(
"""\
{%- set i = 42 -%}
{% for idx in range(2) -%}
{{ test() }}
{%- set i = idx -%}
{% block body scoped %}
{{ test() }}
{% set i = 24 -%}
{{ test() }}
{% endblock -%}
{{ test() }}
{% endfor -%}
{{ test() }}"""
)
tmpl.globals["test"] = test
# values set within a block or loop should not
# show up outside of it
assert tmpl.render() == "42\n0\n24\n0\n42\n1\n24\n1\n42"
@pytest.mark.parametrize("op", ["extends", "include"])
def test_cached_extends(self, op):
env = Environment(
loader=DictLoader(
{"base": "{{ x }} {{ y }}", "main": f"{{% {op} 'base' %}}"}
)
)
env.globals["x"] = "x"
env.globals["y"] = "y"
# template globals overlay env globals
tmpl = env.get_template("main", globals={"x": "bar"})
assert tmpl.render() == "bar y"
# base was loaded indirectly, it just has env globals
tmpl = env.get_template("base")
assert tmpl.render() == "x y"
# set template globals for base, no longer uses env globals
tmpl = env.get_template("base", globals={"x": 42})
assert tmpl.render() == "42 y"
# templates are cached, they keep template globals set earlier
tmpl = env.get_template("main")
assert tmpl.render() == "bar y"
tmpl = env.get_template("base")
assert tmpl.render() == "42 y"
def test_nested_loop_scoping(self, env):
tmpl = env.from_string(
"{% set output %}{% for x in [1,2,3] %}hello{% endfor %}"
"{% endset %}{{ output }}"
)
assert tmpl.render() == "hellohellohello"
@pytest.mark.parametrize("unicode_char", ["\N{FORM FEED}", "\x85"])
def test_unicode_whitespace(env, unicode_char):
content = "Lorem ipsum\n" + unicode_char + "\nMore text"
tmpl = env.from_string(content)
assert tmpl.render() == content
-75
View File
@@ -1,75 +0,0 @@
import itertools
from jinja2 import Template
from jinja2.runtime import LoopContext
TEST_IDX_TEMPLATE_STR_1 = (
"[{% for i in lst|reverse %}(len={{ loop.length }},"
" revindex={{ loop.revindex }}, index={{ loop.index }}, val={{ i }}){% endfor %}]"
)
TEST_IDX0_TEMPLATE_STR_1 = (
"[{% for i in lst|reverse %}(len={{ loop.length }},"
" revindex0={{ loop.revindex0 }}, index0={{ loop.index0 }}, val={{ i }})"
"{% endfor %}]"
)
def test_loop_idx():
t = Template(TEST_IDX_TEMPLATE_STR_1)
lst = [10]
excepted_render = "[(len=1, revindex=1, index=1, val=10)]"
assert excepted_render == t.render(lst=lst)
def test_loop_idx0():
t = Template(TEST_IDX0_TEMPLATE_STR_1)
lst = [10]
excepted_render = "[(len=1, revindex0=0, index0=0, val=10)]"
assert excepted_render == t.render(lst=lst)
def test_loopcontext0():
in_lst = []
lc = LoopContext(reversed(in_lst), None)
assert lc.length == len(in_lst)
def test_loopcontext1():
in_lst = [10]
lc = LoopContext(reversed(in_lst), None)
assert lc.length == len(in_lst)
def test_loopcontext2():
in_lst = [10, 11]
lc = LoopContext(reversed(in_lst), None)
assert lc.length == len(in_lst)
def test_iterator_not_advanced_early():
t = Template("{% for _, g in gs %}{{ loop.index }} {{ g|list }}\n{% endfor %}")
out = t.render(
gs=itertools.groupby([(1, "a"), (1, "b"), (2, "c"), (3, "d")], lambda x: x[0])
)
# groupby groups depend on the current position of the iterator. If
# it was advanced early, the lists would appear empty.
assert out == "1 [(1, 'a'), (1, 'b')]\n2 [(2, 'c')]\n3 [(3, 'd')]\n"
def test_mock_not_pass_arg_marker():
"""If a callable class has a ``__getattr__`` that returns True-like
values for arbitrary attrs, it should not be incorrectly identified
as a ``pass_context`` function.
"""
class Calc:
def __getattr__(self, item):
return object()
def __call__(self, *args, **kwargs):
return len(args) + len(kwargs)
t = Template("{{ calc() }}")
out = t.render(calc=Calc())
# Would be "1" if context argument was passed.
assert out == "0"
-173
View File
@@ -1,173 +0,0 @@
import pytest
from markupsafe import escape
from jinja2 import Environment
from jinja2.exceptions import SecurityError
from jinja2.exceptions import TemplateRuntimeError
from jinja2.exceptions import TemplateSyntaxError
from jinja2.nodes import EvalContext
from jinja2.sandbox import ImmutableSandboxedEnvironment
from jinja2.sandbox import SandboxedEnvironment
from jinja2.sandbox import unsafe
class PrivateStuff:
def bar(self):
return 23
@unsafe
def foo(self):
return 42
def __repr__(self):
return "PrivateStuff"
class PublicStuff:
def bar(self):
return 23
def _foo(self):
return 42
def __repr__(self):
return "PublicStuff"
class TestSandbox:
def test_unsafe(self, env):
env = SandboxedEnvironment()
pytest.raises(
SecurityError, env.from_string("{{ foo.foo() }}").render, foo=PrivateStuff()
)
assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == "23"
pytest.raises(
SecurityError, env.from_string("{{ foo._foo() }}").render, foo=PublicStuff()
)
assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == "23"
assert env.from_string("{{ foo.__class__ }}").render(foo=42) == ""
assert env.from_string("{{ foo.func_code }}").render(foo=lambda: None) == ""
# security error comes from __class__ already.
pytest.raises(
SecurityError,
env.from_string("{{ foo.__class__.__subclasses__() }}").render,
foo=42,
)
def test_immutable_environment(self, env):
env = ImmutableSandboxedEnvironment()
pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render)
pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render)
def test_restricted(self, env):
env = SandboxedEnvironment()
pytest.raises(
TemplateSyntaxError,
env.from_string,
"{% for item.attribute in seq %}...{% endfor %}",
)
pytest.raises(
TemplateSyntaxError,
env.from_string,
"{% for foo, bar.baz in seq %}...{% endfor %}",
)
def test_template_data(self, env):
env = Environment(autoescape=True)
t = env.from_string(
"{% macro say_hello(name) %}"
"<p>Hello {{ name }}!</p>{% endmacro %}"
'{{ say_hello("<blink>foo</blink>") }}'
)
escaped_out = "<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>"
assert t.render() == escaped_out
assert str(t.module) == escaped_out
assert escape(t.module) == escaped_out
assert t.module.say_hello("<blink>foo</blink>") == escaped_out
assert (
escape(t.module.say_hello(EvalContext(env), "<blink>foo</blink>"))
== escaped_out
)
assert escape(t.module.say_hello("<blink>foo</blink>")) == escaped_out
def test_attr_filter(self, env):
env = SandboxedEnvironment()
tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
pytest.raises(SecurityError, tmpl.render, cls=int)
def test_binary_operator_intercepting(self, env):
def disable_op(left, right):
raise TemplateRuntimeError("that operator so does not work")
for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"):
env = SandboxedEnvironment()
env.binop_table["+"] = disable_op
t = env.from_string(f"{{{{ {expr} }}}}")
assert t.render(ctx) == rv
env.intercepted_binops = frozenset(["+"])
t = env.from_string(f"{{{{ {expr} }}}}")
with pytest.raises(TemplateRuntimeError):
t.render(ctx)
def test_unary_operator_intercepting(self, env):
def disable_op(arg):
raise TemplateRuntimeError("that operator so does not work")
for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"):
env = SandboxedEnvironment()
env.unop_table["-"] = disable_op
t = env.from_string(f"{{{{ {expr} }}}}")
assert t.render(ctx) == rv
env.intercepted_unops = frozenset(["-"])
t = env.from_string(f"{{{{ {expr} }}}}")
with pytest.raises(TemplateRuntimeError):
t.render(ctx)
class TestStringFormat:
def test_basic_format_safety(self):
env = SandboxedEnvironment()
t = env.from_string('{{ "a{0.__class__}b".format(42) }}')
assert t.render() == "ab"
def test_basic_format_all_okay(self):
env = SandboxedEnvironment()
t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}')
assert t.render() == "a42b"
def test_safe_format_safety(self):
env = SandboxedEnvironment()
t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}')
assert t.render() == "ab&lt;foo&gt;"
def test_safe_format_all_okay(self):
env = SandboxedEnvironment()
t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}')
assert t.render() == "a42b&lt;foo&gt;"
def test_empty_braces_format(self):
env = SandboxedEnvironment()
t1 = env.from_string('{{ ("a{}b{}").format("foo", "42")}}')
t2 = env.from_string('{{ ("a{}b{}"|safe).format(42, "<foo>") }}')
assert t1.render() == "afoob42"
assert t2.render() == "a42b&lt;foo&gt;"
class TestStringFormatMap:
def test_basic_format_safety(self):
env = SandboxedEnvironment()
t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}')
assert t.render() == "ab"
def test_basic_format_all_okay(self):
env = SandboxedEnvironment()
t = env.from_string('{{ "a{x.foo}b".format_map({"x":{"foo": 42}}) }}')
assert t.render() == "a42b"
def test_safe_format_all_okay(self):
env = SandboxedEnvironment()
t = env.from_string(
'{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}'
)
assert t.render() == "a42b&lt;foo&gt;"
-233
View File
@@ -1,233 +0,0 @@
import pytest
from markupsafe import Markup
from jinja2 import Environment
from jinja2 import TemplateAssertionError
from jinja2 import TemplateRuntimeError
class MyDict(dict):
pass
class TestTestsCase:
def test_defined(self, env):
tmpl = env.from_string("{{ missing is defined }}|{{ true is defined }}")
assert tmpl.render() == "False|True"
def test_even(self, env):
tmpl = env.from_string("""{{ 1 is even }}|{{ 2 is even }}""")
assert tmpl.render() == "False|True"
def test_odd(self, env):
tmpl = env.from_string("""{{ 1 is odd }}|{{ 2 is odd }}""")
assert tmpl.render() == "True|False"
def test_lower(self, env):
tmpl = env.from_string("""{{ "foo" is lower }}|{{ "FOO" is lower }}""")
assert tmpl.render() == "True|False"
# Test type checks
@pytest.mark.parametrize(
"op,expect",
(
("none is none", True),
("false is none", False),
("true is none", False),
("42 is none", False),
("none is true", False),
("false is true", False),
("true is true", True),
("0 is true", False),
("1 is true", False),
("42 is true", False),
("none is false", False),
("false is false", True),
("true is false", False),
("0 is false", False),
("1 is false", False),
("42 is false", False),
("none is boolean", False),
("false is boolean", True),
("true is boolean", True),
("0 is boolean", False),
("1 is boolean", False),
("42 is boolean", False),
("0.0 is boolean", False),
("1.0 is boolean", False),
("3.14159 is boolean", False),
("none is integer", False),
("false is integer", False),
("true is integer", False),
("42 is integer", True),
("3.14159 is integer", False),
("(10 ** 100) is integer", True),
("none is float", False),
("false is float", False),
("true is float", False),
("42 is float", False),
("4.2 is float", True),
("(10 ** 100) is float", False),
("none is number", False),
("false is number", True),
("true is number", True),
("42 is number", True),
("3.14159 is number", True),
("complex is number", True),
("(10 ** 100) is number", True),
("none is string", False),
("false is string", False),
("true is string", False),
("42 is string", False),
('"foo" is string', True),
("none is sequence", False),
("false is sequence", False),
("42 is sequence", False),
('"foo" is sequence', True),
("[] is sequence", True),
("[1, 2, 3] is sequence", True),
("{} is sequence", True),
("none is mapping", False),
("false is mapping", False),
("42 is mapping", False),
('"foo" is mapping', False),
("[] is mapping", False),
("{} is mapping", True),
("mydict is mapping", True),
("none is iterable", False),
("false is iterable", False),
("42 is iterable", False),
('"foo" is iterable', True),
("[] is iterable", True),
("{} is iterable", True),
("range(5) is iterable", True),
("none is callable", False),
("false is callable", False),
("42 is callable", False),
('"foo" is callable', False),
("[] is callable", False),
("{} is callable", False),
("range is callable", True),
),
)
def test_types(self, env, op, expect):
t = env.from_string(f"{{{{ {op} }}}}")
assert t.render(mydict=MyDict(), complex=complex(1, 2)) == str(expect)
def test_upper(self, env):
tmpl = env.from_string('{{ "FOO" is upper }}|{{ "foo" is upper }}')
assert tmpl.render() == "True|False"
def test_equalto(self, env):
tmpl = env.from_string(
"{{ foo is eq 12 }}|"
"{{ foo is eq 0 }}|"
"{{ foo is eq (3 * 4) }}|"
'{{ bar is eq "baz" }}|'
'{{ bar is eq "zab" }}|'
'{{ bar is eq ("ba" + "z") }}|'
"{{ bar is eq bar }}|"
"{{ bar is eq foo }}"
)
assert (
tmpl.render(foo=12, bar="baz")
== "True|False|True|True|False|True|True|False"
)
@pytest.mark.parametrize(
"op,expect",
(
("eq 2", True),
("eq 3", False),
("ne 3", True),
("ne 2", False),
("lt 3", True),
("lt 2", False),
("le 2", True),
("le 1", False),
("gt 1", True),
("gt 2", False),
("ge 2", True),
("ge 3", False),
),
)
def test_compare_aliases(self, env, op, expect):
t = env.from_string(f"{{{{ 2 is {op} }}}}")
assert t.render() == str(expect)
def test_sameas(self, env):
tmpl = env.from_string("{{ foo is sameas false }}|{{ 0 is sameas false }}")
assert tmpl.render(foo=False) == "True|False"
def test_no_paren_for_arg1(self, env):
tmpl = env.from_string("{{ foo is sameas none }}")
assert tmpl.render(foo=None) == "True"
def test_escaped(self, env):
env = Environment(autoescape=True)
tmpl = env.from_string("{{ x is escaped }}|{{ y is escaped }}")
assert tmpl.render(x="foo", y=Markup("foo")) == "False|True"
def test_greaterthan(self, env):
tmpl = env.from_string("{{ 1 is greaterthan 0 }}|{{ 0 is greaterthan 1 }}")
assert tmpl.render() == "True|False"
def test_lessthan(self, env):
tmpl = env.from_string("{{ 0 is lessthan 1 }}|{{ 1 is lessthan 0 }}")
assert tmpl.render() == "True|False"
def test_multiple_tests(self):
items = []
def matching(x, y):
items.append((x, y))
return False
env = Environment()
env.tests["matching"] = matching
tmpl = env.from_string(
"{{ 'us-west-1' is matching '(us-east-1|ap-northeast-1)'"
" or 'stage' is matching '(dev|stage)' }}"
)
assert tmpl.render() == "False"
assert items == [
("us-west-1", "(us-east-1|ap-northeast-1)"),
("stage", "(dev|stage)"),
]
def test_in(self, env):
tmpl = env.from_string(
'{{ "o" is in "foo" }}|'
'{{ "foo" is in "foo" }}|'
'{{ "b" is in "foo" }}|'
"{{ 1 is in ((1, 2)) }}|"
"{{ 3 is in ((1, 2)) }}|"
"{{ 1 is in [1, 2] }}|"
"{{ 3 is in [1, 2] }}|"
'{{ "foo" is in {"foo": 1}}}|'
'{{ "baz" is in {"bar": 1}}}'
)
assert tmpl.render() == "True|True|False|True|False|True|False|True|False"
def test_name_undefined(env):
with pytest.raises(TemplateAssertionError, match="No test named 'f'"):
env.from_string("{{ x is f }}")
def test_name_undefined_in_if(env):
t = env.from_string("{% if x is defined %}{{ x is f }}{% endif %}")
assert t.render() == ""
with pytest.raises(TemplateRuntimeError, match="No test named 'f'"):
t.render(x=1)
def test_is_filter(env):
assert env.call_test("filter", "title")
assert not env.call_test("filter", "bad-name")
def test_is_test(env):
assert env.call_test("test", "number")
assert not env.call_test("test", "bad-name")
-185
View File
@@ -1,185 +0,0 @@
import pickle
import random
from collections import deque
from copy import copy as shallow_copy
import pytest
from markupsafe import Markup
from jinja2.utils import consume
from jinja2.utils import generate_lorem_ipsum
from jinja2.utils import LRUCache
from jinja2.utils import missing
from jinja2.utils import object_type_repr
from jinja2.utils import select_autoescape
from jinja2.utils import urlize
class TestLRUCache:
def test_simple(self):
d = LRUCache(3)
d["a"] = 1
d["b"] = 2
d["c"] = 3
d["a"]
d["d"] = 4
assert d.keys() == ["d", "a", "c"]
def test_values(self):
cache = LRUCache(3)
cache["b"] = 1
cache["a"] = 2
assert cache.values() == [2, 1]
def test_values_empty(self):
cache = LRUCache(2)
assert cache.values() == []
def test_pickleable(self):
cache = LRUCache(2)
cache["foo"] = 42
cache["bar"] = 23
cache["foo"]
for protocol in range(3):
copy = pickle.loads(pickle.dumps(cache, protocol))
assert copy.capacity == cache.capacity
assert copy._mapping == cache._mapping
assert copy._queue == cache._queue
@pytest.mark.parametrize("copy_func", [LRUCache.copy, shallow_copy])
def test_copy(self, copy_func):
cache = LRUCache(2)
cache["a"] = 1
cache["b"] = 2
copy = copy_func(cache)
assert copy._queue == cache._queue
copy["c"] = 3
assert copy._queue != cache._queue
assert copy.keys() == ["c", "b"]
def test_clear(self):
d = LRUCache(3)
d["a"] = 1
d["b"] = 2
d["c"] = 3
d.clear()
assert d.__getstate__() == {"capacity": 3, "_mapping": {}, "_queue": deque([])}
def test_repr(self):
d = LRUCache(3)
d["a"] = 1
d["b"] = 2
d["c"] = 3
# Sort the strings - mapping is unordered
assert sorted(repr(d)) == sorted("<LRUCache {'a': 1, 'b': 2, 'c': 3}>")
def test_items(self):
"""Test various items, keys, values and iterators of LRUCache."""
d = LRUCache(3)
d["a"] = 1
d["b"] = 2
d["c"] = 3
assert d.items() == [("c", 3), ("b", 2), ("a", 1)]
assert d.keys() == ["c", "b", "a"]
assert d.values() == [3, 2, 1]
assert list(reversed(d)) == ["a", "b", "c"]
# Change the cache a little
d["b"]
d["a"] = 4
assert d.items() == [("a", 4), ("b", 2), ("c", 3)]
assert d.keys() == ["a", "b", "c"]
assert d.values() == [4, 2, 3]
assert list(reversed(d)) == ["c", "b", "a"]
def test_setdefault(self):
d = LRUCache(3)
assert len(d) == 0
assert d.setdefault("a") is None
assert d.setdefault("a", 1) is None
assert len(d) == 1
assert d.setdefault("b", 2) == 2
assert len(d) == 2
class TestHelpers:
def test_object_type_repr(self):
class X:
pass
assert object_type_repr(42) == "int object"
assert object_type_repr([]) == "list object"
assert object_type_repr(X()) == "test_utils.X object"
assert object_type_repr(None) == "None"
assert object_type_repr(Ellipsis) == "Ellipsis"
def test_autoescape_select(self):
func = select_autoescape(
enabled_extensions=("html", ".htm"),
disabled_extensions=("txt",),
default_for_string="STRING",
default="NONE",
)
assert func(None) == "STRING"
assert func("unknown.foo") == "NONE"
assert func("foo.html")
assert func("foo.htm")
assert not func("foo.txt")
assert func("FOO.HTML")
assert not func("FOO.TXT")
class TestEscapeUrlizeTarget:
def test_escape_urlize_target(self):
url = "http://example.org"
target = "<script>"
assert urlize(url, target=target) == (
'<a href="http://example.org"'
' target="&lt;script&gt;">'
"http://example.org</a>"
)
class TestLoremIpsum:
def test_lorem_ipsum_markup(self):
"""Test that output of lorem_ipsum is Markup by default."""
assert isinstance(generate_lorem_ipsum(), Markup)
def test_lorem_ipsum_html(self):
"""Test that output of lorem_ipsum is a string_type when not html."""
assert isinstance(generate_lorem_ipsum(html=False), str)
def test_lorem_ipsum_n(self):
"""Test that the n (number of lines) works as expected."""
assert generate_lorem_ipsum(n=0, html=False) == ""
for n in range(1, 50):
assert generate_lorem_ipsum(n=n, html=False).count("\n") == (n - 1) * 2
def test_lorem_ipsum_min(self):
"""Test that at least min words are in the output of each line"""
for _ in range(5):
m = random.randrange(20, 99)
for _ in range(10):
assert generate_lorem_ipsum(n=1, min=m, html=False).count(" ") >= m - 1
def test_lorem_ipsum_max(self):
"""Test that at least max words are in the output of each line"""
for _ in range(5):
m = random.randrange(21, 100)
for _ in range(10):
assert generate_lorem_ipsum(n=1, max=m, html=False).count(" ") < m - 1
def test_missing():
"""Test the repr of missing."""
assert repr(missing) == "missing"
def test_consume():
"""Test that consume consumes an iterator."""
x = iter([1, 2, 3, 4, 5])
consume(x)
with pytest.raises(StopIteration):
next(x)
View File