Repository: pallets/jinja
Branch: main
Commit: 5ef70112a1ff
Files: 102
Total size: 940.1 KB
Directory structure:
gitextract_8ck6zqut/
├── .devcontainer/
│ ├── devcontainer.json
│ └── on-create-command.sh
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.md
│ │ ├── config.yml
│ │ └── feature-request.md
│ ├── pull_request_template.md
│ └── workflows/
│ ├── lock.yaml
│ ├── pre-commit.yaml
│ ├── publish.yaml
│ └── tests.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CHANGES.rst
├── LICENSE.txt
├── README.md
├── docs/
│ ├── Makefile
│ ├── api.rst
│ ├── changes.rst
│ ├── conf.py
│ ├── examples/
│ │ ├── cache_extension.py
│ │ └── inline_gettext_extension.py
│ ├── extensions.rst
│ ├── faq.rst
│ ├── index.rst
│ ├── integration.rst
│ ├── intro.rst
│ ├── license.rst
│ ├── make.bat
│ ├── nativetypes.rst
│ ├── sandbox.rst
│ ├── switching.rst
│ ├── templates.rst
│ └── tricks.rst
├── examples/
│ └── basic/
│ ├── cycle.py
│ ├── debugger.py
│ ├── inheritance.py
│ ├── templates/
│ │ ├── broken.html
│ │ └── subbroken.html
│ ├── test.py
│ ├── test_filter_and_linestatements.py
│ ├── test_loop_filter.py
│ └── translate.py
├── pyproject.toml
├── scripts/
│ └── generate_identifier_pattern.py
├── src/
│ └── jinja2/
│ ├── __init__.py
│ ├── _identifier.py
│ ├── async_utils.py
│ ├── bccache.py
│ ├── compiler.py
│ ├── constants.py
│ ├── debug.py
│ ├── defaults.py
│ ├── environment.py
│ ├── exceptions.py
│ ├── ext.py
│ ├── filters.py
│ ├── idtracking.py
│ ├── lexer.py
│ ├── loaders.py
│ ├── meta.py
│ ├── nativetypes.py
│ ├── nodes.py
│ ├── optimizer.py
│ ├── parser.py
│ ├── py.typed
│ ├── runtime.py
│ ├── sandbox.py
│ ├── tests.py
│ ├── utils.py
│ └── visitor.py
└── tests/
├── conftest.py
├── res/
│ ├── __init__.py
│ ├── templates/
│ │ ├── broken.html
│ │ ├── foo/
│ │ │ └── test.html
│ │ ├── mojibake.txt
│ │ ├── syntaxerror.html
│ │ └── test.html
│ └── templates2/
│ └── foo
├── test_api.py
├── test_async.py
├── test_async_filters.py
├── test_bytecode_cache.py
├── test_compile.py
├── test_core_tags.py
├── test_debug.py
├── test_ext.py
├── test_filters.py
├── test_idtracking.py
├── test_imports.py
├── test_inheritance.py
├── test_lexnparse.py
├── test_loader.py
├── test_nativetypes.py
├── test_nodes.py
├── test_pickle.py
├── test_regression.py
├── test_runtime.py
├── test_security.py
├── test_tests.py
└── test_utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/devcontainer.json
================================================
{
"name": "pallets/jinja",
"image": "mcr.microsoft.com/devcontainers/python:3",
"customizations": {
"vscode": {
"settings": {
"python.defaultInterpreterPath": "${workspaceFolder}/.venv",
"python.terminal.activateEnvInCurrentTerminal": true,
"python.terminal.launchArgs": [
"-X",
"dev"
]
}
}
},
"onCreateCommand": ".devcontainer/on-create-command.sh"
}
================================================
FILE: .devcontainer/on-create-command.sh
================================================
#!/bin/bash
set -e
# Install uv if not already installed
if ! command -v uv &> /dev/null; then
echo "Installing uv..."
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.cargo/bin:$PATH"
fi
# Create venv using uv and install dependencies
echo "Creating virtual environment and installing dependencies..."
uv sync
# Install pre-commit hooks
echo "Installing pre-commit hooks..."
pre-commit install --install-hooks
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8
max_line_length = 88
[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}]
indent_size = 2
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug report
about: Report a bug in Jinja (not other projects which depend on Jinja)
---
Environment:
- Python version:
- Jinja version:
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Questions on Discussions
url: https://github.com/pallets/jinja/discussions/
about: Ask questions about your own code on the Discussions tab.
- name: Questions on Chat
url: https://discord.gg/pallets
about: Ask questions about your own code on our Discord chat.
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature request
about: Suggest a new feature for Jinja
---
================================================
FILE: .github/pull_request_template.md
================================================
================================================
FILE: .github/workflows/lock.yaml
================================================
name: Lock inactive closed issues
# Lock closed issues that have not received any further activity for two weeks.
# This does not close open issues, only humans may do that. It is easier to
# respond to new issues with fresh examples rather than continuing discussions
# on old issues.
on:
schedule:
- cron: '0 0 * * *'
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: lock
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
with:
issue-inactive-days: 14
pr-inactive-days: 14
discussion-inactive-days: 14
================================================
FILE: .github/workflows/pre-commit.yaml
================================================
name: pre-commit
on:
pull_request:
push:
branches: [main, stable]
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
with:
enable-cache: true
prune-cache: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
id: setup-python
with:
python-version-file: pyproject.toml
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }}
- run: uv run --locked --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
if: ${{ !cancelled() }}
================================================
FILE: .github/workflows/publish.yaml
================================================
name: Publish
on:
push:
tags: ['*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
with:
enable-cache: true
prune-cache: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version-file: pyproject.toml
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- run: uv build
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
path: ./dist
create-release:
needs: [build]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- name: create release
run: >
gh release create --draft --repo ${{ github.repository }}
${{ github.ref_name }} artifact/*
env:
GH_TOKEN: ${{ github.token }}
publish-pypi:
needs: [build]
environment:
name: publish
url: https://pypi.org/project/Jinja2/${{ github.ref_name }}
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
packages-dir: artifact/
================================================
FILE: .github/workflows/tests.yaml
================================================
name: Tests
on:
pull_request:
paths-ignore: ['docs/**', 'README.md']
push:
branches: [main, stable]
paths-ignore: ['docs/**', 'README.md']
jobs:
tests:
name: ${{ matrix.name || matrix.python }}
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
include:
- {python: '3.13'}
- {name: Windows, python: '3.13', os: windows-latest}
- {name: Mac, python: '3.13', os: macos-latest}
- {python: '3.12'}
- {python: '3.11'}
- {python: '3.10'}
- {name: PyPy, python: 'pypy-3.11', tox: pypy3.11}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
with:
enable-cache: true
prune-cache: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python }}
- run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
typing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
with:
enable-cache: true
prune-cache: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version-file: pyproject.toml
- name: cache mypy
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ./.mypy_cache
key: mypy|${{ hashFiles('pyproject.toml') }}
- run: uv run --locked tox run -e typing
================================================
FILE: .gitignore
================================================
.idea/
.vscode/
__pycache__/
dist/
.coverage*
htmlcov/
.tox/
docs/_build/
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 76e47323a83cd9795e4ff9a1de1c0d2eef610f17 # frozen: v0.11.11
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 648bdbfd6bb1a82f132ecc2c666e0d1b2e4b0d94 # frozen: 0.7.8
hooks:
- id: uv-lock
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0
hooks:
- id: check-merge-conflict
- id: debug-statements
- id: fix-byte-order-marker
- id: trailing-whitespace
- id: end-of-file-fixer
================================================
FILE: .readthedocs.yaml
================================================
version: 2
build:
os: ubuntu-24.04
tools:
python: '3.13'
commands:
- asdf plugin add uv
- asdf install uv latest
- asdf global uv latest
- uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html
================================================
FILE: CHANGES.rst
================================================
.. currentmodule:: jinja2
Version 3.2.0
-------------
Unreleased
- Drop support for Python 3.7, 3.8, and 3.9.
- Update minimum MarkupSafe version to >= 3.0.
- Update minimum Babel version to >= 2.17.
- Deprecate the ``__version__`` attribute. Use feature detection or
``importlib.metadata.version("jinja2")`` instead.
- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``.
:pr:`1793`
- Use ``flit_core`` instead of ``setuptools`` as build backend.
Version 3.1.6
-------------
Released 2025-03-05
- The ``|attr`` filter does not bypass the environment's attribute lookup,
allowing the sandbox to apply its checks. :ghsa:`cpwx-vrp4-4pq7`
Version 3.1.5
-------------
Released 2024-12-21
- The sandboxed environment handles indirect calls to ``str.format``, such as
by passing a stored reference to a filter that calls its argument.
:ghsa:`q2x7-8rv6-6q7h`
- Escape template name before formatting it into error messages, to avoid
issues with names that contain f-string syntax.
:issue:`1792`, :ghsa:`gmj6-6f8f-6699`
- Sandbox does not allow ``clear`` and ``pop`` on known mutable sequence
types. :issue:`2032`
- Calling sync ``render`` for an async template uses ``asyncio.run``.
:pr:`1952`
- Avoid unclosed ``auto_aiter`` warnings. :pr:`1960`
- Return an ``aclose``-able ``AsyncGenerator`` from
``Template.generate_async``. :pr:`1960`
- Avoid leaving ``root_render_func()`` unclosed in
``Template.generate_async``. :pr:`1960`
- Avoid leaving async generators unclosed in blocks, includes and extends.
:pr:`1960`
- The runtime uses the correct ``concat`` function for the current environment
when calling block references. :issue:`1701`
- Make ``|unique`` async-aware, allowing it to be used after another
async-aware filter. :issue:`1781`
- ``|int`` filter handles ``OverflowError`` from scientific notation.
:issue:`1921`
- Make compiling deterministic for tuple unpacking in a ``{% set ... %}``
call. :issue:`2021`
- Fix dunder protocol (`copy`/`pickle`/etc) interaction with ``Undefined``
objects. :issue:`2025`
- Fix `copy`/`pickle` support for the internal ``missing`` object.
:issue:`2027`
- ``Environment.overlay(enable_async)`` is applied correctly. :pr:`2061`
- The error message from ``FileSystemLoader`` includes the paths that were
searched. :issue:`1661`
- ``PackageLoader`` shows a clearer error message when the package does not
contain the templates directory. :issue:`1705`
- Improve annotations for methods returning copies. :pr:`1880`
- ``urlize`` does not add ``mailto:`` to values like `@a@b`. :pr:`1870`
- Tests decorated with `@pass_context`` can be used with the ``|select``
filter. :issue:`1624`
- Using ``set`` for multiple assignment (``a, b = 1, 2``) does not fail when the
target is a namespace attribute. :issue:`1413`
- Using ``set`` in all branches of ``{% if %}{% elif %}{% else %}`` blocks
does not cause the variable to be considered initially undefined.
:issue:`1253`
Version 3.1.4
-------------
Released 2024-05-05
- The ``xmlattr`` filter does not allow keys with ``/`` solidus, ``>``
greater-than sign, or ``=`` equals sign, in addition to disallowing spaces.
Regardless of any validation done by Jinja, user input should never be used
as keys to this filter, or must be separately validated first.
:ghsa:`h75v-3vvj-5mfj`
Version 3.1.3
-------------
Released 2024-01-10
- Fix compiler error when checking if required blocks in parent templates are
empty. :pr:`1858`
- ``xmlattr`` filter does not allow keys with spaces. :ghsa:`h5c8-rqwp-cp95`
- Make error messages stemming from invalid nesting of ``{% trans %}`` blocks
more helpful. :pr:`1918`
Version 3.1.2
-------------
Released 2022-04-28
- Add parameters to ``Environment.overlay`` to match ``__init__``.
:issue:`1645`
- Handle race condition in ``FileSystemBytecodeCache``. :issue:`1654`
Version 3.1.1
-------------
Released 2022-03-25
- The template filename on Windows uses the primary path separator.
:issue:`1637`
Version 3.1.0
-------------
Released 2022-03-24
- Drop support for Python 3.6. :pr:`1534`
- Remove previously deprecated code. :pr:`1544`
- ``WithExtension`` and ``AutoEscapeExtension`` are built-in now.
- ``contextfilter`` and ``contextfunction`` are replaced by
``pass_context``. ``evalcontextfilter`` and
``evalcontextfunction`` are replaced by ``pass_eval_context``.
``environmentfilter`` and ``environmentfunction`` are replaced
by ``pass_environment``.
- ``Markup`` and ``escape`` should be imported from MarkupSafe.
- Compiled templates from very old Jinja versions may need to be
recompiled.
- Legacy resolve mode for ``Context`` subclasses is no longer
supported. Override ``resolve_or_missing`` instead of
``resolve``.
- ``unicode_urlencode`` is renamed to ``url_quote``.
- Add support for native types in macros. :issue:`1510`
- The ``{% trans %}`` tag can use ``pgettext`` and ``npgettext`` by
passing a context string as the first token in the tag, like
``{% trans "title" %}``. :issue:`1430`
- Update valid identifier characters from Python 3.6 to 3.7.
:pr:`1571`
- Filters and tests decorated with ``@async_variant`` are pickleable.
:pr:`1612`
- Add ``items`` filter. :issue:`1561`
- Subscriptions (``[0]``, etc.) can be used after filters, tests, and
calls when the environment is in async mode. :issue:`1573`
- The ``groupby`` filter is case-insensitive by default, matching
other comparison filters. Added the ``case_sensitive`` parameter to
control this. :issue:`1463`
- Windows drive-relative path segments in template names will not
result in ``FileSystemLoader`` and ``PackageLoader`` loading from
drive-relative paths. :pr:`1621`
Version 3.0.3
-------------
Released 2021-11-09
- Fix traceback rewriting internals for Python 3.10 and 3.11.
:issue:`1535`
- Fix how the native environment treats leading and trailing spaces
when parsing values on Python 3.10. :pr:`1537`
- Improve async performance by avoiding checks for common types.
:issue:`1514`
- Revert change to ``hash(Node)`` behavior. Nodes are hashed by id
again :issue:`1521`
- ``PackageLoader`` works when the package is a single module file.
:issue:`1512`
Version 3.0.2
-------------
Released 2021-10-04
- Fix a loop scoping bug that caused assignments in nested loops
to still be referenced outside of it. :issue:`1427`
- Make ``compile_templates`` deterministic for filter and import
names. :issue:`1452, 1453`
- Revert an unintended change that caused ``Undefined`` to act like
``StrictUndefined`` for the ``in`` operator. :issue:`1448`
- Imported macros have access to the current template globals in async
environments. :issue:`1494`
- ``PackageLoader`` will not include a current directory (.) path
segment. This allows loading templates from the root of a zip
import. :issue:`1467`
Version 3.0.1
-------------
Released 2021-05-18
- Update MarkupSafe dependency to >= 2.0. :pr:`1418`
- Mark top-level names as exported so type checking understands
imports in user projects. :issue:`1426`
- Fix some types that weren't available in Python 3.6.0. :issue:`1433`
- The deprecation warning for unneeded ``autoescape`` and ``with_``
extensions shows more relevant context. :issue:`1429`
- Fixed calling deprecated ``jinja2.Markup`` without an argument.
Use ``markupsafe.Markup`` instead. :issue:`1438`
- Calling sync ``render`` for an async template uses ``asyncio.new_event_loop``
This fixes a deprecation that Python 3.10 introduces. :issue:`1443`
Version 3.0.0
-------------
Released 2021-05-11
- Drop support for Python 2.7 and 3.5.
- Bump MarkupSafe dependency to >=1.1.
- Bump Babel optional dependency to >=2.1.
- Remove code that was marked deprecated.
- Add type hinting. :pr:`1412`
- Use :pep:`451` API to load templates with
:class:`~loaders.PackageLoader`. :issue:`1168`
- Fix a bug that caused imported macros to not have access to the
current template's globals. :issue:`688`
- Add ability to ignore ``trim_blocks`` using ``+%}``. :issue:`1036`
- Fix a bug that caused custom async-only filters to fail with
constant input. :issue:`1279`
- Fix UndefinedError incorrectly being thrown on an undefined variable
instead of ``Undefined`` being returned on
``NativeEnvironment`` on Python 3.10. :issue:`1335`
- Blocks can be marked as ``required``. They must be overridden at
some point, but not necessarily by the direct child. :issue:`1147`
- Deprecate the ``autoescape`` and ``with`` extensions, they are
built-in to the compiler. :issue:`1203`
- The ``urlize`` filter recognizes ``mailto:`` links and takes
``extra_schemes`` (or ``env.policies["urlize.extra_schemes"]``) to
recognize other schemes. It tries to balance parentheses within a
URL instead of ignoring trailing characters. The parsing in general
has been updated to be more efficient and match more cases. URLs
without a scheme are linked as ``https://`` instead of ``http://``.
:issue:`522, 827, 1172`, :pr:`1195`
- Filters that get attributes, such as ``map`` and ``groupby``, can
use a false or empty value as a default. :issue:`1331`
- Fix a bug that prevented variables set in blocks or loops from
being accessed in custom context functions. :issue:`768`
- Fix a bug that caused scoped blocks from accessing special loop
variables. :issue:`1088`
- Update the template globals when calling
``Environment.get_template(globals=...)`` even if the template was
already loaded. :issue:`295`
- Do not raise an error for undefined filters in unexecuted
if-statements and conditional expressions. :issue:`842`
- Add ``is filter`` and ``is test`` tests to test if a name is a
registered filter or test. This allows checking if a filter is
available in a template before using it. Test functions can be
decorated with ``@pass_environment``, ``@pass_eval_context``,
or ``@pass_context``. :issue:`842`, :pr:`1248`
- Support ``pgettext`` and ``npgettext`` (message contexts) in i18n
extension. :issue:`441`
- The ``|indent`` filter's ``width`` argument can be a string to
indent by. :pr:`1167`
- The parser understands hex, octal, and binary integer literals.
:issue:`1170`
- ``Undefined.__contains__`` (``in``) raises an ``UndefinedError``
instead of a ``TypeError``. :issue:`1198`
- ``Undefined`` is iterable in an async environment. :issue:`1294`
- ``NativeEnvironment`` supports async mode. :issue:`1362`
- Template rendering only treats ``\n``, ``\r\n`` and ``\r`` as line
breaks. Other characters are left unchanged. :issue:`769, 952, 1313`
- ``|groupby`` filter takes an optional ``default`` argument.
:issue:`1359`
- The function and filter decorators have been renamed and unified.
The old names are deprecated. :issue:`1381`
- ``pass_context`` replaces ``contextfunction`` and
``contextfilter``.
- ``pass_eval_context`` replaces ``evalcontextfunction`` and
``evalcontextfilter``
- ``pass_environment`` replaces ``environmentfunction`` and
``environmentfilter``.
- Async support no longer requires Jinja to patch itself. It must
still be enabled with ``Environment(enable_async=True)``.
:issue:`1390`
- Overriding ``Context.resolve`` is deprecated, override
``resolve_or_missing`` instead. :issue:`1380`
Version 2.11.3
--------------
Released 2021-01-31
- Improve the speed of the ``urlize`` filter by reducing regex
backtracking. Email matching requires a word character at the start
of the domain part, and only word characters in the TLD. :pr:`1343`
Version 2.11.2
--------------
Released 2020-04-13
- Fix a bug that caused callable objects with ``__getattr__``, like
:class:`~unittest.mock.Mock` to be treated as a
:func:`contextfunction`. :issue:`1145`
- Update ``wordcount`` filter to trigger :class:`Undefined` methods
by wrapping the input in :func:`soft_str`. :pr:`1160`
- Fix a hang when displaying tracebacks on Python 32-bit.
:issue:`1162`
- Showing an undefined error for an object that raises
``AttributeError`` on access doesn't cause a recursion error.
:issue:`1177`
- Revert changes to :class:`~loaders.PackageLoader` from 2.10 which
removed the dependency on setuptools and pkg_resources, and added
limited support for namespace packages. The changes caused issues
when using Pytest. Due to the difficulty in supporting Python 2 and
:pep:`451` simultaneously, the changes are reverted until 3.0.
:pr:`1182`
- Fix line numbers in error messages when newlines are stripped.
:pr:`1178`
- The special ``namespace()`` assignment object in templates works in
async environments. :issue:`1180`
- Fix whitespace being removed before tags in the middle of lines when
``lstrip_blocks`` is enabled. :issue:`1138`
- :class:`~nativetypes.NativeEnvironment` doesn't evaluate
intermediate strings during rendering. This prevents early
evaluation which could change the value of an expression.
:issue:`1186`
Version 2.11.1
--------------
Released 2020-01-30
- Fix a bug that prevented looking up a key after an attribute
(``{{ data.items[1:] }}``) in an async template. :issue:`1141`
Version 2.11.0
--------------
Released 2020-01-27
- Drop support for Python 2.6, 3.3, and 3.4. This will be the last
version to support Python 2.7 and 3.5.
- Added a new ``ChainableUndefined`` class to support getitem and
getattr on an undefined object. :issue:`977`
- Allow ``{%+`` syntax (with NOP behavior) when ``lstrip_blocks`` is
disabled. :issue:`748`
- Added a ``default`` parameter for the ``map`` filter. :issue:`557`
- Exclude environment globals from
:func:`meta.find_undeclared_variables`. :issue:`931`
- Float literals can be written with scientific notation, like
2.56e-3. :issue:`912`, :pr:`922`
- Int and float literals can be written with the '_' separator for
legibility, like 12_345. :pr:`923`
- Fix a bug causing deadlocks in ``LRUCache.setdefault``. :pr:`1000`
- The ``trim`` filter takes an optional string of characters to trim.
:pr:`828`
- A new ``jinja2.ext.debug`` extension adds a ``{% debug %}`` tag to
quickly dump the current context and available filters and tests.
:issue:`174`, :pr:`798, 983`
- Lexing templates with large amounts of whitespace is much faster.
:issue:`857`, :pr:`858`
- Parentheses around comparisons are preserved, so
``{{ 2 * (3 < 5) }}`` outputs "2" instead of "False".
:issue:`755`, :pr:`938`
- Add new ``boolean``, ``false``, ``true``, ``integer`` and ``float``
tests. :pr:`824`
- The environment's ``finalize`` function is only applied to the
output of expressions (constant or not), not static template data.
:issue:`63`
- When providing multiple paths to ``FileSystemLoader``, a template
can have the same name as a directory. :issue:`821`
- Always return :class:`Undefined` when omitting the ``else`` clause
in a ``{{ 'foo' if bar }}`` expression, regardless of the
environment's ``undefined`` class. Omitting the ``else`` clause is a
valid shortcut and should not raise an error when using
:class:`StrictUndefined`. :issue:`710`, :pr:`1079`
- Fix behavior of ``loop`` control variables such as ``length`` and
``revindex0`` when looping over a generator. :issue:`459, 751, 794`,
:pr:`993`
- Async support is only loaded the first time an environment enables
it, in order to avoid a slow initial import. :issue:`765`
- In async environments, the ``|map`` filter will await the filter
call if needed. :pr:`913`
- In for loops that access ``loop`` attributes, the iterator is not
advanced ahead of the current iteration unless ``length``,
``revindex``, ``nextitem``, or ``last`` are accessed. This makes it
less likely to break ``groupby`` results. :issue:`555`, :pr:`1101`
- In async environments, the ``loop`` attributes ``length`` and
``revindex`` work for async iterators. :pr:`1101`
- In async environments, values from attribute/property access will
be awaited if needed. :pr:`1101`
- :class:`~loader.PackageLoader` doesn't depend on setuptools or
pkg_resources. :issue:`970`
- ``PackageLoader`` has limited support for :pep:`420` namespace
packages. :issue:`1097`
- Support :class:`os.PathLike` objects in
:class:`~loader.FileSystemLoader` and :class:`~loader.ModuleLoader`.
:issue:`870`
- :class:`~nativetypes.NativeTemplate` correctly handles quotes
between expressions. ``"'{{ a }}', '{{ b }}'"`` renders as the tuple
``('1', '2')`` rather than the string ``'1, 2'``. :issue:`1020`
- Creating a :class:`~nativetypes.NativeTemplate` directly creates a
:class:`~nativetypes.NativeEnvironment` instead of a default
:class:`Environment`. :issue:`1091`
- After calling ``LRUCache.copy()``, the copy's queue methods point to
the correct queue. :issue:`843`
- Compiling templates always writes UTF-8 instead of defaulting to the
system encoding. :issue:`889`
- ``|wordwrap`` filter treats existing newlines as separate paragraphs
to be wrapped individually, rather than creating short intermediate
lines. :issue:`175`
- Add ``break_on_hyphens`` parameter to ``|wordwrap`` filter.
:issue:`550`
- Cython compiled functions decorated as context functions will be
passed the context. :pr:`1108`
- When chained comparisons of constants are evaluated at compile time,
the result follows Python's behavior of returning ``False`` if any
comparison returns ``False``, rather than only the last one.
:issue:`1102`
- Tracebacks for exceptions in templates show the correct line numbers
and source for Python >= 3.7. :issue:`1104`
- Tracebacks for template syntax errors in Python 3 no longer show
internal compiler frames. :issue:`763`
- Add a ``DerivedContextReference`` node that can be used by
extensions to get the current context and local variables such as
``loop``. :issue:`860`
- Constant folding during compilation is applied to some node types
that were previously overlooked. :issue:`733`
- ``TemplateSyntaxError.source`` is not empty when raised from an
included template. :issue:`457`
- Passing an ``Undefined`` value to ``get_template`` (such as through
``extends``, ``import``, or ``include``), raises an
``UndefinedError`` consistently. ``select_template`` will show the
undefined message in the list of attempts rather than the empty
string. :issue:`1037`
- ``TemplateSyntaxError`` can be pickled. :pr:`1117`
Version 2.10.3
--------------
Released 2019-10-04
- Fix a typo in Babel entry point in ``setup.py`` that was preventing
installation.
Version 2.10.2
--------------
Released 2019-10-04
- Fix Python 3.7 deprecation warnings.
- Using ``range`` in the sandboxed environment uses ``xrange`` on
Python 2 to avoid memory use. :issue:`933`
- Use Python 3.7's better traceback support to avoid a core dump when
using debug builds of Python 3.7. :issue:`1050`
Version 2.10.1
--------------
Released 2019-04-06
- ``SandboxedEnvironment`` securely handles ``str.format_map`` in
order to prevent code execution through untrusted format strings.
The sandbox already handled ``str.format``.
Version 2.10
------------
Released 2017-11-08
- Added a new extension node called ``OverlayScope`` which can be used
to create an unoptimized scope that will look up all variables from
a derived context.
- Added an ``in`` test that works like the in operator. This can be
used in combination with ``reject`` and ``select``.
- Added ``previtem`` and ``nextitem`` to loop contexts, providing
access to the previous/next item in the loop. If such an item does
not exist, the value is undefined.
- Added ``changed(*values)`` to loop contexts, providing an easy way
of checking whether a value has changed since the last iteration (or
rather since the last call of the method)
- Added a ``namespace`` function that creates a special object which
allows attribute assignment using the ``set`` tag. This can be used
to carry data across scopes, e.g. from a loop body to code that
comes after the loop.
- Added a ``trimmed`` modifier to ``{% trans %}`` to strip linebreaks
and surrounding whitespace. Also added a new policy to enable this
for all ``trans`` blocks.
- The ``random`` filter is no longer incorrectly constant folded and
will produce a new random choice each time the template is rendered.
:pr:`478`
- Added a ``unique`` filter. :pr:`469`
- Added ``min`` and ``max`` filters. :pr:`475`
- Added tests for all comparison operators: ``eq``, ``ne``, ``lt``,
``le``, ``gt``, ``ge``. :pr:`665`
- ``import`` statement cannot end with a trailing comma. :pr:`617`,
:pr:`618`
- ``indent`` filter will not indent blank lines by default. :pr:`685`
- Add ``reverse`` argument for ``dictsort`` filter. :pr:`692`
- Add a ``NativeEnvironment`` that renders templates to native Python
types instead of strings. :pr:`708`
- Added filter support to the block ``set`` tag. :pr:`489`
- ``tojson`` filter marks output as safe to match documented behavior.
:pr:`718`
- Resolved a bug where getting debug locals for tracebacks could
modify template context.
- Fixed a bug where having many ``{% elif ... %}`` blocks resulted in
a "too many levels of indentation" error. These blocks now compile
to native ``elif ..:`` instead of ``else: if ..:`` :issue:`759`
Version 2.9.6
-------------
Released 2017-04-03
- Fixed custom context behavior in fast resolve mode :issue:`675`
Version 2.9.5
-------------
Released 2017-01-28
- Restored the original repr of the internal ``_GroupTuple`` because
this caused issues with ansible and it was an unintended change.
:issue:`654`
- Added back support for custom contexts that override the old
``resolve`` method since it was hard for people to spot that this
could cause a regression.
- Correctly use the buffer for the else block of for loops. This
caused invalid syntax errors to be caused on 2.x and completely
wrong behavior on Python 3 :issue:`669`
- Resolve an issue where the ``{% extends %}`` tag could not be used
with async environments. :issue:`668`
- Reduce memory footprint slightly by reducing our unicode database
dump we use for identifier matching on Python 3 :issue:`666`
- Fixed autoescaping not working for macros in async compilation mode.
:issue:`671`
Version 2.9.4
-------------
Released 2017-01-10
- Solved some warnings for string literals. :issue:`646`
- Increment the bytecode cache version which was not done due to an
oversight before.
- Corrected bad code generation and scoping for filtered loops.
:issue:`649`
- Resolved an issue where top-level output silencing after known
extend blocks could generate invalid code when blocks where
contained in if statements. :issue:`651`
- Made the ``truncate.leeway`` default configurable to improve
compatibility with older templates.
Version 2.9.3
-------------
Released 2017-01-08
- Restored the use of blocks in macros to the extend that was possible
before. On Python 3 it would render a generator repr instead of the
block contents. :issue:`645`
- Set a consistent behavior for assigning of variables in inner scopes
when the variable is also read from an outer scope. This now sets
the intended behavior in all situations however it does not restore
the old behavior where limited assignments to outer scopes was
possible. For more information and a discussion see :issue:`641`
- Resolved an issue where ``block scoped`` would not take advantage of
the new scoping rules. In some more exotic cases a variable
overridden in a local scope would not make it into a block.
- Change the code generation of the ``with`` statement to be in line
with the new scoping rules. This resolves some unlikely bugs in edge
cases. This also introduces a new internal ``With`` node that can be
used by extensions.
Version 2.9.2
-------------
Released 2017-01-08
- Fixed a regression that caused for loops to not be able to use the
same variable for the target as well as source iterator.
:issue:`640`
- Add support for a previously unknown behavior of macros. It used to
be possible in some circumstances to explicitly provide a caller
argument to macros. While badly buggy and unintended it turns out
that this is a common case that gets copy pasted around. To not
completely break backwards compatibility with the most common cases
it's now possible to provide an explicit keyword argument for caller
if it's given an explicit default. :issue:`642`
Version 2.9.1
-------------
Released 2017-01-07
- Resolved a regression with call block scoping for macros. Nested
caller blocks that used the same identifiers as outer macros could
refer to the wrong variable incorrectly.
Version 2.9
-----------
Released 2017-01-07, codename Derivation
- Change cache key definition in environment. This fixes a performance
regression introduced in 2.8.
- Added support for ``generator_stop`` on supported Python versions
(Python 3.5 and later)
- Corrected a long standing issue with operator precedence of math
operations not being what was expected.
- Added support for Python 3.6 async iterators through a new async
mode.
- Added policies for filter defaults and similar things.
- Urlize now sets "rel noopener" by default.
- Support attribute fallback for old-style classes in 2.x.
- Support toplevel set statements in extend situations.
- Restored behavior of Cycler for Python 3 users.
- Subtraction now follows the same behavior as other operators on
undefined values.
- ``map`` and friends will now give better error messages if you
forgot to quote the parameter.
- Depend on MarkupSafe 0.23 or higher.
- Improved the ``truncate`` filter to support better truncation in
case the string is barely truncated at all.
- Change the logic for macro autoescaping to be based on the runtime
autoescaping information at call time instead of macro define time.
- Ported a modified version of the ``tojson`` filter from Flask to
Jinja and hooked it up with the new policy framework.
- Block sets are now marked ``safe`` by default.
- On Python 2 the asciification of ASCII strings can now be disabled
with the ``compiler.ascii_str`` policy.
- Tests now no longer accept an arbitrary expression as first argument
but a restricted one. This means that you can now properly use
multiple tests in one expression without extra parentheses. In
particular you can now write ``foo is divisibleby 2 or foo is
divisibleby 3`` as you would expect.
- Greatly changed the scoping system to be more consistent with what
template designers and developers expect. There is now no more magic
difference between the different include and import constructs.
Context is now always propagated the same way. The only remaining
differences is the defaults for ``with context`` and ``without
context``.
- The ``with`` and ``autoescape`` tags are now built-in.
- Added the new ``select_autoescape`` function which helps configuring
better autoescaping easier.
- Fixed a runtime error in the sandbox when attributes of async
generators were accessed.
Version 2.8.1
-------------
Released 2016-12-29
- Fixed the ``for_qs`` flag for ``urlencode``.
- Fixed regression when applying ``int`` to non-string values.
- SECURITY: if the sandbox mode is used format expressions are now
sandboxed with the same rules as in Jinja. This solves various
information leakage problems that can occur with format strings.
Version 2.8
-----------
Released 2015-07-26, codename Replacement
- Added ``target`` parameter to urlize function.
- Added support for ``followsymlinks`` to the file system loader.
- The truncate filter now counts the length.
- Added equalto filter that helps with select filters.
- Changed cache keys to use absolute file names if available instead
of load names.
- Fixed loop length calculation for some iterators.
- Changed how Jinja enforces strings to be native strings in Python 2
to work when people break their default encoding.
- Added ``make_logging_undefined`` which returns an undefined
object that logs failures into a logger.
- If unmarshalling of cached data fails the template will be reloaded
now.
- Implemented a block ``set`` tag.
- Default cache size was increased to 400 from a low 50.
- Fixed ``is number`` test to accept long integers in all Python
versions.
- Changed ``is number`` to accept Decimal as a number.
- Added a check for default arguments followed by non-default
arguments. This change makes ``{% macro m(x, y=1, z) %}`` a syntax
error. The previous behavior for this code was broken anyway
(resulting in the default value being applied to ``y``).
- Add ability to use custom subclasses of
``jinja2.compiler.CodeGenerator`` and ``jinja2.runtime.Context`` by
adding two new attributes to the environment
(``code_generator_class`` and ``context_class``). :pr:`404`
- Added support for context/environment/evalctx decorator functions on
the finalize callback of the environment.
- Escape query strings for urlencode properly. Previously slashes were
not escaped in that place.
- Add 'base' parameter to 'int' filter.
Version 2.7.3
-------------
Released 2014-06-06
- Security issue: Corrected the security fix for the cache folder.
This fix was provided by RedHat.
Version 2.7.2
-------------
Released 2014-01-10
- Prefix loader was not forwarding the locals properly to inner
loaders. This is now fixed.
- Security issue: Changed the default folder for the filesystem cache
to be user specific and read and write protected on UNIX systems.
See `Debian bug 734747`_ for more information.
.. _Debian bug 734747: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=734747
Version 2.7.1
-------------
Released 2013-08-07
- Fixed a bug with ``call_filter`` not working properly on environment
and context filters.
- Fixed lack of Python 3 support for bytecode caches.
- Reverted support for defining blocks in included templates as this
broke existing templates for users.
- Fixed some warnings with hashing of undefineds and nodes if Python
is run with warnings for Python 3.
- Added support for properly hashing undefined objects.
- Fixed a bug with the title filter not working on already uppercase
strings.
Version 2.7
-----------
Released 2013-05-20, codename Translation
- Choice and prefix loaders now dispatch source and template lookup
separately in order to work in combination with module loaders as
advertised.
- Fixed filesizeformat.
- Added a non-silent option for babel extraction.
- Added ``urlencode`` filter that automatically quotes values for URL
safe usage with utf-8 as only supported encoding. If applications
want to change this encoding they can override the filter.
- Added ``keep-trailing-newline`` configuration to environments and
templates to optionally preserve the final trailing newline.
- Accessing ``last`` on the loop context no longer causes the iterator
to be consumed into a list.
- Python requirement changed: 2.6, 2.7 or >= 3.3 are required now,
supported by same source code, using the "six" compatibility
library.
- Allow ``contextfunction`` and other decorators to be applied to
``__call__``.
- Added support for changing from newline to different signs in the
``wordwrap`` filter.
- Added support for ignoring memcache errors silently.
- Added support for keeping the trailing newline in templates.
- Added finer grained support for stripping whitespace on the left
side of blocks.
- Added ``map``, ``select``, ``reject``, ``selectattr`` and
``rejectattr`` filters.
- Added support for ``loop.depth`` to figure out how deep inside a
recursive loop the code is.
- Disabled py_compile for pypy and python 3.
Version 2.6
-----------
Released 2011-07-24, codename Convolution
- Internal attributes now raise an internal attribute error now
instead of returning an undefined. This fixes problems when passing
undefined objects to Python semantics expecting APIs.
- Traceback support now works properly for PyPy. (Tested with 1.4)
- Implemented operator intercepting for sandboxed environments. This
allows application developers to disable builtin operators for
better security. (For instance limit the mathematical operators to
actual integers instead of longs)
- Groupby filter now supports dotted notation for grouping by
attributes of attributes.
- Scoped blocks now properly treat toplevel assignments and imports.
Previously an import suddenly "disappeared" in a scoped block.
- Automatically detect newer Python interpreter versions before
loading code from bytecode caches to prevent segfaults on invalid
opcodes. The segfault in earlier Jinja versions here was not a
Jinja bug but a limitation in the underlying Python interpreter. If
you notice Jinja segfaulting in earlier versions after an upgrade
of the Python interpreter you don't have to upgrade, it's enough to
flush the bytecode cache. This just no longer makes this necessary,
Jinja will automatically detect these cases now.
- The sum filter can now sum up values by attribute. This is a
backwards incompatible change. The argument to the filter previously
was the optional starting index which defaults to zero. This now
became the second argument to the function because it's rarely used.
- Like sum, sort now also makes it possible to order items by
attribute.
- Like sum and sort, join now also is able to join attributes of
objects as string.
- The internal eval context now has a reference to the environment.
- Added a mapping test to see if an object is a dict or an object with
a similar interface.
Version 2.5.5
-------------
Released 2010-10-18
- Built documentation is no longer part of release.
Version 2.5.4
-------------
Released 2010-10-17
- Fixed extensions not loading properly with overlays.
- Work around a bug in cpython for the debugger that causes segfaults
on 64bit big-endian architectures.
Version 2.5.3
-------------
Released 2010-10-17
- Fixed an operator precedence error introduced in 2.5.2. Statements
like "-foo.bar" had their implicit parentheses applied around the
first part of the expression ("(-foo).bar") instead of the more
correct "-(foo.bar)".
Version 2.5.2
-------------
Released 2010-08-18
- Improved setup.py script to better work with assumptions people
might still have from it (``--with-speedups``).
- Fixed a packaging error that excluded the new debug support.
Version 2.5.1
-------------
Released 2010-08-17
- StopIteration exceptions raised by functions called from templates
are now intercepted and converted to undefineds. This solves a lot
of debugging grief. (StopIteration is used internally to abort
template execution)
- Improved performance of macro calls slightly.
- Babel extraction can now properly extract newstyle gettext calls.
- Using the variable ``num`` in newstyle gettext for something else
than the pluralize count will no longer raise a :exc:`KeyError`.
- Removed builtin markup class and switched to markupsafe. For
backwards compatibility the pure Python implementation still exists
but is pulled from markupsafe by the Jinja developers. The debug
support went into a separate feature called "debugsupport" and is
disabled by default because it is only relevant for Python 2.4
- Fixed an issue with unary operators having the wrong precedence.
Version 2.5
-----------
Released 2010-05-29, codename Incoherence
- Improved the sort filter (should have worked like this for a long
time) by adding support for case insensitive searches.
- Fixed a bug for getattribute constant folding.
- Support for newstyle gettext translations which result in a nicer
in-template user interface and more consistent catalogs.
- It's now possible to register extensions after an environment was
created.
Version 2.4.1
-------------
Released 2010-04-20
- Fixed an error reporting bug for undefined.
Version 2.4
-----------
Released 2010-04-13, codename Correlation
- The environment template loading functions now transparently pass
through a template object if it was passed to it. This makes it
possible to import or extend from a template object that was passed
to the template.
- Added a ``ModuleLoader`` that can load templates from
precompiled sources. The environment now features a method to
compile the templates from a configured loader into a zip file or
folder.
- The _speedups C extension now supports Python 3.
- Added support for autoescaping toggling sections and support for
evaluation contexts.
- Extensions have a priority now.
Version 2.3.1
-------------
Released 2010-02-19
- Fixed an error reporting bug on all python versions
- Fixed an error reporting bug on Python 2.4
Version 2.3
-----------
Released 2010-02-10, codename 3000 Pythons
- Fixes issue with code generator that causes unbound variables to be
generated if set was used in if-blocks and other small identifier
problems.
- Include tags are now able to select between multiple templates and
take the first that exists, if a list of templates is given.
- Fixed a problem with having call blocks in outer scopes that have an
argument that is also used as local variable in an inner frame
:issue:`360`.
- Greatly improved error message reporting :pr:`339`
- Implicit tuple expressions can no longer be totally empty. This
change makes ``{% if %}`` a syntax error now. :issue:`364`
- Added support for translator comments if extracted via babel.
- Added with-statement extension.
- Experimental Python 3 support.
Version 2.2.1
-------------
Released 2009-09-14
- Fixes some smaller problems for Jinja on Jython.
Version 2.2
-----------
Released 2009-09-13, codename Kong
- Include statements can now be marked with ``ignore missing`` to skip
non existing templates.
- Priority of ``not`` raised. It's now possible to write ``not foo in
bar`` as an alias to ``foo not in bar`` like in python. Previously
the grammar required parentheses (``not (foo in bar)``) which was
odd.
- Fixed a bug that caused syntax errors when defining macros or using
the ``{% call %}`` tag inside loops.
- Fixed a bug in the parser that made ``{{ foo[1, 2] }}`` impossible.
- Made it possible to refer to names from outer scopes in included
templates that were unused in the callers frame :issue:`327`
- Fixed a bug that caused internal errors if names where used as
iteration variable and regular variable *after* the loop if that
variable was unused *before* the loop. :pr:`331`
- Added support for optional ``scoped`` modifier to blocks.
- Added support for line-comments.
- Added the ``meta`` module.
- Renamed (undocumented) attribute "overlay" to "overlayed" on the
environment because it was clashing with a method of the same name.
- Speedup extension is now disabled by default.
Version 2.1.1
-------------
Released 2008-12-25
- Fixed a translation error caused by looping over empty recursive
loops.
Version 2.1
-----------
Released 2008-11-23, codename Yasuzō
- Fixed a bug with nested loops and the special loop variable. Before
the change an inner loop overwrote the loop variable from the outer
one after iteration.
- Fixed a bug with the i18n extension that caused the explicit
pluralization block to look up the wrong variable.
- Fixed a limitation in the lexer that made ``{{ foo.0.0 }}``
impossible.
- Index based subscribing of variables with a constant value returns
an undefined object now instead of raising an index error. This was
a bug caused by eager optimizing.
- The i18n extension looks up ``foo.ugettext`` now followed by
``foo.gettext`` if an translations object is installed. This makes
dealing with custom translations classes easier.
- Fixed a confusing behavior with conditional extending. loops were
partially executed under some conditions even though they were not
part of a visible area.
- Added ``sort`` filter that works like ``dictsort`` but for arbitrary
sequences.
- Fixed a bug with empty statements in macros.
- Implemented a bytecode cache system.
- The template context is now weakref-able
- Inclusions and imports "with context" forward all variables now, not
only the initial context.
- Added a cycle helper called ``cycler``.
- Added a joining helper called ``joiner``.
- Added a ``compile_expression`` method to the environment that allows
compiling of Jinja expressions into callable Python objects.
- Fixed an escaping bug in urlize
Version 2.0
-----------
Released 2008-07-17, codename Jinjavitus
- The subscribing of objects (looking up attributes and items) changed
from slightly. It's now possible to give attributes or items a
higher priority by either using dot-notation lookup or the bracket
syntax. This also changed the AST slightly. ``Subscript`` is gone
and was replaced with ``Getitem`` and ``Getattr``.
- Added support for preprocessing and token stream filtering for
extensions. This would allow extensions to allow simplified gettext
calls in template data and something similar.
- Added ``TemplateStream.dump``.
- Added missing support for implicit string literal concatenation.
``{{ "foo" "bar" }}`` is equivalent to ``{{ "foobar" }}``
- ``else`` is optional for conditional expressions. If not given it
evaluates to ``false``.
- Improved error reporting for undefined values by providing a
position.
- ``filesizeformat`` filter uses decimal prefixes now by default and
can be set to binary mode with the second parameter.
- Fixed bug in finalizer
Version 2.0rc1
--------------
Released 2008-06-09
- First release of Jinja 2.
================================================
FILE: LICENSE.txt
================================================
Copyright 2007 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# 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.
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.
- AsyncIO support for generating templates and calling async
functions.
- 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.
## In A Nutshell
```jinja
{% extends "base.html" %}
{% block title %}Members{% endblock %}
{% block content %}
{% endblock %}
```
## Donate
The Pallets organization develops and supports Jinja and other popular
packages. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, [please
donate today][].
[please donate today]: https://palletsprojects.com/donate
## Contributing
See our [detailed contributing documentation][contrib] for many ways to
contribute, including reporting issues, requesting features, asking or answering
questions, and making PRs.
[contrib]: https://palletsprojects.com/contributing/
================================================
FILE: docs/Makefile
================================================
# 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)
================================================
FILE: docs/api.rst
================================================
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
`.
.. 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:
- 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 `. 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|datetime_format }}
{{ article.pub_date|datetime_format("%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 `` `` and ``
``
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 = " \n"
if eval_ctx.autoescape:
value = escape(value)
br = Markup(br)
result = "\n\n".join(
f"
{br.join(p.splitlines())}
"
for p in re.split(r"(?:\r\n|\r(?!\n)|\n){2,}", value)
)
return Markup(result) if eval_ctx.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 `. 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 test. The object is passed as the first argument, making the value
being tested 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
` 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
================================================
FILE: docs/changes.rst
================================================
Changes
=======
.. include:: ../CHANGES.rst
================================================
FILE: docs/conf.py
================================================
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 --------------------------------------------------------------
default_role = "code"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.extlinks",
"sphinx.ext.intersphinx",
"sphinxcontrib.log_cabinet",
"pallets_sphinx_themes",
]
autodoc_member_order = "bysource"
autodoc_typehints = "description"
autodoc_preserve_defaults = True
extlinks = {
"issue": ("https://github.com/pallets/jinja/issues/%s", "#%s"),
"pr": ("https://github.com/pallets/jinja/pull/%s", "#%s"),
"ghsa": ("https://github.com/pallets/jinja/security/advisories/GHSA-%s", "GHSA-%s"),
}
intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
}
# 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("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-icon.svg"
html_logo = "_static/jinja-logo.svg"
html_title = f"Jinja Documentation ({version})"
html_show_sourcelink = False
================================================
FILE: docs/examples/cache_extension.py
================================================
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
================================================
FILE: docs/examples/inline_gettext_extension.py
================================================
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::
_(Welcome)
_(This is a paragraph)
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,
)
================================================
FILE: docs/extensions.rst
================================================
.. _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.
A convenient way to provide these functions is to call one of the below
methods depending on the translation system in use. If you do not require
actual translation, use ``Environment.install_null_translations`` to
install no-op functions.
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 `.
.. _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 `.
.. _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 %}
...
{% 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
_(Welcome)
_(This is a paragraph)
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
================================================
FILE: docs/faq.rst
================================================
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.
.. _MarkupSafe: https://markupsafe.palletsprojects.com/
================================================
FILE: docs/index.rst
================================================
.. rst-class:: hide-header
Jinja
=====
.. image:: _static/jinja-name.svg
:align: center
:height: 200px
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
================================================
FILE: docs/integration.rst
================================================
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://pylonsproject.org/
================================================
FILE: docs/intro.rst
================================================
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.10 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/
================================================
FILE: docs/license.rst
================================================
BSD-3-Clause License
====================
.. literalinclude:: ../LICENSE.txt
:language: text
================================================
FILE: docs/make.bat
================================================
@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
================================================
FILE: docs/nativetypes.rst
================================================
.. 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
Sandboxed Native Environment
----------------------------
You can combine :class:`.SandboxedEnvironment` and :class:`NativeEnvironment` to
get both behaviors.
.. code-block:: python
class SandboxedNativeEnvironment(SandboxedEnvironment, NativeEnvironment):
pass
API
---
.. autoclass:: NativeEnvironment([options])
.. autoclass:: NativeTemplate([options])
:members: render
================================================
FILE: docs/sandbox.rst
================================================
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 ` 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)
================================================
FILE: docs/switching.rst
================================================
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 %}
{{ forloop.counter }}. {{ 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 %}
{{ user }}
{% endfor %}
In Jinja, the ``loop`` context has a ``cycle`` method.
.. code-block:: jinja
{% for user in users %}
{{ user }}
{% 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>
% for item in list:
${item}
% endfor
Looks like this in Jinja with the above configuration:
.. code-block:: jinja
<% extends "layout.html" %>
<% block title %>Page Title<% endblock %>
<% block body %>
% for item in list:
${item}
% endfor
<% endblock %>
================================================
FILE: docs/templates.rst
================================================
.. py:currentmodule:: jinja2
.. highlight:: html+jinja
Template Designer Documentation
===============================
This document describes the syntax and semantics of the template engine and
will be most useful as reference to those creating Jinja templates. As the
template engine is very flexible, the configuration from the application can
be slightly different from the code presented here in terms of delimiters and
behavior of undefined values.
Synopsis
--------
A Jinja template is simply a text file. Jinja can generate any text-based
format (HTML, XML, CSV, LaTeX, etc.). A Jinja template doesn't need to have a
specific extension: ``.html``, ``.xml``, or any other extension is just fine.
A template contains **variables** and/or **expressions**, which get replaced
with values when a template is *rendered*; and **tags**, which control the
logic of the template. The template syntax is heavily inspired by Django and
Python.
Below is a minimal template that illustrates a few basics using the default
Jinja configuration. We will cover the details later in this document::
My Webpage
{{ a_variable }}
{# a comment #}
The following example shows the default configuration settings. An application
developer can change the syntax configuration from ``{% foo %}`` to ``<% foo
%>``, or something similar.
There are a few kinds of delimiters. The default Jinja delimiters are
configured as follows:
* ``{% ... %}`` for :ref:`Statements `
* ``{{ ... }}`` for :ref:`Expressions` to print to the template output
* ``{# ... #}`` for :ref:`Comments` not included in the template output
:ref:`Line Statements and Comments ` are also possible,
though they don't have default prefix characters. To use them, set
``line_statement_prefix`` and ``line_comment_prefix`` when creating the
:class:`~jinja2.Environment`.
Template File Extension
~~~~~~~~~~~~~~~~~~~~~~~
As stated above, any file can be loaded as a template, regardless of
file extension. Adding a ``.jinja`` extension, like ``user.html.jinja``
may make it easier for some IDEs or editor plugins, but is not required.
Autoescaping, introduced later, can be applied based on file extension,
so you'll need to take the extra suffix into account in that case.
Another good heuristic for identifying templates is that they are in a
``templates`` folder, regardless of extension. This is a common layout
for projects.
.. _variables:
Variables
---------
Template variables are defined by the context dictionary passed to the
template.
You can mess around with the variables in templates provided they are passed in
by the application. Variables may have attributes or elements on them you can
access too. What attributes a variable has depends heavily on the application
providing that variable.
You can use a dot (``.``) to access attributes of a variable in addition
to the standard Python ``__getitem__`` "subscript" syntax (``[]``).
The following lines do the same thing::
{{ foo.bar }}
{{ foo['bar'] }}
It's important to know that the outer double-curly braces are *not* part of the
variable, but the print statement. If you access variables inside tags don't
put the braces around them.
If a variable or attribute does not exist, you will get back an undefined
value. What you can do with that kind of value depends on the application
configuration: the default behavior is to evaluate to an empty string if
printed or iterated over, and to fail for every other operation.
.. _notes-on-subscriptions:
.. admonition:: Implementation
For the sake of convenience, ``foo.bar`` in Jinja does the following
things on the Python layer:
- check for an attribute called `bar` on `foo`
(``getattr(foo, 'bar')``)
- if there is not, check for an item ``'bar'`` in `foo`
(``foo.__getitem__('bar')``)
- if there is not, return an undefined object.
``foo['bar']`` works mostly the same with a small difference in sequence:
- check for an item ``'bar'`` in `foo`.
(``foo.__getitem__('bar')``)
- if there is not, check for an attribute called `bar` on `foo`.
(``getattr(foo, 'bar')``)
- if there is not, return an undefined object.
This is important if an object has an item and attribute with the same
name. Additionally, the :func:`attr` filter only looks up attributes.
.. _filters:
Filters
-------
Variables can be modified by **filters**. Filters are separated from the
variable by a pipe symbol (``|``) and may have optional arguments in
parentheses. Multiple filters can be chained. The output of one filter is
applied to the next.
For example, ``{{ name|striptags|title }}`` will remove all HTML Tags from
variable `name` and title-case the output (``title(striptags(name))``).
Filters that accept arguments have parentheses around the arguments, just like
a function call. For example: ``{{ listx|join(', ') }}`` will join a list with
commas (``str.join(', ', listx)``).
The :ref:`builtin-filters` below describes all the builtin filters.
.. _tests:
Tests
-----
Beside filters, there are also so-called "tests" available. Tests can be used
to test a variable against a common expression. To test a variable or
expression, you add `is` plus the name of the test after the variable. For
example, to find out if a variable is defined, you can do ``name is defined``,
which will then return true or false depending on whether `name` is defined
in the current template context.
Tests can accept arguments, too. If the test only takes one argument, you can
leave out the parentheses. For example, the following two
expressions do the same thing::
{% if loop.index is divisibleby 3 %}
{% if loop.index is divisibleby(3) %}
The :ref:`builtin-tests` below describes all the builtin tests.
.. _comments:
Comments
--------
To comment-out part of a line in a template, use the comment syntax which is
by default set to ``{# ... #}``. This is useful to comment out parts of the
template for debugging or to add information for other template designers or
yourself::
{# note: commented-out template because we no longer use this
{% for user in users %}
...
{% endfor %}
#}
Whitespace Control
------------------
In the default configuration:
* a single trailing newline is stripped if present
* other whitespace (spaces, tabs, newlines etc.) is returned unchanged
If an application configures Jinja to `trim_blocks`, the first newline after a
template tag is removed automatically (like in PHP). The `lstrip_blocks`
option can also be set to strip tabs and spaces from the beginning of a
line to the start of a block. (Nothing will be stripped if there are
other characters before the start of the block.)
With both ``trim_blocks`` and ``lstrip_blocks`` disabled (the default), block
tags on their own lines will be removed, but a blank line will remain and the
spaces in the content will be preserved. For example, this template:
.. code-block:: jinja
{% if True %}
yay
{% endif %}
With both ``trim_blocks`` and ``lstrip_blocks`` disabled, the template is
rendered with blank lines inside the div:
.. code-block:: text
yay
With both ``trim_blocks`` and ``lstrip_blocks`` enabled, the template block
lines are completely removed:
.. code-block:: text
yay
You can manually disable the `lstrip_blocks` behavior by putting a
plus sign (``+``) at the start of a block::
{%+ if something %}yay{% endif %}
Similarly, you can manually disable the ``trim_blocks`` behavior by
putting a plus sign (``+``) at the end of a block::
{% if something +%}
yay
{% endif %}
You can also strip whitespace in templates by hand. If you add a minus
sign (``-``) to the start or end of a block (e.g. a :ref:`for-loop` tag), a
comment, or a variable expression, the whitespaces before or after
that block will be removed::
{% for item in seq -%}
{{ item }}
{%- endfor %}
This will yield all elements without whitespace between them. If `seq` was
a list of numbers from ``1`` to ``9``, the output would be ``123456789``.
If :ref:`line-statements` are enabled, they strip leading whitespace
automatically up to the beginning of the line.
By default, Jinja also removes trailing newlines. To keep single
trailing newlines, configure Jinja to `keep_trailing_newline`.
.. admonition:: Note
You must not add whitespace between the tag and the minus sign.
**valid**::
{%- if foo -%}...{% endif %}
**invalid**::
{% - if foo - %}...{% endif %}
Escaping
--------
It is sometimes desirable -- even necessary -- to have Jinja ignore parts
it would otherwise handle as variables or blocks. For example, if, with
the default syntax, you want to use ``{{`` as a raw string in a template and
not start a variable, you have to use a trick.
The easiest way to output a literal variable delimiter (``{{``) is by using a
variable expression::
{{ '{{' }}
For bigger sections, it makes sense to mark a block `raw`. For example, to
include example Jinja syntax in a template, you can use this snippet::
{% raw %}
{% for item in seq %}
{{ item }}
{% endfor %}
{% endraw %}
.. admonition:: Note
Minus sign at the end of ``{% raw -%}`` tag cleans all the spaces and newlines
preceding the first character of your raw data.
.. _line-statements:
Line Statements
---------------
If line statements are enabled by the application, it's possible to mark a
line as a statement. For example, if the line statement prefix is configured
to ``#``, the following two examples are equivalent::
# for item in seq
{{ item }}
# endfor
{% for item in seq %}
{{ item }}
{% endfor %}
The line statement prefix can appear anywhere on the line as long as no text
precedes it. For better readability, statements that start a block (such as
`for`, `if`, `elif` etc.) may end with a colon::
# for item in seq:
...
# endfor
.. admonition:: Note
Line statements can span multiple lines if there are open parentheses,
braces or brackets::
# for href, caption in [('index.html', 'Index'),
('about.html', 'About')]:
Since Jinja 2.2, line-based comments are available as well. For example, if
the line-comment prefix is configured to be ``##``, everything from ``##`` to
the end of the line is ignored (excluding the newline sign)::
# for item in seq:
{{ item }}
## this comment is ignored
# endfor
.. _template-inheritance:
Template Inheritance
--------------------
The most powerful part of Jinja is template inheritance. Template inheritance
allows you to build a base "skeleton" template that contains all the common
elements of your site and defines **blocks** that child templates can override.
Sounds complicated but is very basic. It's easiest to understand it by starting
with an example.
Base Template
~~~~~~~~~~~~~
This template, which we'll call ``base.html``, defines a simple HTML skeleton
document that you might use for a simple two-column page. It's the job of
"child" templates to fill the empty blocks with content::
{% block head %}
{% block title %}{% endblock %} - My Webpage
{% endblock %}
{% block content %}{% endblock %}
In this example, the ``{% block %}`` tags define four blocks that child templates
can fill in. All the `block` tag does is tell the template engine that a
child template may override those placeholders in the template.
``block`` tags can be inside other blocks such as ``if``, but they will
always be executed regardless of if the ``if`` block is actually
rendered.
Child Template
~~~~~~~~~~~~~~
A child template might look like this::
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block content %}
Index
Welcome to my awesome homepage.
{% endblock %}
The ``{% extends %}`` tag is the key here. It tells the template engine that
this template "extends" another template. When the template system evaluates
this template, it first locates the parent. The extends tag should be the
first tag in the template. Everything before it is printed out normally and
may cause confusion. For details about this behavior and how to take
advantage of it, see :ref:`null-default-fallback`. Also a block will always be
filled in regardless of whether the surrounding condition is evaluated to be true
or false.
The filename of the template depends on the template loader. For example, the
:class:`FileSystemLoader` allows you to access other templates by giving the
filename. You can access templates in subdirectories with a slash::
{% extends "layout/default.html" %}
But this behavior can depend on the application embedding Jinja. Note that
since the child template doesn't define the ``footer`` block, the value from
the parent template is used instead.
You can't define multiple ``{% block %}`` tags with the same name in the
same template. This limitation exists because a block tag works in "both"
directions. That is, a block tag doesn't just provide a placeholder to fill
- it also defines the content that fills the placeholder in the *parent*.
If there were two similarly-named ``{% block %}`` tags in a template,
that template's parent wouldn't know which one of the blocks' content to use.
If you want to print a block multiple times, you can, however, use the special
`self` variable and call the block with that name::
{% block title %}{% endblock %}
{{ self.title() }}
{% block body %}{% endblock %}
Super Blocks
~~~~~~~~~~~~
It's possible to render the contents of the parent block by calling ``super()``.
This gives back the results of the parent block::
{% block sidebar %}
Table Of Contents
...
{{ super() }}
{% endblock %}
Nesting extends
~~~~~~~~~~~~~~~
In the case of multiple levels of ``{% extends %}``,
``super`` references may be chained (as in ``super.super()``)
to skip levels in the inheritance tree.
For example::
# parent.tmpl
body: {% block body %}Hi from parent.{% endblock %}
# child.tmpl
{% extends "parent.tmpl" %}
{% block body %}Hi from child. {{ super() }}{% endblock %}
# grandchild1.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild1.{% endblock %}
# grandchild2.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild2. {{ super.super() }} {% endblock %}
Rendering ``child.tmpl`` will give
``body: Hi from child. Hi from parent.``
Rendering ``grandchild1.tmpl`` will give
``body: Hi from grandchild1.``
Rendering ``grandchild2.tmpl`` will give
``body: Hi from grandchild2. Hi from parent.``
Named Block End-Tags
~~~~~~~~~~~~~~~~~~~~
Jinja allows you to put the name of the block after the end tag for better
readability::
{% block sidebar %}
{% block inner_sidebar %}
...
{% endblock inner_sidebar %}
{% endblock sidebar %}
However, the name after the `endblock` word must match the block name.
Block Nesting and Scope
~~~~~~~~~~~~~~~~~~~~~~~
Blocks can be nested for more complex layouts. By default, a block may not
access variables from outside the block (outer scopes)::
{% for item in seq %}
{% block loop_item %}{{ item }}{% endblock %}
{% endfor %}
This example would output empty ``
`` items because `item` is unavailable
inside the block. The reason for this is that if the block is replaced by
a child template, a variable would appear that was not defined in the block or
passed to the context.
Starting with Jinja 2.2, you can explicitly specify that variables are
available in a block by setting the block to "scoped" by adding the `scoped`
modifier to a block declaration::
{% for item in seq %}
{% endfor %}
When overriding a block, the `scoped` modifier does not have to be provided.
Required Blocks
~~~~~~~~~~~~~~~
Blocks can be marked as ``required``. They must be overridden at some
point, but not necessarily by the direct child template. Required blocks
may only contain space and comments, and they cannot be rendered
directly.
.. code-block:: jinja
:caption: ``page.txt``
{% block body required %}{% endblock %}
.. code-block:: jinja
:caption: ``issue.txt``
{% extends "page.txt" %}
.. code-block:: jinja
:caption: ``bug_report.txt``
{% extends "issue.txt" %}
{% block body %}Provide steps to demonstrate the bug.{% endblock %}
Rendering ``page.txt`` or ``issue.txt`` will raise
``TemplateRuntimeError`` because they don't override the ``body`` block.
Rendering ``bug_report.txt`` will succeed because it does override the
block.
When combined with ``scoped``, the ``required`` modifier must be placed
*after* the scoped modifier. Here are some valid examples:
.. code-block:: jinja
{% block body scoped %}{% endblock %}
{% block body required %}{% endblock %}
{% block body scoped required %}{% endblock %}
Template Objects
~~~~~~~~~~~~~~~~
``extends``, ``include``, and ``import`` can take a template object
instead of the name of a template to load. This could be useful in some
advanced situations, since you can use Python code to load a template
first and pass it in to ``render``.
.. code-block:: python
if debug_mode:
layout = env.get_template("debug_layout.html")
else:
layout = env.get_template("layout.html")
user_detail = env.get_template("user/detail.html")
return user_detail.render(layout=layout)
.. code-block:: jinja
{% extends layout %}
Note how ``extends`` is passed the variable with the template object
that was passed to ``render``, instead of a string.
HTML Escaping
-------------
When generating HTML from templates, there's always a risk that a variable will
include characters that affect the resulting HTML. There are two approaches:
a. manually escaping each variable; or
b. automatically escaping everything by default.
Jinja supports both. What is used depends on the application configuration.
The default configuration is no automatic escaping; for various reasons:
- Escaping everything except for safe values will also mean that Jinja is
escaping variables known to not include HTML (e.g. numbers, booleans)
which can be a huge performance hit.
- The information about the safety of a variable is very fragile. It could
happen that by coercing safe and unsafe values, the return value is
double-escaped HTML.
Working with Manual Escaping
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If manual escaping is enabled, it's **your** responsibility to escape
variables if needed. What to escape? If you have a variable that *may*
include any of the following chars (``>``, ``<``, ``&``, or ``"``) you
**SHOULD** escape it unless the variable contains well-formed and trusted
HTML. Escaping works by piping the variable through the ``|e`` filter::
{{ user.username|e }}
Working with Automatic Escaping
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When automatic escaping is enabled, everything is escaped by default except
for values explicitly marked as safe. Variables and expressions
can be marked as safe either in:
a. The context dictionary by the application with
:class:`markupsafe.Markup`
b. The template, with the ``|safe`` filter.
If a string that you marked safe is passed through other Python code
that doesn't understand that mark, it may get lost. Be aware of when
your data is marked safe and how it is processed before arriving at the
template.
If a value has been escaped but is not marked safe, auto-escaping will
still take place and result in double-escaped characters. If you know
you have data that is already safe but not marked, be sure to wrap it in
``Markup`` or use the ``|safe`` filter.
Jinja functions (macros, `super`, `self.BLOCKNAME`) always return template
data that is marked as safe.
String literals in templates with automatic escaping are considered
unsafe because native Python strings are not safe.
.. _list-of-control-structures:
List of Control Structures
--------------------------
A control structure refers to all those things that control the flow of a
program - conditionals (i.e. if/elif/else), for-loops, as well as things like
macros and blocks. With the default syntax, control structures appear inside
``{% ... %}`` blocks.
.. _for-loop:
For
~~~
Loop over each item in a sequence. For example, to display a list of users
provided in a variable called `users`::
Members
{% for user in users %}
{{ user.username|e }}
{% endfor %}
As variables in templates retain their object properties, it is possible to
iterate over containers like `dict`::
{% for key, value in my_dict.items() %}
{{ key|e }}
{{ value|e }}
{% endfor %}
Python dicts may not be in the order you want to display them in. If
order matters, use the ``|dictsort`` filter.
.. code-block:: jinja
{% for key, value in my_dict | dictsort %}
{{ key|e }}
{{ value|e }}
{% endfor %}
Inside of a for-loop block, you can access some special variables:
+-----------------------+---------------------------------------------------+
| Variable | Description |
+=======================+===================================================+
| `loop.index` | The current iteration of the loop. (1 indexed) |
+-----------------------+---------------------------------------------------+
| `loop.index0` | The current iteration of the loop. (0 indexed) |
+-----------------------+---------------------------------------------------+
| `loop.revindex` | The number of iterations from the end of the loop |
| | (1 indexed) |
+-----------------------+---------------------------------------------------+
| `loop.revindex0` | The number of iterations from the end of the loop |
| | (0 indexed) |
+-----------------------+---------------------------------------------------+
| `loop.first` | True if first iteration. |
+-----------------------+---------------------------------------------------+
| `loop.last` | True if last iteration. |
+-----------------------+---------------------------------------------------+
| `loop.length` | The number of items in the sequence. |
+-----------------------+---------------------------------------------------+
| `loop.cycle` | A helper function to cycle between a list of |
| | sequences. See the explanation below. |
+-----------------------+---------------------------------------------------+
| `loop.depth` | Indicates how deep in a recursive loop |
| | the rendering currently is. Starts at level 1 |
+-----------------------+---------------------------------------------------+
| `loop.depth0` | Indicates how deep in a recursive loop |
| | the rendering currently is. Starts at level 0 |
+-----------------------+---------------------------------------------------+
| `loop.previtem` | The item from the previous iteration of the loop. |
| | Undefined during the first iteration. |
+-----------------------+---------------------------------------------------+
| `loop.nextitem` | The item from the following iteration of the loop.|
| | Undefined during the last iteration. |
+-----------------------+---------------------------------------------------+
| `loop.changed(*val)` | True if previously called with a different value |
| | (or not called at all). |
+-----------------------+---------------------------------------------------+
Within a for-loop, it's possible to cycle among a list of strings/variables
each time through the loop by using the special `loop.cycle` helper::
{% for row in rows %}
{{ row }}
{% endfor %}
Since Jinja 2.1, an extra `cycle` helper exists that allows loop-unbound
cycling. For more information, have a look at the :ref:`builtin-globals`.
.. _loop-filtering:
Unlike in Python, it's not possible to `break` or `continue` in a loop. You
can, however, filter the sequence during iteration, which allows you to skip
items. The following example skips all the users which are hidden::
{% for user in users if not user.hidden %}
{{ user.username|e }}
{% endfor %}
The advantage is that the special `loop` variable will count correctly; thus
not counting the users not iterated over.
If no iteration took place because the sequence was empty or the filtering
removed all the items from the sequence, you can render a default block
by using `else`::
{% for user in users %}
{{ user.username|e }}
{% else %}
no users found
{% endfor %}
Note that, in Python, `else` blocks are executed whenever the corresponding
loop **did not** `break`. Since Jinja loops cannot `break` anyway,
a slightly different behavior of the `else` keyword was chosen.
It is also possible to use loops recursively. This is useful if you are
dealing with recursive data such as sitemaps or RDFa.
To use loops recursively, you basically have to add the `recursive` modifier
to the loop definition and call the `loop` variable with the new iterable
where you want to recurse.
The following example implements a sitemap with recursive loops::
The `loop` variable always refers to the closest (innermost) loop. If we
have more than one level of loops, we can rebind the variable `loop` by
writing `{% set outer_loop = loop %}` after the loop that we want to
use recursively. Then, we can call it using `{{ outer_loop(...) }}`
Please note that assignments in loops will be cleared at the end of the
iteration and cannot outlive the loop scope. Older versions of Jinja had
a bug where in some circumstances it appeared that assignments would work.
This is not supported. See :ref:`assignments` for more information about
how to deal with this.
If all you want to do is check whether some value has changed since the
last iteration or will change in the next iteration, you can use `previtem`
and `nextitem`::
{% for value in values %}
{% if loop.previtem is defined and value > loop.previtem %}
The value just increased!
{% endif %}
{{ value }}
{% if loop.nextitem is defined and loop.nextitem > value %}
The value will increase even more!
{% endif %}
{% endfor %}
If you only care whether the value changed at all, using `changed` is even
easier::
{% for entry in entries %}
{% if loop.changed(entry.category) %}
{{ entry.category }}
{% endif %}
{{ entry.message }}
{% endfor %}
.. _if:
If
~~
The `if` statement in Jinja is comparable with the Python if statement.
In the simplest form, you can use it to test if a variable is defined, not
empty and not false::
{% if users %}
{% for user in users %}
{{ user.username|e }}
{% endfor %}
{% endif %}
For multiple branches, `elif` and `else` can be used like in Python. You can
use more complex :ref:`expressions` there, too::
{% if kenny.sick %}
Kenny is sick.
{% elif kenny.dead %}
You killed Kenny! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
If can also be used as an :ref:`inline expression ` and for
:ref:`loop filtering `.
.. _macros:
Macros
~~~~~~
Macros are comparable with functions in regular programming languages. They
are useful to put often used idioms into reusable functions to not repeat
yourself ("DRY").
Here's a small example of a macro that renders a form element::
{% macro input(name, value='', type='text', size=20) -%}
{%- endmacro %}
The macro can then be called like a function in the namespace::
{{ input('username') }}
{{ input('password', type='password') }}
If the macro was defined in a different template, you have to
:ref:`import ` it first.
Inside macros, you have access to three special variables:
`varargs`
If more positional arguments are passed to the macro than accepted by the
macro, they end up in the special `varargs` variable as a list of values.
`kwargs`
Like `varargs` but for keyword arguments. All unconsumed keyword
arguments are stored in this special variable.
`caller`
If the macro was called from a :ref:`call` tag, the caller is stored
in this variable as a callable macro.
Macros also expose some of their internal details. The following attributes
are available on a macro object:
`name`
The name of the macro. ``{{ input.name }}`` will print ``input``.
`arguments`
A tuple of the names of arguments the macro accepts.
`catch_kwargs`
This is `true` if the macro accepts extra keyword arguments (i.e.: accesses
the special `kwargs` variable).
`catch_varargs`
This is `true` if the macro accepts extra positional arguments (i.e.:
accesses the special `varargs` variable).
`caller`
This is `true` if the macro accesses the special `caller` variable and may
be called from a :ref:`call` tag.
If a macro name starts with an underscore, it's not exported and can't
be imported.
Due to how scopes work in Jinja, a macro in a child template does not
override a macro in a parent template. The following will output
"LAYOUT", not "CHILD".
.. code-block:: jinja
:caption: ``layout.txt``
{% macro foo() %}LAYOUT{% endmacro %}
{% block body %}{% endblock %}
.. code-block:: jinja
:caption: ``child.txt``
{% extends 'layout.txt' %}
{% macro foo() %}CHILD{% endmacro %}
{% block body %}{{ foo() }}{% endblock %}
.. _call:
Call
~~~~
In some cases it can be useful to pass a macro to another macro. For this
purpose, you can use the special `call` block. The following example shows
a macro that takes advantage of the call functionality and how it can be
used::
{% macro render_dialog(title, class='dialog') -%}
{{ title }}
{{ caller() }}
{%- endmacro %}
{% call render_dialog('Hello World') %}
This is a simple dialog rendered by using a macro and
a call block.
{% endcall %}
It's also possible to pass arguments back to the call block. This makes it
useful as a replacement for loops. Generally speaking, a call block works
exactly like a macro without a name.
Here's an example of how a call block can be used with arguments::
{% macro dump_users(users) -%}
{% endcall %}
Filters
~~~~~~~
Filter sections allow you to apply regular Jinja filters on a block of
template data. Just wrap the code in the special `filter` section::
{% filter upper %}
This text becomes uppercase
{% endfilter %}
Filters that accept arguments can be called like this::
{% filter center(100) %}Center this{% endfilter %}
.. _assignments:
Assignments
~~~~~~~~~~~
Inside code blocks, you can also assign values to variables. Assignments at
top level (outside of blocks, macros or loops) are exported from the template
like top level macros and can be imported by other templates.
Assignments use the `set` tag and can have multiple targets::
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}
.. admonition:: Scoping Behavior
Please keep in mind that it is not possible to set variables inside a
block and have them show up outside of it. This also applies to
loops. The only exception to that rule are if statements which do not
introduce a scope. As a result the following template is not going
to do what you might expect::
{% set iterated = false %}
{% for item in seq %}
{{ item }}
{% set iterated = true %}
{% endfor %}
{% if not iterated %} did not iterate {% endif %}
It is not possible with Jinja syntax to do this. Instead use
alternative constructs like the loop else block or the special `loop`
variable::
{% for item in seq %}
{{ item }}
{% else %}
did not iterate
{% endfor %}
As of version 2.10 more complex use cases can be handled using namespace
objects which allow propagating of changes across scopes::
{% set ns = namespace(found=false) %}
{% for item in items %}
{% if item.check_something() %}
{% set ns.found = true %}
{% endif %}
* {{ item.title }}
{% endfor %}
Found item having something: {{ ns.found }}
Note that the ``obj.attr`` notation in the `set` tag is only allowed for
namespace objects; attempting to assign an attribute on any other object
will raise an exception.
.. versionadded:: 2.10 Added support for namespace objects
Block Assignments
~~~~~~~~~~~~~~~~~
It's possible to use `set` as a block to assign the content of the block to a
variable. This can be used to create multi-line strings, since Jinja doesn't
support Python's triple quotes (``"""``, ``'''``).
Instead of using an equals sign and a value, you only write the variable name,
and everything until ``{% endset %}`` is captured.
.. code-block:: jinja
{% set navigation %}
Downloads
{% endset %}
Filters applied to the variable name will be applied to the block's content.
.. code-block:: jinja
{% set reply | wordwrap %}
You wrote:
{{ message }}
{% endset %}
.. versionadded:: 2.8
.. versionchanged:: 2.10
Block assignment supports filters.
.. _extends:
Extends
~~~~~~~
The `extends` tag can be used to extend one template from another. You can
have multiple `extends` tags in a file, but only one of them may be executed at
a time.
See the section about :ref:`template-inheritance` above.
.. _blocks:
Blocks
~~~~~~
Blocks are used for inheritance and act as both placeholders and replacements
at the same time. They are documented in detail in the
:ref:`template-inheritance` section.
Include
~~~~~~~
The ``include`` tag renders another template and outputs the result into
the current template.
.. code-block:: jinja
{% include 'header.html' %}
Body goes here.
{% include 'footer.html' %}
The included template has access to context of the current template by
default. Use ``without context`` to use a separate context instead.
``with context`` is also valid, but is the default behavior. See
:ref:`import-visibility`.
The included template can ``extend`` another template and override
blocks in that template. However, the current template cannot override
any blocks that the included template outputs.
Use ``ignore missing`` to ignore the statement if the template does not
exist. It must be placed *before* a context visibility statement.
.. code-block:: jinja
{% include "sidebar.html" without context %}
{% include "sidebar.html" ignore missing %}
{% include "sidebar.html" ignore missing with context %}
{% include "sidebar.html" ignore missing without context %}
If a list of templates is given, each will be tried in order until one
is not missing. This can be used with ``ignore missing`` to ignore if
none of the templates exist.
.. code-block:: jinja
{% include ['page_detailed.html', 'page.html'] %}
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}
A variable, with either a template name or template object, can also be
passed to the statement.
.. _import:
Import
~~~~~~
Jinja supports putting often used code into macros. These macros can go into
different templates and get imported from there. This works similarly to the
import statements in Python. It's important to know that imports are cached
and imported templates don't have access to the current template variables,
just the globals by default. For more details about context behavior of
imports and includes, see :ref:`import-visibility`.
There are two ways to import templates. You can import a complete template
into a variable or request specific macros / exported variables from it.
Imagine we have a helper module that renders forms (called `forms.html`)::
{% macro input(name, value='', type='text') -%}
{%- endmacro %}
{%- macro textarea(name, value='', rows=10, cols=40) -%}
{%- endmacro %}
The easiest and most flexible way to access a template's variables
and macros is to import the whole template module into a variable.
That way, you can access the attributes::
{% import 'forms.html' as forms %}
Username
{{ forms.input('username') }}
Password
{{ forms.input('password', type='password') }}
{{ forms.textarea('comment') }}
Alternatively, you can import specific names from a template into the current
namespace::
{% from 'forms.html' import input as input_field, textarea %}
Username
{{ input_field('username') }}
Password
{{ input_field('password', type='password') }}
{{ textarea('comment') }}
Macros and variables starting with one or more underscores are private and
cannot be imported.
.. versionchanged:: 2.4
If a template object was passed to the template context, you can
import from that object.
.. _import-visibility:
Import Context Behavior
-----------------------
By default, included templates are passed the current context and imported
templates are not. The reason for this is that imports, unlike includes,
are cached; as imports are often used just as a module that holds macros.
This behavior can be changed explicitly: by adding `with context`
or `without context` to the import/include directive, the current context
can be passed to the template and caching is disabled automatically.
Here are two examples::
{% from 'forms.html' import input with context %}
{% include 'header.html' without context %}
.. admonition:: Note
In Jinja 2.0, the context that was passed to the included template
did not include variables defined in the template. As a matter of
fact, this did not work::
{% for box in boxes %}
{% include "render_box.html" %}
{% endfor %}
The included template ``render_box.html`` is *not* able to access
`box` in Jinja 2.0. As of Jinja 2.1, ``render_box.html`` *is* able
to do so.
.. _expressions:
Expressions
-----------
Jinja allows basic expressions everywhere. These work very similarly to
regular Python; even if you're not working with Python
you should feel comfortable with it.
Literals
~~~~~~~~
The simplest form of expressions are literals. Literals are representations
for Python objects such as strings and numbers. The following literals exist:
``"Hello World"``
Everything between two double or single quotes is a string. They are
useful whenever you need a string in the template (e.g. as
arguments to function calls and filters, or just to extend or include a
template).
``42`` / ``123_456``
Integers are whole numbers without a decimal part. The '_' character
can be used to separate groups for legibility.
``42.23`` / ``42.1e2`` / ``123_456.789``
Floating point numbers can be written using a '.' as a decimal mark.
They can also be written in scientific notation with an upper or
lower case 'e' to indicate the exponent part. The '_' character can
be used to separate groups for legibility, but cannot be used in the
exponent part.
``['list', 'of', 'objects']``
Everything between two brackets is a list. Lists are useful for storing
sequential data to be iterated over. For example, you can easily
create a list of links using lists and tuples for (and with) a for loop::
{% for href, caption in [('index.html', 'Index'), ('about.html', 'About'),
('downloads.html', 'Downloads')] %}
``('tuple', 'of', 'values')``
Tuples are like lists that cannot be modified ("immutable"). If a tuple
only has one item, it must be followed by a comma (``('1-tuple',)``).
Tuples are usually used to represent items of two or more elements.
See the list example above for more details.
``{'dict': 'of', 'key': 'and', 'value': 'pairs'}``
A dict in Python is a structure that combines keys and values. Keys must
be unique and always have exactly one value. Dicts are rarely used in
templates; they are useful in some rare cases such as the :func:`xmlattr`
filter.
``true`` / ``false``
``true`` is always true and ``false`` is always false.
.. admonition:: Note
The special constants `true`, `false`, and `none` are indeed lowercase.
Because that caused confusion in the past, (`True` used to expand
to an undefined variable that was considered false),
all three can now also be written in title case
(`True`, `False`, and `None`).
However, for consistency, (all Jinja identifiers are lowercase)
you should use the lowercase versions.
Math
~~~~
Jinja allows you to calculate with values. This is rarely useful in templates
but exists for completeness' sake. The following operators are supported:
``+``
Adds two objects together. Usually the objects are numbers, but if both are
strings or lists, you can concatenate them this way. This, however, is not
the preferred way to concatenate strings! For string concatenation, have
a look-see at the ``~`` operator. ``{{ 1 + 1 }}`` is ``2``.
``-``
Subtract the second number from the first one. ``{{ 3 - 2 }}`` is ``1``.
``/``
Divide two numbers. The return value will be a floating point number.
``{{ 1 / 2 }}`` is ``{{ 0.5 }}``.
``//``
Divide two numbers and return the truncated integer result.
``{{ 20 // 7 }}`` is ``2``.
``%``
Calculate the remainder of an integer division. ``{{ 11 % 7 }}`` is ``4``.
``*``
Multiply the left operand with the right one. ``{{ 2 * 2 }}`` would
return ``4``. This can also be used to repeat a string multiple times.
``{{ '=' * 80 }}`` would print a bar of 80 equal signs.
``**``
Raise the left operand to the power of the right operand.
``{{ 2**3 }}`` would return ``8``.
Unlike Python, chained pow is evaluated left to right.
``{{ 3**3**3 }}`` is evaluated as ``(3**3)**3`` in Jinja, but would
be evaluated as ``3**(3**3)`` in Python. Use parentheses in Jinja
to be explicit about what order you want. It is usually preferable
to do extended math in Python and pass the results to ``render``
rather than doing it in the template.
This behavior may be changed in the future to match Python, if it's
possible to introduce an upgrade path.
Comparisons
~~~~~~~~~~~
``==``
Compares two objects for equality.
``!=``
Compares two objects for inequality.
``>``
``true`` if the left hand side is greater than the right hand side.
``>=``
``true`` if the left hand side is greater or equal to the right hand side.
``<``
``true`` if the left hand side is lower than the right hand side.
``<=``
``true`` if the left hand side is lower or equal to the right hand side.
Logic
~~~~~
For ``if`` statements, ``for`` filtering, and ``if`` expressions, it can be
useful to combine multiple expressions.
``and``
For ``x and y``, if ``x`` is false, then the value is ``x``, else ``y``. In
a boolean context, this will be treated as ``True`` if both operands are
truthy.
``or``
For ``x or y``, if ``x`` is true, then the value is ``x``, else ``y``. In a
boolean context, this will be treated as ``True`` if at least one operand is
truthy.
``not``
For ``not x``, if ``x`` is false, then the value is ``True``, else
``False``.
Prefer negating ``is`` and ``in`` using their infix notation:
``foo is not bar`` instead of ``not foo is bar``; ``foo not in bar`` instead
of ``not foo in bar``. All other expressions require prefix notation:
``not (foo and bar).``
``(expr)``
Parentheses group an expression. This is used to change evaluation order, or
to make a long expression easier to read or less ambiguous.
Other Operators
~~~~~~~~~~~~~~~
The following operators are very useful but don't fit into any of the other
two categories:
``in``
Perform a sequence / mapping containment test. Returns true if the left
operand is contained in the right. ``{{ 1 in [1, 2, 3] }}`` would, for
example, return true.
``is``
Performs a :ref:`test `.
``|`` (pipe, vertical bar)
Applies a :ref:`filter `.
``~`` (tilde)
Converts all operands into strings and concatenates them.
``{{ "Hello " ~ name ~ "!" }}`` would return (assuming `name` is set
to ``'John'``) ``Hello John!``.
``()``
Call a callable: ``{{ post.render() }}``. Inside of the parentheses you
can use positional arguments and keyword arguments like in Python:
``{{ post.render(user, full=true) }}``.
``.`` / ``[]``
Get an attribute of an object. (See :ref:`variables`)
.. _if-expression:
If Expression
~~~~~~~~~~~~~
It is also possible to use inline `if` expressions. These are useful in some
situations. For example, you can use this to extend from one template if a
variable is defined, otherwise from the default layout template::
{% extends layout_template if layout_template is defined else 'default.html' %}
The general syntax is `` if else ``.
The `else` part is optional. If not provided, the else block implicitly
evaluates into an :class:`Undefined` object (regardless of what ``undefined``
in the environment is set to):
.. code-block:: jinja
{{ "[{}]".format(page.title) if page.title }}
.. _python-methods:
Python Methods
~~~~~~~~~~~~~~
You can also use any of the methods defined on a variable's type.
The value returned from the method invocation is used as the value of the expression.
Here is an example that uses methods defined on strings (where ``page.title`` is a string):
.. code-block:: text
{{ page.title.capitalize() }}
This works for methods on user-defined types. For example, if variable
``f`` of type ``Foo`` has a method ``bar`` defined on it, you can do the
following:
.. code-block:: text
{{ f.bar(value) }}
Operator methods also work as expected. For example, ``%`` implements
printf-style for strings:
.. code-block:: text
{{ "Hello, %s!" % name }}
Although you should prefer the ``.format`` method for that case (which
is a bit contrived in the context of rendering a template):
.. code-block:: text
{{ "Hello, {}!".format(name) }}
.. _builtin-filters:
List of Builtin Filters
-----------------------
.. py:currentmodule:: jinja-filters
.. jinja:filters:: jinja2.defaults.DEFAULT_FILTERS
.. _builtin-tests:
List of Builtin Tests
---------------------
.. py:currentmodule:: jinja-tests
.. jinja:tests:: jinja2.defaults.DEFAULT_TESTS
.. _builtin-globals:
List of Global Functions
------------------------
The following functions are available in the global scope by default:
.. py:currentmodule:: jinja-globals
.. function:: range([start,] stop[, step])
Return a list containing an arithmetic progression of integers.
``range(i, j)`` returns ``[i, i+1, i+2, ..., j-1]``;
start (!) defaults to ``0``.
When step is given, it specifies the increment (or decrement).
For example, ``range(4)`` and ``range(0, 4, 1)`` return ``[0, 1, 2, 3]``.
The end point is omitted!
These are exactly the valid indices for a list of 4 elements.
This is useful to repeat a template block multiple times, e.g.
to fill a list. Imagine you have 7 users in the list but you want to
render three empty items to enforce a height with CSS::
{% for user in users %}
{{ user.username }}
{% endfor %}
{% for number in range(10 - users|count) %}
...
{% endfor %}
.. function:: lipsum(n=5, html=True, min=20, max=100)
Generates some lorem ipsum for the template. By default, five paragraphs
of HTML are generated with each paragraph between 20 and 100 words.
If html is False, regular text is returned. This is useful to generate simple
contents for layout testing.
.. function:: dict(\**items)
A convenient alternative to dict literals. ``{'foo': 'bar'}`` is the same
as ``dict(foo='bar')``.
.. class:: cycler(\*items)
Cycle through values by yielding them one at a time, then restarting
once the end is reached.
Similar to ``loop.cycle``, but can be used outside loops or across
multiple loops. For example, render a list of folders and files in a
list, alternating giving them "odd" and "even" classes.
.. code-block:: html+jinja
{% set row_class = cycler("odd", "even") %}
{% for folder in folders %}
{{ folder }}
{% endfor %}
{% for file in files %}
{{ file }}
{% endfor %}
:param items: Each positional argument will be yielded in the order
given for each cycle.
.. versionadded:: 2.1
.. property:: current
Return the current item. Equivalent to the item that will be
returned next time :meth:`next` is called.
.. method:: next()
Return the current item, then advance :attr:`current` to the
next item.
.. method:: reset()
Resets the current item to the first item.
.. class:: joiner(sep=', ')
A tiny helper that can be used to "join" multiple sections. A joiner is
passed a string and will return that string every time it's called, except
the first time (in which case it returns an empty string). You can
use this to join things::
{% set pipe = joiner("|") %}
{% if categories %} {{ pipe() }}
Categories: {{ categories|join(", ") }}
{% endif %}
{% if author %} {{ pipe() }}
Author: {{ author() }}
{% endif %}
{% if can_edit %} {{ pipe() }}
Edit
{% endif %}
.. versionadded:: 2.1
.. class:: namespace(...)
Creates a new container that allows attribute assignment using the
``{% set %}`` tag::
{% set ns = namespace() %}
{% set ns.foo = 'bar' %}
The main purpose of this is to allow carrying a value from within a loop
body to an outer scope. Initial values can be provided as a dict, as
keyword arguments, or both (same behavior as Python's `dict` constructor)::
{% set ns = namespace(found=false) %}
{% for item in items %}
{% if item.check_something() %}
{% set ns.found = true %}
{% endif %}
* {{ item.title }}
{% endfor %}
Found item having something: {{ ns.found }}
.. versionadded:: 2.10
.. versionchanged:: 3.2
Namespace attributes can be assigned to in multiple assignment.
Extensions
----------
.. py:currentmodule:: jinja2
The following sections cover the built-in Jinja extensions that may be
enabled by an application. An application could also provide further
extensions not covered by this documentation; in which case there should
be a separate document explaining said :ref:`extensions
`.
.. _i18n-in-templates:
i18n
~~~~
If the :ref:`i18n-extension` is enabled, it's possible to mark text in
the template as translatable. To mark a section as translatable, use a
``trans`` block:
.. code-block:: jinja
{% trans %}Hello, {{ user }}!{% endtrans %}
Inside the block, no statements are allowed, only text and simple
variable tags.
Variable tags can only be a name, not attribute access, filters, or
other expressions. To use an expression, bind it to a name in the
``trans`` tag for use in the block.
.. code-block:: jinja
{% trans user=user.username %}Hello, {{ user }}!{% endtrans %}
To bind more than one expression, separate each with a comma (``,``).
.. code-block:: jinja
{% trans book_title=book.title, author=author.name %}
This is {{ book_title }} by {{ author }}
{% endtrans %}
To pluralize, specify both the singular and plural forms separated by
the ``pluralize`` tag.
.. code-block:: jinja
{% trans count=list|length %}
There is {{ count }} {{ name }} object.
{% pluralize %}
There are {{ count }} {{ name }} objects.
{% endtrans %}
By default, the first variable in a block is used to determine whether
to use singular or plural form. If that isn't correct, specify the
variable used for pluralizing as a parameter to ``pluralize``.
.. code-block:: jinja
{% trans ..., user_count=users|length %}...
{% pluralize user_count %}...{% endtrans %}
When translating blocks of text, whitespace and linebreaks result in
hard to read and error-prone translation strings. To avoid this, a trans
block can be marked as trimmed, which will replace all linebreaks and
the whitespace surrounding them with a single space and remove leading
and trailing whitespace.
.. code-block:: jinja
{% trans trimmed book_title=book.title %}
This is {{ book_title }}.
You should read it!
{% endtrans %}
This results in ``This is %(book_title)s. You should read it!`` in the
translation file.
If trimming is enabled globally, the ``notrimmed`` modifier can be used
to disable it for a block.
.. versionadded:: 2.10
The ``trimmed`` and ``notrimmed`` modifiers have been added.
If the translation depends on the context that the message appears in,
the ``pgettext`` and ``npgettext`` functions take a ``context`` string
as the first argument, which is used to select the appropriate
translation. To specify a context with the ``{% trans %}`` tag, provide
a string as the first token after ``trans``.
.. code-block:: jinja
{% trans "fruit" %}apple{% endtrans %}
{% trans "fruit" trimmed count -%}
1 apple
{%- pluralize -%}
{{ count }} apples
{%- endtrans %}
.. versionadded:: 3.1
A context can be passed to the ``trans`` tag to use ``pgettext`` and
``npgettext``.
It's possible to translate strings in expressions with these functions:
- ``_(message)``: Alias for ``gettext``.
- ``gettext(message)``: Translate a message.
- ``ngettext(singular, plural, n)``: Translate a singular or plural
message based on a count variable.
- ``pgettext(context, message)``: Like ``gettext()``, but picks the
translation based on the context string.
- ``npgettext(context, singular, plural, n)``: Like ``npgettext()``,
but picks the translation based on the context string.
You can print a translated string like this:
.. code-block:: jinja
{{ _("Hello, World!") }}
To use placeholders, use the ``format`` filter.
.. code-block:: jinja
{{ _("Hello, %(user)s!")|format(user=user.username) }}
Always use keyword arguments to ``format``, as other languages may not
use the words in the same order.
If :ref:`newstyle-gettext` calls are activated, using placeholders is
easier. Formatting is part of the ``gettext`` call instead of using the
``format`` filter.
.. sourcecode:: jinja
{{ gettext('Hello World!') }}
{{ gettext('Hello %(name)s!', name='World') }}
{{ ngettext('%(num)d apple', '%(num)d apples', apples|count) }}
The ``ngettext`` function's format string automatically receives the
count as a ``num`` parameter in addition to the given parameters.
Expression Statement
~~~~~~~~~~~~~~~~~~~~
If the expression-statement extension is loaded, a tag called `do` is available
that works exactly like the regular variable expression (``{{ ... }}``); except
it doesn't print anything. This can be used to modify lists::
{% do navigation.append('a string') %}
Loop Controls
~~~~~~~~~~~~~
If the application enables the :ref:`loopcontrols-extension`, it's possible to
use `break` and `continue` in loops. When `break` is reached, the loop is
terminated; if `continue` is reached, the processing is stopped and continues
with the next iteration.
Here's a loop that skips every second item::
{% for user in users %}
{%- if loop.index is even %}{% continue %}{% endif %}
...
{% endfor %}
Likewise, a loop that stops processing after the 10th iteration::
{% for user in users %}
{%- if loop.index >= 10 %}{% break %}{% endif %}
{%- endfor %}
Note that ``loop.index`` starts with 1, and ``loop.index0`` starts with 0
(See: :ref:`for-loop`).
Debug Statement
~~~~~~~~~~~~~~~
If the :ref:`debug-extension` is enabled, a ``{% debug %}`` tag will be
available 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.
.. code-block:: html+jinja
{% debug %}
.. code-block:: text
{'context': {'cycler': ,
...,
'namespace': },
'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
With Statement
~~~~~~~~~~~~~~
.. versionadded:: 2.3
The with statement makes it possible to create a new inner scope.
Variables set within this scope are not visible outside of the scope.
With in a nutshell::
{% with %}
{% set foo = 42 %}
{{ foo }} foo is 42 here
{% endwith %}
foo is not visible here any longer
Because it is common to set variables at the beginning of the scope,
you can do that within the `with` statement. The following two examples
are equivalent::
{% with foo = 42 %}
{{ foo }}
{% endwith %}
{% with %}
{% set foo = 42 %}
{{ foo }}
{% endwith %}
An important note on scoping here. In Jinja versions before 2.9 the
behavior of referencing one variable to another had some unintended
consequences. In particular one variable could refer to another defined
in the same with block's opening statement. This caused issues with the
cleaned up scoping behavior and has since been improved. In particular
in newer Jinja versions the following code always refers to the variable
`a` from outside the `with` block::
{% with a={}, b=a.attribute %}...{% endwith %}
In earlier Jinja versions the `b` attribute would refer to the results of
the first attribute. If you depend on this behavior you can rewrite it to
use the ``set`` tag::
{% with a={} %}
{% set b = a.attribute %}
{% endwith %}
.. admonition:: Extension
In older versions of Jinja (before 2.9) it was required to enable this
feature with an extension. It's now enabled by default.
.. _autoescape-overrides:
Autoescape Overrides
--------------------
.. versionadded:: 2.4
If you want you can activate and deactivate the autoescaping from within
the templates.
Example::
{% autoescape true %}
Autoescaping is active within this block
{% endautoescape %}
{% autoescape false %}
Autoescaping is inactive within this block
{% endautoescape %}
After an `endautoescape` the behavior is reverted to what it was before.
.. admonition:: Extension
In older versions of Jinja (before 2.9) it was required to enable this
feature with an extension. It's now enabled by default.
================================================
FILE: docs/tricks.rst
================================================
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 by 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 -%}
{% block title %}The Page Title{% endblock %}
{% block body %}
This is the page body.
{% 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::
{% for row in rows %}
{{ row }}
{% endfor %}
`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') -%}
...
...
.. _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::
{%- for item in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] if item % 2 == 0 %}
{{ loop.index }} / {{ loop.length }}: {{ item }}
{%- endfor %}
if condition: {{ 1 if foo else 0 }}
"""
)
print(tmpl.render(foo=True))
================================================
FILE: examples/basic/translate.py
================================================
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])
)
================================================
FILE: pyproject.toml
================================================
[project]
name = "Jinja2"
version = "3.2.0.dev"
description = "A very fast and expressive template engine."
readme = "README.md"
license = "BSD-3-Clause"
license-files = ["LICENSE.txt"]
maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Text Processing :: Markup :: HTML",
"Typing :: Typed",
]
requires-python = ">=3.10"
dependencies = ["MarkupSafe>=3.0"]
[project.urls]
Donate = "https://palletsprojects.com/donate"
Documentation = "https://jinja.palletsprojects.com/"
Changes = "https://jinja.palletsprojects.com/page/changes/"
Source = "https://github.com/pallets/jinja/"
Chat = "https://discord.gg/pallets"
[project.optional-dependencies]
i18n = ["Babel>=2.17"]
[dependency-groups]
dev = [
"ruff",
"tox",
"tox-uv",
]
docs = [
"pallets-sphinx-themes",
"sphinx",
"sphinxcontrib-log-cabinet",
]
docs-auto = [
"sphinx-autobuild",
]
gha-update = [
"gha-update ; python_full_version >= '3.12'",
]
pre-commit = [
"pre-commit",
"pre-commit-uv",
]
tests = [
"pytest",
"pytest-timeout",
"trio"
]
typing = [
"mypy",
"pyright",
"pytest",
]
[build-system]
requires = ["flit_core<4"]
build-backend = "flit_core.buildapi"
[tool.flit.module]
name = "jinja2"
[tool.flit.sdist]
include = [
"docs/",
"examples/",
"tests/",
"CHANGES.rst",
"uv.lock"
]
exclude = [
"docs/_build/",
]
[tool.uv]
default-groups = ["dev", "pre-commit", "tests", "typing"]
[tool.pytest.ini_options]
testpaths = ["tests"]
filterwarnings = [
"error",
]
[tool.coverage.run]
branch = true
source = ["jinja2", "tests"]
[tool.coverage.paths]
source = ["src", "*/site-packages"]
[tool.coverage.report]
exclude_also = [
"if t.TYPE_CHECKING",
"raise NotImplementedError",
": \\.{3}",
]
[tool.mypy]
python_version = "3.10"
files = ["src"]
show_error_codes = true
pretty = true
strict = true
[tool.pyright]
pythonVersion = "3.10"
include = ["src"]
typeCheckingMode = "standard"
[tool.ruff]
src = ["src"]
fix = true
show-fixes = true
output-format = "full"
[tool.ruff.lint]
select = [
"B", # flake8-bugbear
"E", # pycodestyle error
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"W", # pycodestyle warning
]
ignore = [
"UP038", # keep isinstance tuple
]
[tool.ruff.lint.isort]
force-single-line = true
order-by-type = false
[tool.gha-update]
tag-only = [
"slsa-framework/slsa-github-generator",
]
[tool.tox]
env_list = [
"py3.13", "py3.12", "py3.11", "py3.10",
"pypy3.11",
"style",
"typing",
"docs",
]
[tool.tox.env_run_base]
description = "pytest on latest dependency versions"
runner = "uv-venv-lock-runner"
package = "wheel"
wheel_build_env = ".pkg"
constrain_package_deps = true
use_frozen_constraints = true
dependency_groups = ["tests"]
commands = [[
"pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}",
{replace = "posargs", default = [], extend = true},
]]
[tool.tox.env.style]
description = "run all pre-commit hooks on all files"
dependency_groups = ["pre-commit"]
skip_install = true
commands = [["pre-commit", "run", "--all-files"]]
[tool.tox.env.typing]
description = "run static type checkers"
dependency_groups = ["typing"]
commands = [
["mypy"],
]
[tool.tox.env.docs]
description = "build docs"
dependency_groups = ["docs"]
commands = [["sphinx-build", "-E", "-W", "-b", "dirhtml", "docs", "docs/_build/dirhtml"]]
[tool.tox.env.docs-auto]
description = "continuously rebuild docs and start a local server"
dependency_groups = ["docs", "docs-auto"]
commands = [["sphinx-autobuild", "-W", "-b", "dirhtml", "--watch", "src", "docs", "docs/_build/dirhtml"]]
[tool.tox.env.update-actions]
description = "update GitHub Actions pins"
labels = ["update"]
dependency_groups = ["gha-update"]
skip_install = true
commands = [["gha-update"]]
[tool.tox.env.update-pre_commit]
description = "update pre-commit pins"
labels = ["update"]
dependency_groups = ["pre-commit"]
skip_install = true
commands = [["pre-commit", "autoupdate", "--freeze", "-j4"]]
[tool.tox.env.update-requirements]
description = "update uv lock"
labels = ["update"]
dependency_groups = []
no_default_groups = true
skip_install = true
commands = [["uv", "lock", {replace = "posargs", default = ["-U"], extend = true}]]
================================================
FILE: scripts/generate_identifier_pattern.py
================================================
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 _, g in itertools.groupby(enumerate(data), lambda x: ord(x[1]) - x[0]):
lb = list(g)
yield lb[0][1], lb[-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("# generated by scripts/generate_identifier_pattern.py")
f.write(f"# Python {sys.version_info[0]}.{sys.version_info[1]}\n")
f.write("import re\n\n")
f.write("pattern = re.compile(\n")
f.write(f' r"[\\w{pattern}]+" # noqa: B950\n')
f.write(")\n")
if __name__ == "__main__":
main()
================================================
FILE: src/jinja2/__init__.py
================================================
"""Jinja is a template engine written in pure Python. It provides a
non-XML syntax that supports inline expressions and an optional
sandboxed environment.
"""
from __future__ import annotations
import typing as t
from .bccache import BytecodeCache as BytecodeCache
from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
from .environment import Environment as Environment
from .environment import Template as Template
from .exceptions import TemplateAssertionError as TemplateAssertionError
from .exceptions import TemplateError as TemplateError
from .exceptions import TemplateNotFound as TemplateNotFound
from .exceptions import TemplateRuntimeError as TemplateRuntimeError
from .exceptions import TemplatesNotFound as TemplatesNotFound
from .exceptions import TemplateSyntaxError as TemplateSyntaxError
from .exceptions import UndefinedError as UndefinedError
from .loaders import BaseLoader as BaseLoader
from .loaders import ChoiceLoader as ChoiceLoader
from .loaders import DictLoader as DictLoader
from .loaders import FileSystemLoader as FileSystemLoader
from .loaders import FunctionLoader as FunctionLoader
from .loaders import ModuleLoader as ModuleLoader
from .loaders import PackageLoader as PackageLoader
from .loaders import PrefixLoader as PrefixLoader
from .runtime import ChainableUndefined as ChainableUndefined
from .runtime import DebugUndefined as DebugUndefined
from .runtime import make_logging_undefined as make_logging_undefined
from .runtime import StrictUndefined as StrictUndefined
from .runtime import Undefined as Undefined
from .utils import clear_caches as clear_caches
from .utils import is_undefined as is_undefined
from .utils import pass_context as pass_context
from .utils import pass_environment as pass_environment
from .utils import pass_eval_context as pass_eval_context
from .utils import select_autoescape as select_autoescape
def __getattr__(name: str) -> t.Any:
if name == "__version__":
import importlib.metadata
import warnings
warnings.warn(
"The `__version__` attribute is deprecated and will be removed in"
" Jinja 3.3. Use feature detection or"
' `importlib.metadata.version("jinja2")` instead.',
DeprecationWarning,
stacklevel=2,
)
return importlib.metadata.version("jinja2")
raise AttributeError(name)
================================================
FILE: src/jinja2/_identifier.py
================================================
# generated by scripts/generate_identifier_pattern.py for Python 3.10
import re
pattern = re.compile(
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍୕-ୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣඁ-ඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᪿᫀᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧ꠬ꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧𐺫𐺬-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑇎𑇏𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑤰-𑤵𑤷𑤸𑤻-𑤾𑥀𑥂𑥃𑧑-𑧗𑧚-𑧠𑧤𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽏𖽑-𖾇𖾏-𖾒𖿤𖿰𖿱𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞀪𞄰-𞄶𞋬-𞣐𞋯-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
)
================================================
FILE: src/jinja2/async_utils.py
================================================
import inspect
import typing as t
from functools import WRAPPER_ASSIGNMENTS
from functools import wraps
from .utils import _PassArg
from .utils import pass_eval_context
if t.TYPE_CHECKING:
import typing_extensions as te
V = t.TypeVar("V")
def async_variant(normal_func): # type: ignore
def decorator(async_func): # type: ignore
pass_arg = _PassArg.from_obj(normal_func)
need_eval_context = pass_arg is None
if pass_arg is _PassArg.environment:
def is_async(args: t.Any) -> bool:
return t.cast(bool, args[0].is_async)
else:
def is_async(args: t.Any) -> bool:
return t.cast(bool, args[0].environment.is_async)
# Take the doc and annotations from the sync function, but the
# name from the async function. Pallets-Sphinx-Themes
# build_function_directive expects __wrapped__ to point to the
# sync function.
async_func_attrs = ("__module__", "__name__", "__qualname__")
normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs))
@wraps(normal_func, assigned=normal_func_attrs)
@wraps(async_func, assigned=async_func_attrs, updated=())
def wrapper(*args, **kwargs): # type: ignore
b = is_async(args)
if need_eval_context:
args = args[1:]
if b:
return async_func(*args, **kwargs)
return normal_func(*args, **kwargs)
if need_eval_context:
wrapper = pass_eval_context(wrapper)
wrapper.jinja_async_variant = True # type: ignore[attr-defined]
return wrapper
return decorator
_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)}
async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V":
# Avoid a costly call to isawaitable
if type(value) in _common_primitives:
return t.cast("V", value)
if inspect.isawaitable(value):
return await t.cast("t.Awaitable[V]", value)
return value
class _IteratorToAsyncIterator(t.Generic[V]):
def __init__(self, iterator: "t.Iterator[V]"):
self._iterator = iterator
def __aiter__(self) -> "te.Self":
return self
async def __anext__(self) -> V:
try:
return next(self._iterator)
except StopIteration as e:
raise StopAsyncIteration(e.value) from e
def auto_aiter(
iterable: "t.AsyncIterable[V] | t.Iterable[V]",
) -> "t.AsyncIterator[V]":
if hasattr(iterable, "__aiter__"):
return iterable.__aiter__()
else:
return _IteratorToAsyncIterator(iter(iterable))
async def auto_to_list(
value: "t.AsyncIterable[V] | t.Iterable[V]",
) -> list["V"]:
return [x async for x in auto_aiter(value)]
================================================
FILE: src/jinja2/bccache.py
================================================
"""The optional bytecode cache system. This is useful if you have very
complex template situations and the compilation of all those templates
slows down your application too much.
Situations where this is useful are often forking web applications that
are initialized on the first request.
"""
import errno
import fnmatch
import marshal
import os
import pickle
import stat
import sys
import tempfile
import typing as t
from hashlib import sha1
from io import BytesIO
from types import CodeType
if t.TYPE_CHECKING:
import typing_extensions as te
from .environment import Environment
class _MemcachedClient(te.Protocol):
def get(self, key: str) -> bytes: ...
def set(self, key: str, value: bytes, timeout: int | None = None) -> None: ...
bc_version = 5
# Magic bytes to identify Jinja bytecode cache files. Contains the
# Python major and minor version to avoid loading incompatible bytecode
# if a project upgrades its Python version.
bc_magic = (
b"j2"
+ pickle.dumps(bc_version, 2)
+ pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2)
)
class Bucket:
"""Buckets are used to store the bytecode for one template. It's created
and initialized by the bytecode cache and passed to the loading functions.
The buckets get an internal checksum from the cache assigned and use this
to automatically reject outdated cache material. Individual bytecode
cache subclasses don't have to care about cache invalidation.
"""
def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
self.environment = environment
self.key = key
self.checksum = checksum
self.reset()
def reset(self) -> None:
"""Resets the bucket (unloads the bytecode)."""
self.code: CodeType | None = None
def load_bytecode(self, f: t.BinaryIO) -> None:
"""Loads bytecode from a file or file like object."""
# make sure the magic header is correct
magic = f.read(len(bc_magic))
if magic != bc_magic:
self.reset()
return
# the source code of the file changed, we need to reload
checksum = pickle.load(f)
if self.checksum != checksum:
self.reset()
return
# if marshal_load fails then we need to reload
try:
self.code = marshal.load(f)
except (EOFError, ValueError, TypeError):
self.reset()
return
def write_bytecode(self, f: t.IO[bytes]) -> None:
"""Dump the bytecode into the file or file like object passed."""
if self.code is None:
raise TypeError("can't write empty bucket")
f.write(bc_magic)
pickle.dump(self.checksum, f, 2)
marshal.dump(self.code, f)
def bytecode_from_string(self, string: bytes) -> None:
"""Load bytecode from bytes."""
self.load_bytecode(BytesIO(string))
def bytecode_to_string(self) -> bytes:
"""Return the bytecode as bytes."""
out = BytesIO()
self.write_bytecode(out)
return out.getvalue()
class BytecodeCache:
"""To implement your own bytecode cache you have to subclass this class
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
these methods are passed a :class:`~jinja2.bccache.Bucket`.
A very basic bytecode cache that saves the bytecode on the file system::
from os import path
class MyCache(BytecodeCache):
def __init__(self, directory):
self.directory = directory
def load_bytecode(self, bucket):
filename = path.join(self.directory, bucket.key)
if path.exists(filename):
with open(filename, 'rb') as f:
bucket.load_bytecode(f)
def dump_bytecode(self, bucket):
filename = path.join(self.directory, bucket.key)
with open(filename, 'wb') as f:
bucket.write_bytecode(f)
A more advanced version of a filesystem based bytecode cache is part of
Jinja.
"""
def load_bytecode(self, bucket: Bucket) -> None:
"""Subclasses have to override this method to load bytecode into a
bucket. If they are not able to find code in the cache for the
bucket, it must not do anything.
"""
raise NotImplementedError()
def dump_bytecode(self, bucket: Bucket) -> None:
"""Subclasses have to override this method to write the bytecode
from a bucket back to the cache. If it unable to do so it must not
fail silently but raise an exception.
"""
raise NotImplementedError()
def clear(self) -> None:
"""Clears the cache. This method is not used by Jinja but should be
implemented to allow applications to clear the bytecode cache used
by a particular environment.
"""
def get_cache_key(self, name: str, filename: str | None = None) -> str:
"""Returns the unique hash key for this template name."""
hash = sha1(name.encode("utf-8"))
if filename is not None:
hash.update(f"|{filename}".encode())
return hash.hexdigest()
def get_source_checksum(self, source: str) -> str:
"""Returns a checksum for the source."""
return sha1(source.encode("utf-8")).hexdigest()
def get_bucket(
self,
environment: "Environment",
name: str,
filename: str | None,
source: str,
) -> Bucket:
"""Return a cache bucket for the given template. All arguments are
mandatory but filename may be `None`.
"""
key = self.get_cache_key(name, filename)
checksum = self.get_source_checksum(source)
bucket = Bucket(environment, key, checksum)
self.load_bytecode(bucket)
return bucket
def set_bucket(self, bucket: Bucket) -> None:
"""Put the bucket into the cache."""
self.dump_bytecode(bucket)
class FileSystemBytecodeCache(BytecodeCache):
"""A bytecode cache that stores bytecode on the filesystem. It accepts
two arguments: The directory where the cache items are stored and a
pattern string that is used to build the filename.
If no directory is specified a default cache directory is selected. On
Windows the user's temp directory is used, on UNIX systems a directory
is created for the user in the system temp directory.
The pattern can be used to have multiple separate caches operate on the
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
is replaced with the cache key.
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
This bytecode cache supports clearing of the cache using the clear method.
"""
def __init__(
self, directory: str | None = None, pattern: str = "__jinja2_%s.cache"
) -> None:
if directory is None:
directory = self._get_default_cache_dir()
self.directory = directory
self.pattern = pattern
def _get_default_cache_dir(self) -> str:
def _unsafe_dir() -> "te.NoReturn":
raise RuntimeError(
"Cannot determine safe temp directory. You "
"need to explicitly provide one."
)
tmpdir = tempfile.gettempdir()
# On windows the temporary directory is used specific unless
# explicitly forced otherwise. We can just use that.
if os.name == "nt":
return tmpdir
if not hasattr(os, "getuid"):
_unsafe_dir()
dirname = f"_jinja2-cache-{os.getuid()}"
actual_dir = os.path.join(tmpdir, dirname)
try:
os.mkdir(actual_dir, stat.S_IRWXU)
except OSError as e:
if e.errno != errno.EEXIST:
raise
try:
os.chmod(actual_dir, stat.S_IRWXU)
actual_dir_stat = os.lstat(actual_dir)
if (
actual_dir_stat.st_uid != os.getuid()
or not stat.S_ISDIR(actual_dir_stat.st_mode)
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
):
_unsafe_dir()
except OSError as e:
if e.errno != errno.EEXIST:
raise
actual_dir_stat = os.lstat(actual_dir)
if (
actual_dir_stat.st_uid != os.getuid()
or not stat.S_ISDIR(actual_dir_stat.st_mode)
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
):
_unsafe_dir()
return actual_dir
def _get_cache_filename(self, bucket: Bucket) -> str:
return os.path.join(self.directory, self.pattern % (bucket.key,))
def load_bytecode(self, bucket: Bucket) -> None:
filename = self._get_cache_filename(bucket)
# Don't test for existence before opening the file, since the
# file could disappear after the test before the open.
try:
f = open(filename, "rb")
except (FileNotFoundError, IsADirectoryError, PermissionError):
# PermissionError can occur on Windows when an operation is
# in progress, such as calling clear().
return
with f:
bucket.load_bytecode(f)
def dump_bytecode(self, bucket: Bucket) -> None:
# Write to a temporary file, then rename to the real name after
# writing. This avoids another process reading the file before
# it is fully written.
name = self._get_cache_filename(bucket)
f = tempfile.NamedTemporaryFile(
mode="wb",
dir=os.path.dirname(name),
prefix=os.path.basename(name),
suffix=".tmp",
delete=False,
)
def remove_silent() -> None:
try:
os.remove(f.name)
except OSError:
# Another process may have called clear(). On Windows,
# another program may be holding the file open.
pass
try:
with f:
bucket.write_bytecode(f)
except BaseException:
remove_silent()
raise
try:
os.replace(f.name, name)
except OSError:
# Another process may have called clear(). On Windows,
# another program may be holding the file open.
remove_silent()
except BaseException:
remove_silent()
raise
def clear(self) -> None:
# imported lazily here because google app-engine doesn't support
# write access on the file system and the function does not exist
# normally.
from os import remove
files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
for filename in files:
try:
remove(os.path.join(self.directory, filename))
except OSError:
pass
class MemcachedBytecodeCache(BytecodeCache):
"""This class implements a bytecode cache that uses a memcache cache for
storing the information. It does not enforce a specific memcache library
(tummy's memcache or cmemcache) but will accept any class that provides
the minimal interface required.
Libraries compatible with this class:
- `cachelib `_
- `python-memcached `_
(Unfortunately the django cache interface is not compatible because it
does not support storing binary data, only text. You can however pass
the underlying cache client to the bytecode cache which is available
as `django.core.cache.cache._client`.)
The minimal interface for the client passed to the constructor is this:
.. class:: MinimalClientInterface
.. method:: set(key, value[, timeout])
Stores the bytecode in the cache. `value` is a string and
`timeout` the timeout of the key. If timeout is not provided
a default timeout or no timeout should be assumed, if it's
provided it's an integer with the number of seconds the cache
item should exist.
.. method:: get(key)
Returns the value for the cache key. If the item does not
exist in the cache the return value must be `None`.
The other arguments to the constructor are the prefix for all keys that
is added before the actual cache key and the timeout for the bytecode in
the cache system. We recommend a high (or no) timeout.
This bytecode cache does not support clearing of used items in the cache.
The clear method is a no-operation function.
.. versionadded:: 2.7
Added support for ignoring memcache errors through the
`ignore_memcache_errors` parameter.
"""
def __init__(
self,
client: "_MemcachedClient",
prefix: str = "jinja2/bytecode/",
timeout: int | None = None,
ignore_memcache_errors: bool = True,
):
self.client = client
self.prefix = prefix
self.timeout = timeout
self.ignore_memcache_errors = ignore_memcache_errors
def load_bytecode(self, bucket: Bucket) -> None:
try:
code = self.client.get(self.prefix + bucket.key)
except Exception:
if not self.ignore_memcache_errors:
raise
else:
bucket.bytecode_from_string(code)
def dump_bytecode(self, bucket: Bucket) -> None:
key = self.prefix + bucket.key
value = bucket.bytecode_to_string()
try:
if self.timeout is not None:
self.client.set(key, value, self.timeout)
else:
self.client.set(key, value)
except Exception:
if not self.ignore_memcache_errors:
raise
================================================
FILE: src/jinja2/compiler.py
================================================
"""Compiles nodes from the parser into Python code."""
import typing as t
from contextlib import contextmanager
from functools import update_wrapper
from io import StringIO
from itertools import chain
from keyword import iskeyword as is_python_keyword
from markupsafe import escape
from markupsafe import Markup
from . import nodes
from .exceptions import TemplateAssertionError
from .idtracking import Symbols
from .idtracking import VAR_LOAD_ALIAS
from .idtracking import VAR_LOAD_PARAMETER
from .idtracking import VAR_LOAD_RESOLVE
from .idtracking import VAR_LOAD_UNDEFINED
from .nodes import EvalContext
from .optimizer import Optimizer
from .utils import _PassArg
from .utils import concat
from .visitor import NodeVisitor
if t.TYPE_CHECKING:
import typing_extensions as te
from .environment import Environment
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
operators = {
"eq": "==",
"ne": "!=",
"gt": ">",
"gteq": ">=",
"lt": "<",
"lteq": "<=",
"in": "in",
"notin": "not in",
}
def optimizeconst(f: F) -> F:
def new_func(
self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any
) -> t.Any:
# Only optimize if the frame is not volatile
if self.optimizer is not None and not frame.eval_ctx.volatile:
new_node = self.optimizer.visit(node, frame.eval_ctx)
if new_node != node:
return self.visit(new_node, frame)
return f(self, node, frame, **kwargs)
return update_wrapper(new_func, f) # type: ignore[return-value]
def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]:
@optimizeconst
def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None:
if (
self.environment.sandboxed and op in self.environment.intercepted_binops # type: ignore
):
self.write(f"environment.call_binop(context, {op!r}, ")
self.visit(node.left, frame)
self.write(", ")
self.visit(node.right, frame)
else:
self.write("(")
self.visit(node.left, frame)
self.write(f" {op} ")
self.visit(node.right, frame)
self.write(")")
return visitor
def _make_unop(
op: str,
) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]:
@optimizeconst
def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None:
if (
self.environment.sandboxed and op in self.environment.intercepted_unops # type: ignore
):
self.write(f"environment.call_unop(context, {op!r}, ")
self.visit(node.node, frame)
else:
self.write("(" + op)
self.visit(node.node, frame)
self.write(")")
return visitor
def generate(
node: nodes.Template,
environment: "Environment",
name: str | None,
filename: str | None,
stream: t.TextIO | None = None,
defer_init: bool = False,
optimized: bool = True,
) -> str | None:
"""Generate the python source for a node tree."""
if not isinstance(node, nodes.Template):
raise TypeError("Can't compile non template nodes")
generator = environment.code_generator_class(
environment, name, filename, stream, defer_init, optimized
)
generator.visit(node)
if stream is None:
return generator.stream.getvalue() # type: ignore
return None
def has_safe_repr(value: t.Any) -> bool:
"""Does the node have a safe representation?"""
if value is None or value is NotImplemented or value is Ellipsis:
return True
if type(value) in {bool, int, float, complex, range, str, Markup}:
return True
if type(value) in {tuple, list, set, frozenset}:
return all(has_safe_repr(v) for v in value)
if type(value) is dict: # noqa E721
return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())
return False
def find_undeclared(nodes: t.Iterable[nodes.Node], names: t.Iterable[str]) -> set[str]:
"""Check if the names passed are accessed undeclared. The return value
is a set of all the undeclared names from the sequence of names found.
"""
visitor = UndeclaredNameVisitor(names)
try:
for node in nodes:
visitor.visit(node)
except VisitorExit:
pass
return visitor.undeclared
class MacroRef:
def __init__(self, node: nodes.Macro | nodes.CallBlock) -> None:
self.node = node
self.accesses_caller = False
self.accesses_kwargs = False
self.accesses_varargs = False
class Frame:
"""Holds compile time information for us."""
def __init__(
self,
eval_ctx: EvalContext,
parent: t.Optional["Frame"] = None,
level: int | None = None,
) -> None:
self.eval_ctx = eval_ctx
# the parent of this frame
self.parent = parent
if parent is None:
self.symbols = Symbols(level=level)
# in some dynamic inheritance situations the compiler needs to add
# write tests around output statements.
self.require_output_check = False
# inside some tags we are using a buffer rather than yield statements.
# this for example affects {% filter %} or {% macro %}. If a frame
# is buffered this variable points to the name of the list used as
# buffer.
self.buffer: str | None = None
# the name of the block we're in, otherwise None.
self.block: str | None = None
else:
self.symbols = Symbols(parent.symbols, level=level)
self.require_output_check = parent.require_output_check
self.buffer = parent.buffer
self.block = parent.block
# a toplevel frame is the root + soft frames such as if conditions.
self.toplevel = False
# the root frame is basically just the outermost frame, so no if
# conditions. This information is used to optimize inheritance
# situations.
self.rootlevel = False
# variables set inside of loops and blocks should not affect outer frames,
# but they still needs to be kept track of as part of the active context.
self.loop_frame = False
self.block_frame = False
# track whether the frame is being used in an if-statement or conditional
# expression as it determines which errors should be raised during runtime
# or compile time.
self.soft_frame = False
def copy(self) -> "te.Self":
"""Create a copy of the current one."""
rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__)
rv.symbols = self.symbols.copy()
return rv
def inner(self, isolated: bool = False) -> "Frame":
"""Return an inner frame."""
if isolated:
return Frame(self.eval_ctx, level=self.symbols.level + 1)
return Frame(self.eval_ctx, self)
def soft(self) -> "te.Self":
"""Return a soft frame. A soft frame may not be modified as
standalone thing as it shares the resources with the frame it
was created of, but it's not a rootlevel frame any longer.
This is only used to implement if-statements and conditional
expressions.
"""
rv = self.copy()
rv.rootlevel = False
rv.soft_frame = True
return rv
__copy__ = copy
class VisitorExit(RuntimeError):
"""Exception used by the `UndeclaredNameVisitor` to signal a stop."""
class DependencyFinderVisitor(NodeVisitor):
"""A visitor that collects filter and test calls."""
def __init__(self) -> None:
self.filters: set[str] = set()
self.tests: set[str] = set()
def visit_Filter(self, node: nodes.Filter) -> None:
self.generic_visit(node)
self.filters.add(node.name)
def visit_Test(self, node: nodes.Test) -> None:
self.generic_visit(node)
self.tests.add(node.name)
def visit_Block(self, node: nodes.Block) -> None:
"""Stop visiting at blocks."""
class UndeclaredNameVisitor(NodeVisitor):
"""A visitor that checks if a name is accessed without being
declared. This is different from the frame visitor as it will
not stop at closure frames.
"""
def __init__(self, names: t.Iterable[str]) -> None:
self.names = set(names)
self.undeclared: set[str] = set()
def visit_Name(self, node: nodes.Name) -> None:
if node.ctx == "load" and node.name in self.names:
self.undeclared.add(node.name)
if self.undeclared == self.names:
raise VisitorExit()
else:
self.names.discard(node.name)
def visit_Block(self, node: nodes.Block) -> None:
"""Stop visiting a blocks."""
class CompilerExit(Exception):
"""Raised if the compiler encountered a situation where it just
doesn't make sense to further process the code. Any block that
raises such an exception is not further processed.
"""
class CodeGenerator(NodeVisitor):
def __init__(
self,
environment: "Environment",
name: str | None,
filename: str | None,
stream: t.TextIO | None = None,
defer_init: bool = False,
optimized: bool = True,
) -> None:
if stream is None:
stream = StringIO()
self.environment = environment
self.name = name
self.filename = filename
self.stream = stream
self.created_block_context = False
self.defer_init = defer_init
self.optimizer: Optimizer | None = None
if optimized:
self.optimizer = Optimizer(environment)
# aliases for imports
self.import_aliases: dict[str, str] = {}
# a registry for all blocks. Because blocks are moved out
# into the global python scope they are registered here
self.blocks: dict[str, nodes.Block] = {}
# the number of extends statements so far
self.extends_so_far = 0
# some templates have a rootlevel extends. In this case we
# can safely assume that we're a child template and do some
# more optimizations.
self.has_known_extends = False
# the current line number
self.code_lineno = 1
# registry of all filters and tests (global, not block local)
self.tests: dict[str, str] = {}
self.filters: dict[str, str] = {}
# the debug information
self.debug_info: list[tuple[int, int]] = []
self._write_debug_info: int | None = None
# the number of new lines before the next write()
self._new_lines = 0
# the line number of the last written statement
self._last_line = 0
# true if nothing was written so far.
self._first_write = True
# used by the `temporary_identifier` method to get new
# unique, temporary identifier
self._last_identifier = 0
# the current indentation
self._indentation = 0
# Tracks toplevel assignments
self._assign_stack: list[set[str]] = []
# Tracks parameter definition blocks
self._param_def_block: list[set[str]] = []
# Tracks the current context.
self._context_reference_stack = ["context"]
@property
def optimized(self) -> bool:
return self.optimizer is not None
# -- Various compilation helpers
def fail(self, msg: str, lineno: int) -> "te.NoReturn":
"""Fail with a :exc:`TemplateAssertionError`."""
raise TemplateAssertionError(msg, lineno, self.name, self.filename)
def temporary_identifier(self) -> str:
"""Get a new unique identifier."""
self._last_identifier += 1
return f"t_{self._last_identifier}"
def buffer(self, frame: Frame) -> None:
"""Enable buffering for the frame from that point onwards."""
frame.buffer = self.temporary_identifier()
self.writeline(f"{frame.buffer} = []")
def return_buffer_contents(
self, frame: Frame, force_unescaped: bool = False
) -> None:
"""Return the buffer contents of the frame."""
if not force_unescaped:
if frame.eval_ctx.volatile:
self.writeline("if context.eval_ctx.autoescape:")
self.indent()
self.writeline(f"return Markup(concat({frame.buffer}))")
self.outdent()
self.writeline("else:")
self.indent()
self.writeline(f"return concat({frame.buffer})")
self.outdent()
return
elif frame.eval_ctx.autoescape:
self.writeline(f"return Markup(concat({frame.buffer}))")
return
self.writeline(f"return concat({frame.buffer})")
def indent(self) -> None:
"""Indent by one."""
self._indentation += 1
def outdent(self, step: int = 1) -> None:
"""Outdent by step."""
self._indentation -= step
def start_write(self, frame: Frame, node: nodes.Node | None = None) -> None:
"""Yield or write into the frame buffer."""
if frame.buffer is None:
self.writeline("yield ", node)
else:
self.writeline(f"{frame.buffer}.append(", node)
def end_write(self, frame: Frame) -> None:
"""End the writing process started by `start_write`."""
if frame.buffer is not None:
self.write(")")
def simple_write(
self, s: str, frame: Frame, node: nodes.Node | None = None
) -> None:
"""Simple shortcut for start_write + write + end_write."""
self.start_write(frame, node)
self.write(s)
self.end_write(frame)
def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None:
"""Visit a list of nodes as block in a frame. If the current frame
is no buffer a dummy ``if 0: yield None`` is written automatically.
"""
try:
self.writeline("pass")
for node in nodes:
self.visit(node, frame)
except CompilerExit:
pass
def write(self, x: str) -> None:
"""Write a string into the output stream."""
if self._new_lines:
if not self._first_write:
self.stream.write("\n" * self._new_lines)
self.code_lineno += self._new_lines
if self._write_debug_info is not None:
self.debug_info.append((self._write_debug_info, self.code_lineno))
self._write_debug_info = None
self._first_write = False
self.stream.write(" " * self._indentation)
self._new_lines = 0
self.stream.write(x)
def writeline(self, x: str, node: nodes.Node | None = None, extra: int = 0) -> None:
"""Combination of newline and write."""
self.newline(node, extra)
self.write(x)
def newline(self, node: nodes.Node | None = None, extra: int = 0) -> None:
"""Add one or more newlines before the next write."""
self._new_lines = max(self._new_lines, 1 + extra)
if node is not None and node.lineno != self._last_line:
self._write_debug_info = node.lineno
self._last_line = node.lineno
def signature(
self,
node: nodes.Call | nodes.Filter | nodes.Test,
frame: Frame,
extra_kwargs: t.Mapping[str, t.Any] | None = None,
) -> None:
"""Writes a function call to the stream for the current node.
A leading comma is added automatically. The extra keyword
arguments may not include python keywords otherwise a syntax
error could occur. The extra keyword arguments should be given
as python dict.
"""
# if any of the given keyword arguments is a python keyword
# we have to make sure that no invalid call is created.
kwarg_workaround = any(
is_python_keyword(t.cast(str, k))
for k in chain((x.key for x in node.kwargs), extra_kwargs or ())
)
for arg in node.args:
self.write(", ")
self.visit(arg, frame)
if not kwarg_workaround:
for kwarg in node.kwargs:
self.write(", ")
self.visit(kwarg, frame)
if extra_kwargs is not None:
for key, value in extra_kwargs.items():
self.write(f", {key}={value}")
if node.dyn_args:
self.write(", *")
self.visit(node.dyn_args, frame)
if kwarg_workaround:
if node.dyn_kwargs is not None:
self.write(", **dict({")
else:
self.write(", **{")
for kwarg in node.kwargs:
self.write(f"{kwarg.key!r}: ")
self.visit(kwarg.value, frame)
self.write(", ")
if extra_kwargs is not None:
for key, value in extra_kwargs.items():
self.write(f"{key!r}: {value}, ")
if node.dyn_kwargs is not None:
self.write("}, **")
self.visit(node.dyn_kwargs, frame)
self.write(")")
else:
self.write("}")
elif node.dyn_kwargs is not None:
self.write(", **")
self.visit(node.dyn_kwargs, frame)
def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None:
"""Find all filter and test names used in the template and
assign them to variables in the compiled namespace. Checking
that the names are registered with the environment is done when
compiling the Filter and Test nodes. If the node is in an If or
CondExpr node, the check is done at runtime instead.
.. versionchanged:: 3.0
Filters and tests in If and CondExpr nodes are checked at
runtime instead of compile time.
"""
visitor = DependencyFinderVisitor()
for node in nodes:
visitor.visit(node)
for id_map, names, dependency in (
(self.filters, visitor.filters, "filters"),
(
self.tests,
visitor.tests,
"tests",
),
):
for name in sorted(names):
if name not in id_map:
id_map[name] = self.temporary_identifier()
# add check during runtime that dependencies used inside of executed
# blocks are defined, as this step may be skipped during compile time
self.writeline("try:")
self.indent()
self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]")
self.outdent()
self.writeline("except KeyError:")
self.indent()
self.writeline("@internalcode")
self.writeline(f"def {id_map[name]}(*unused):")
self.indent()
self.writeline(
f'raise TemplateRuntimeError("No {dependency[:-1]}'
f' named {name!r} found.")'
)
self.outdent()
self.outdent()
def enter_frame(self, frame: Frame) -> None:
undefs = []
for target, (action, param) in frame.symbols.loads.items():
if action == VAR_LOAD_PARAMETER:
pass
elif action == VAR_LOAD_RESOLVE:
self.writeline(f"{target} = {self.get_resolve_func()}({param!r})")
elif action == VAR_LOAD_ALIAS:
self.writeline(f"{target} = {param}")
elif action == VAR_LOAD_UNDEFINED:
undefs.append(target)
else:
raise NotImplementedError("unknown load instruction")
if undefs:
self.writeline(f"{' = '.join(undefs)} = missing")
def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None:
if not with_python_scope:
undefs = []
for target in frame.symbols.loads:
undefs.append(target)
if undefs:
self.writeline(f"{' = '.join(undefs)} = missing")
def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str:
return async_value if self.environment.is_async else sync_value
def func(self, name: str) -> str:
return f"{self.choose_async()}def {name}"
def macro_body(
self, node: nodes.Macro | nodes.CallBlock, frame: Frame
) -> tuple[Frame, MacroRef]:
"""Dump the function def of a macro or call block."""
frame = frame.inner()
frame.symbols.analyze_node(node)
macro_ref = MacroRef(node)
explicit_caller = None
skip_special_params = set()
args = []
for idx, arg in enumerate(node.args):
if arg.name == "caller":
explicit_caller = idx
if arg.name in ("kwargs", "varargs"):
skip_special_params.add(arg.name)
args.append(frame.symbols.ref(arg.name))
undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs"))
if "caller" in undeclared:
# In older Jinja versions there was a bug that allowed caller
# to retain the special behavior even if it was mentioned in
# the argument list. However thankfully this was only really
# working if it was the last argument. So we are explicitly
# checking this now and error out if it is anywhere else in
# the argument list.
if explicit_caller is not None:
try:
node.defaults[explicit_caller - len(node.args)]
except IndexError:
self.fail(
"When defining macros or call blocks the "
'special "caller" argument must be omitted '
"or be given a default.",
node.lineno,
)
else:
args.append(frame.symbols.declare_parameter("caller"))
macro_ref.accesses_caller = True
if "kwargs" in undeclared and "kwargs" not in skip_special_params:
args.append(frame.symbols.declare_parameter("kwargs"))
macro_ref.accesses_kwargs = True
if "varargs" in undeclared and "varargs" not in skip_special_params:
args.append(frame.symbols.declare_parameter("varargs"))
macro_ref.accesses_varargs = True
# macros are delayed, they never require output checks
frame.require_output_check = False
frame.symbols.analyze_node(node)
self.writeline(f"{self.func('macro')}({', '.join(args)}):", node)
self.indent()
self.buffer(frame)
self.enter_frame(frame)
self.push_parameter_definitions(frame)
for idx, arg in enumerate(node.args):
ref = frame.symbols.ref(arg.name)
self.writeline(f"if {ref} is missing:")
self.indent()
try:
default = node.defaults[idx - len(node.args)]
except IndexError:
self.writeline(
f'{ref} = undefined("parameter {arg.name!r} was not provided",'
f" name={arg.name!r})"
)
else:
self.writeline(f"{ref} = ")
self.visit(default, frame)
self.mark_parameter_stored(ref)
self.outdent()
self.pop_parameter_definitions()
self.blockvisit(node.body, frame)
self.return_buffer_contents(frame, force_unescaped=True)
self.leave_frame(frame, with_python_scope=True)
self.outdent()
return frame, macro_ref
def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None:
"""Dump the macro definition for the def created by macro_body."""
arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args)
name = getattr(macro_ref.node, "name", None)
if len(macro_ref.node.args) == 1:
arg_tuple += ","
self.write(
f"Macro(environment, macro, {name!r}, ({arg_tuple}),"
f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r},"
f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)"
)
def position(self, node: nodes.Node) -> str:
"""Return a human readable position for the node."""
rv = f"line {node.lineno}"
if self.name is not None:
rv = f"{rv} in {self.name!r}"
return rv
def dump_local_context(self, frame: Frame) -> str:
items_kv = ", ".join(
f"{name!r}: {target}"
for name, target in frame.symbols.dump_stores().items()
)
return f"{{{items_kv}}}"
def write_commons(self) -> None:
"""Writes a common preamble that is used by root and block functions.
Primarily this sets up common local helpers and enforces a generator
through a dead branch.
"""
self.writeline("resolve = context.resolve_or_missing")
self.writeline("undefined = environment.undefined")
self.writeline("concat = environment.concat")
# always use the standard Undefined class for the implicit else of
# conditional expressions
self.writeline("cond_expr_undefined = Undefined")
self.writeline("if 0: yield None")
def push_parameter_definitions(self, frame: Frame) -> None:
"""Pushes all parameter targets from the given frame into a local
stack that permits tracking of yet to be assigned parameters. In
particular this enables the optimization from `visit_Name` to skip
undefined expressions for parameters in macros as macros can reference
otherwise unbound parameters.
"""
self._param_def_block.append(frame.symbols.dump_param_targets())
def pop_parameter_definitions(self) -> None:
"""Pops the current parameter definitions set."""
self._param_def_block.pop()
def mark_parameter_stored(self, target: str) -> None:
"""Marks a parameter in the current parameter definitions as stored.
This will skip the enforced undefined checks.
"""
if self._param_def_block:
self._param_def_block[-1].discard(target)
def push_context_reference(self, target: str) -> None:
self._context_reference_stack.append(target)
def pop_context_reference(self) -> None:
self._context_reference_stack.pop()
def get_context_ref(self) -> str:
return self._context_reference_stack[-1]
def get_resolve_func(self) -> str:
target = self._context_reference_stack[-1]
if target == "context":
return "resolve"
return f"{target}.resolve"
def derive_context(self, frame: Frame) -> str:
return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})"
def parameter_is_undeclared(self, target: str) -> bool:
"""Checks if a given target is an undeclared parameter."""
if not self._param_def_block:
return False
return target in self._param_def_block[-1]
def push_assign_tracking(self) -> None:
"""Pushes a new layer for assignment tracking."""
self._assign_stack.append(set())
def pop_assign_tracking(self, frame: Frame) -> None:
"""Pops the topmost level for assignment tracking and updates the
context variables if necessary.
"""
vars = self._assign_stack.pop()
if (
not frame.block_frame
and not frame.loop_frame
and not frame.toplevel
or not vars
):
return
public_names = [x for x in vars if x[:1] != "_"]
if len(vars) == 1:
name = next(iter(vars))
ref = frame.symbols.ref(name)
if frame.loop_frame:
self.writeline(f"_loop_vars[{name!r}] = {ref}")
return
if frame.block_frame:
self.writeline(f"_block_vars[{name!r}] = {ref}")
return
self.writeline(f"context.vars[{name!r}] = {ref}")
else:
if frame.loop_frame:
self.writeline("_loop_vars.update({")
elif frame.block_frame:
self.writeline("_block_vars.update({")
else:
self.writeline("context.vars.update({")
for idx, name in enumerate(sorted(vars)):
if idx:
self.write(", ")
ref = frame.symbols.ref(name)
self.write(f"{name!r}: {ref}")
self.write("})")
if not frame.block_frame and not frame.loop_frame and public_names:
if len(public_names) == 1:
self.writeline(f"context.exported_vars.add({public_names[0]!r})")
else:
names_str = ", ".join(map(repr, sorted(public_names)))
self.writeline(f"context.exported_vars.update(({names_str}))")
# -- Statement Visitors
def visit_Template(self, node: nodes.Template, frame: Frame | None = None) -> None:
assert frame is None, "no root frame allowed"
eval_ctx = EvalContext(self.environment, self.name)
from .runtime import async_exported
from .runtime import exported
if self.environment.is_async:
exported_names = sorted(exported + async_exported)
else:
exported_names = sorted(exported)
self.writeline("from jinja2.runtime import " + ", ".join(exported_names))
# if we want a deferred initialization we cannot move the
# environment into a local name
envenv = "" if self.defer_init else ", environment=environment"
# do we have an extends tag at all? If not, we can save some
# overhead by just not processing any inheritance code.
have_extends = node.find(nodes.Extends) is not None
# find all blocks
for block in node.find_all(nodes.Block):
if block.name in self.blocks:
self.fail(f"block {block.name!r} defined twice", block.lineno)
self.blocks[block.name] = block
# find all imports and import them
for import_ in node.find_all(nodes.ImportedName):
if import_.importname not in self.import_aliases:
imp = import_.importname
self.import_aliases[imp] = alias = self.temporary_identifier()
if "." in imp:
module, obj = imp.rsplit(".", 1)
self.writeline(f"from {module} import {obj} as {alias}")
else:
self.writeline(f"import {imp} as {alias}")
# add the load name
self.writeline(f"name = {self.name!r}")
# generate the root render function.
self.writeline(
f"{self.func('root')}(context, missing=missing{envenv}):", extra=1
)
self.indent()
self.write_commons()
# process the root
frame = Frame(eval_ctx)
if "self" in find_undeclared(node.body, ("self",)):
ref = frame.symbols.declare_parameter("self")
self.writeline(f"{ref} = TemplateReference(context)")
frame.symbols.analyze_node(node)
frame.toplevel = frame.rootlevel = True
frame.require_output_check = have_extends and not self.has_known_extends
if have_extends:
self.writeline("parent_template = None")
self.enter_frame(frame)
self.pull_dependencies(node.body)
self.blockvisit(node.body, frame)
self.leave_frame(frame, with_python_scope=True)
self.outdent()
# make sure that the parent root is called.
if have_extends:
if not self.has_known_extends:
self.indent()
self.writeline("if parent_template is not None:")
self.indent()
if not self.environment.is_async:
self.writeline("yield from parent_template.root_render_func(context)")
else:
self.writeline("agen = parent_template.root_render_func(context)")
self.writeline("try:")
self.indent()
self.writeline("async for event in agen:")
self.indent()
self.writeline("yield event")
self.outdent()
self.outdent()
self.writeline("finally: await agen.aclose()")
self.outdent(1 + (not self.has_known_extends))
# at this point we now have the blocks collected and can visit them too.
for name, block in self.blocks.items():
self.writeline(
f"{self.func('block_' + name)}(context, missing=missing{envenv}):",
block,
1,
)
self.indent()
self.write_commons()
# It's important that we do not make this frame a child of the
# toplevel template. This would cause a variety of
# interesting issues with identifier tracking.
block_frame = Frame(eval_ctx)
block_frame.block_frame = True
undeclared = find_undeclared(block.body, ("self", "super"))
if "self" in undeclared:
ref = block_frame.symbols.declare_parameter("self")
self.writeline(f"{ref} = TemplateReference(context)")
if "super" in undeclared:
ref = block_frame.symbols.declare_parameter("super")
self.writeline(f"{ref} = context.super({name!r}, block_{name})")
block_frame.symbols.analyze_node(block)
block_frame.block = name
self.writeline("_block_vars = {}")
self.enter_frame(block_frame)
self.pull_dependencies(block.body)
self.blockvisit(block.body, block_frame)
self.leave_frame(block_frame, with_python_scope=True)
self.outdent()
blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks)
self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1)
debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info)
self.writeline(f"debug_info = {debug_kv_str!r}")
def visit_Block(self, node: nodes.Block, frame: Frame) -> None:
"""Call a block and register it for the template."""
level = 0
if frame.toplevel:
# if we know that we are a child template, there is no need to
# check if we are one
if self.has_known_extends:
return
if self.extends_so_far > 0:
self.writeline("if parent_template is None:")
self.indent()
level += 1
if node.scoped:
context = self.derive_context(frame)
else:
context = self.get_context_ref()
if node.required:
self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node)
self.indent()
self.writeline(
f'raise TemplateRuntimeError("Required block {node.name!r} not found")',
node,
)
self.outdent()
if not self.environment.is_async and frame.buffer is None:
self.writeline(
f"yield from context.blocks[{node.name!r}][0]({context})", node
)
else:
self.writeline(f"gen = context.blocks[{node.name!r}][0]({context})")
self.writeline("try:")
self.indent()
self.writeline(
f"{self.choose_async()}for event in gen:",
node,
)
self.indent()
self.simple_write("event", frame)
self.outdent()
self.outdent()
self.writeline(
f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}"
)
self.outdent(level)
def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None:
"""Calls the extender."""
if not frame.toplevel:
self.fail("cannot use extend from a non top-level scope", node.lineno)
# if the number of extends statements in general is zero so
# far, we don't have to add a check if something extended
# the template before this one.
if self.extends_so_far > 0:
# if we have a known extends we just add a template runtime
# error into the generated code. We could catch that at compile
# time too, but i welcome it not to confuse users by throwing the
# same error at different times just "because we can".
if not self.has_known_extends:
self.writeline("if parent_template is not None:")
self.indent()
self.writeline('raise TemplateRuntimeError("extended multiple times")')
# if we have a known extends already we don't need that code here
# as we know that the template execution will end here.
if self.has_known_extends:
raise CompilerExit()
else:
self.outdent()
self.writeline("parent_template = environment.get_template(", node)
self.visit(node.template, frame)
self.write(f", {self.name!r})")
self.writeline("for name, parent_block in parent_template.blocks.items():")
self.indent()
self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
self.outdent()
# if this extends statement was in the root level we can take
# advantage of that information and simplify the generated code
# in the top level from this point onwards
if frame.rootlevel:
self.has_known_extends = True
# and now we have one more
self.extends_so_far += 1
def visit_Include(self, node: nodes.Include, frame: Frame) -> None:
"""Handles includes."""
if node.ignore_missing:
self.writeline("try:")
self.indent()
func_name = "get_or_select_template"
if isinstance(node.template, nodes.Const):
if isinstance(node.template.value, str):
func_name = "get_template"
elif isinstance(node.template.value, (tuple, list)):
func_name = "select_template"
elif isinstance(node.template, (nodes.Tuple, nodes.List)):
func_name = "select_template"
self.writeline(f"template = environment.{func_name}(", node)
self.visit(node.template, frame)
self.write(f", {self.name!r})")
if node.ignore_missing:
self.outdent()
self.writeline("except TemplateNotFound:")
self.indent()
self.writeline("pass")
self.outdent()
self.writeline("else:")
self.indent()
def loop_body() -> None:
self.indent()
self.simple_write("event", frame)
self.outdent()
if node.with_context:
self.writeline(
f"gen = template.root_render_func("
"template.new_context(context.get_all(), True,"
f" {self.dump_local_context(frame)}))"
)
self.writeline("try:")
self.indent()
self.writeline(f"{self.choose_async()}for event in gen:")
loop_body()
self.outdent()
self.writeline(
f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}"
)
elif self.environment.is_async:
self.writeline(
"for event in (await template._get_default_module_async())"
"._body_stream:"
)
loop_body()
else:
self.writeline("yield from template._get_default_module()._body_stream")
if node.ignore_missing:
self.outdent()
def _import_common(
self, node: nodes.Import | nodes.FromImport, frame: Frame
) -> None:
self.write(f"{self.choose_async('await ')}environment.get_template(")
self.visit(node.template, frame)
self.write(f", {self.name!r}).")
if node.with_context:
f_name = f"make_module{self.choose_async('_async')}"
self.write(
f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})"
)
else:
self.write(f"_get_default_module{self.choose_async('_async')}(context)")
def visit_Import(self, node: nodes.Import, frame: Frame) -> None:
"""Visit regular imports."""
self.writeline(f"{frame.symbols.ref(node.target)} = ", node)
if frame.toplevel:
self.write(f"context.vars[{node.target!r}] = ")
self._import_common(node, frame)
if frame.toplevel and not node.target.startswith("_"):
self.writeline(f"context.exported_vars.discard({node.target!r})")
def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None:
"""Visit named imports."""
self.newline(node)
self.write("included_template = ")
self._import_common(node, frame)
var_names = []
discarded_names = []
for name in node.names:
if isinstance(name, tuple):
name, alias = name
else:
alias = name
self.writeline(
f"{frame.symbols.ref(alias)} ="
f" getattr(included_template, {name!r}, missing)"
)
self.writeline(f"if {frame.symbols.ref(alias)} is missing:")
self.indent()
# The position will contain the template name, and will be formatted
# into a string that will be compiled into an f-string. Curly braces
# in the name must be replaced with escapes so that they will not be
# executed as part of the f-string.
position = self.position(node).replace("{", "{{").replace("}", "}}")
message = (
"the template {included_template.__name__!r}"
f" (imported on {position})"
f" does not export the requested name {name!r}"
)
self.writeline(
f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})"
)
self.outdent()
if frame.toplevel:
var_names.append(alias)
if not alias.startswith("_"):
discarded_names.append(alias)
if var_names:
if len(var_names) == 1:
name = var_names[0]
self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}")
else:
names_kv = ", ".join(
f"{name!r}: {frame.symbols.ref(name)}" for name in var_names
)
self.writeline(f"context.vars.update({{{names_kv}}})")
if discarded_names:
if len(discarded_names) == 1:
self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})")
else:
names_str = ", ".join(map(repr, discarded_names))
self.writeline(
f"context.exported_vars.difference_update(({names_str}))"
)
def visit_For(self, node: nodes.For, frame: Frame) -> None:
loop_frame = frame.inner()
loop_frame.loop_frame = True
test_frame = frame.inner()
else_frame = frame.inner()
# try to figure out if we have an extended loop. An extended loop
# is necessary if the loop is in recursive mode if the special loop
# variable is accessed in the body if the body is a scoped block.
extended_loop = (
node.recursive
or "loop"
in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",))
or any(block.scoped for block in node.find_all(nodes.Block))
)
loop_ref = None
if extended_loop:
loop_ref = loop_frame.symbols.declare_parameter("loop")
loop_frame.symbols.analyze_node(node, for_branch="body")
if node.else_:
else_frame.symbols.analyze_node(node, for_branch="else")
if node.test:
loop_filter_func = self.temporary_identifier()
test_frame.symbols.analyze_node(node, for_branch="test")
self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test)
self.indent()
self.enter_frame(test_frame)
self.writeline(self.choose_async("async for ", "for "))
self.visit(node.target, loop_frame)
self.write(" in ")
self.write(self.choose_async("auto_aiter(fiter)", "fiter"))
self.write(":")
self.indent()
self.writeline("if ", node.test)
self.visit(node.test, test_frame)
self.write(":")
self.indent()
self.writeline("yield ")
self.visit(node.target, loop_frame)
self.outdent(3)
self.leave_frame(test_frame, with_python_scope=True)
# if we don't have an recursive loop we have to find the shadowed
# variables at that point. Because loops can be nested but the loop
# variable is a special one we have to enforce aliasing for it.
if node.recursive:
self.writeline(
f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node
)
self.indent()
self.buffer(loop_frame)
# Use the same buffer for the else frame
else_frame.buffer = loop_frame.buffer
# make sure the loop variable is a special one and raise a template
# assertion error if a loop tries to write to loop
if extended_loop:
self.writeline(f"{loop_ref} = missing")
for name in node.find_all(nodes.Name):
if name.ctx == "store" and name.name == "loop":
self.fail(
"Can't assign to special loop variable in for-loop target",
name.lineno,
)
if node.else_:
iteration_indicator = self.temporary_identifier()
self.writeline(f"{iteration_indicator} = 1")
self.writeline(self.choose_async("async for ", "for "), node)
self.visit(node.target, loop_frame)
if extended_loop:
self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(")
else:
self.write(" in ")
if node.test:
self.write(f"{loop_filter_func}(")
if node.recursive:
self.write("reciter")
else:
if self.environment.is_async and not extended_loop:
self.write("auto_aiter(")
self.visit(node.iter, frame)
if self.environment.is_async and not extended_loop:
self.write(")")
if node.test:
self.write(")")
if node.recursive:
self.write(", undefined, loop_render_func, depth):")
else:
self.write(", undefined):" if extended_loop else ":")
self.indent()
self.enter_frame(loop_frame)
self.writeline("_loop_vars = {}")
self.blockvisit(node.body, loop_frame)
if node.else_:
self.writeline(f"{iteration_indicator} = 0")
self.outdent()
self.leave_frame(
loop_frame, with_python_scope=node.recursive and not node.else_
)
if node.else_:
self.writeline(f"if {iteration_indicator}:")
self.indent()
self.enter_frame(else_frame)
self.blockvisit(node.else_, else_frame)
self.leave_frame(else_frame)
self.outdent()
# if the node was recursive we have to return the buffer contents
# and start the iteration code
if node.recursive:
self.return_buffer_contents(loop_frame)
self.outdent()
self.start_write(frame, node)
self.write(f"{self.choose_async('await ')}loop(")
if self.environment.is_async:
self.write("auto_aiter(")
self.visit(node.iter, frame)
if self.environment.is_async:
self.write(")")
self.write(", loop)")
self.end_write(frame)
# at the end of the iteration, clear any assignments made in the
# loop from the top level
if self._assign_stack:
self._assign_stack[-1].difference_update(loop_frame.symbols.stores)
def visit_If(self, node: nodes.If, frame: Frame) -> None:
if_frame = frame.soft()
self.writeline("if ", node)
self.visit(node.test, if_frame)
self.write(":")
self.indent()
self.blockvisit(node.body, if_frame)
self.outdent()
for elif_ in node.elif_:
self.writeline("elif ", elif_)
self.visit(elif_.test, if_frame)
self.write(":")
self.indent()
self.blockvisit(elif_.body, if_frame)
self.outdent()
if node.else_:
self.writeline("else:")
self.indent()
self.blockvisit(node.else_, if_frame)
self.outdent()
def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None:
macro_frame, macro_ref = self.macro_body(node, frame)
self.newline()
if frame.toplevel:
if not node.name.startswith("_"):
self.write(f"context.exported_vars.add({node.name!r})")
self.writeline(f"context.vars[{node.name!r}] = ")
self.write(f"{frame.symbols.ref(node.name)} = ")
self.macro_def(macro_ref, macro_frame)
def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None:
call_frame, macro_ref = self.macro_body(node, frame)
self.writeline("caller = ")
self.macro_def(macro_ref, call_frame)
self.start_write(frame, node)
self.visit_Call(node.call, frame, forward_caller=True)
self.end_write(frame)
def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None:
filter_frame = frame.inner()
filter_frame.symbols.analyze_node(node)
self.enter_frame(filter_frame)
self.buffer(filter_frame)
self.blockvisit(node.body, filter_frame)
self.start_write(frame, node)
self.visit_Filter(node.filter, filter_frame)
self.end_write(frame)
self.leave_frame(filter_frame)
def visit_With(self, node: nodes.With, frame: Frame) -> None:
with_frame = frame.inner()
with_frame.symbols.analyze_node(node)
self.enter_frame(with_frame)
for target, expr in zip(node.targets, node.values, strict=False):
self.newline()
self.visit(target, with_frame)
self.write(" = ")
self.visit(expr, frame)
self.blockvisit(node.body, with_frame)
self.leave_frame(with_frame)
def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None:
self.newline(node)
self.visit(node.node, frame)
class _FinalizeInfo(t.NamedTuple):
const: t.Callable[..., str] | None
src: str | None
@staticmethod
def _default_finalize(value: t.Any) -> t.Any:
"""The default finalize function if the environment isn't
configured with one. Or, if the environment has one, this is
called on that function's output for constants.
"""
return str(value)
_finalize: _FinalizeInfo | None = None
def _make_finalize(self) -> _FinalizeInfo:
"""Build the finalize function to be used on constants and at
runtime. Cached so it's only created once for all output nodes.
Returns a ``namedtuple`` with the following attributes:
``const``
A function to finalize constant data at compile time.
``src``
Source code to output around nodes to be evaluated at
runtime.
"""
if self._finalize is not None:
return self._finalize
finalize: t.Callable[..., t.Any] | None
finalize = default = self._default_finalize
src = None
if self.environment.finalize:
src = "environment.finalize("
env_finalize = self.environment.finalize
pass_arg = {
_PassArg.context: "context",
_PassArg.eval_context: "context.eval_ctx",
_PassArg.environment: "environment",
}.get(
_PassArg.from_obj(env_finalize) # type: ignore
)
finalize = None
if pass_arg is None:
def finalize(value: t.Any) -> t.Any: # noqa: F811
return default(env_finalize(value))
else:
src = f"{src}{pass_arg}, "
if pass_arg == "environment":
def finalize(value: t.Any) -> t.Any: # noqa: F811
return default(env_finalize(self.environment, value))
self._finalize = self._FinalizeInfo(finalize, src)
return self._finalize
def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
"""Given a group of constant values converted from ``Output``
child nodes, produce a string to write to the template module
source.
"""
return repr(concat(group))
def _output_child_to_const(
self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
) -> str:
"""Try to optimize a child of an ``Output`` node by trying to
convert it to constant, finalized data at compile time.
If :exc:`Impossible` is raised, the node is not constant and
will be evaluated at runtime. Any other exception will also be
evaluated at runtime for easier debugging.
"""
const = node.as_const(frame.eval_ctx)
if frame.eval_ctx.autoescape:
const = escape(const)
# Template data doesn't go through finalize.
if isinstance(node, nodes.TemplateData):
return str(const)
return finalize.const(const) # type: ignore
def _output_child_pre(
self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
) -> None:
"""Output extra source code before visiting a child of an
``Output`` node.
"""
if frame.eval_ctx.volatile:
self.write("(escape if context.eval_ctx.autoescape else str)(")
elif frame.eval_ctx.autoescape:
self.write("escape(")
else:
self.write("str(")
if finalize.src is not None:
self.write(finalize.src)
def _output_child_post(
self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
) -> None:
"""Output extra source code after visiting a child of an
``Output`` node.
"""
self.write(")")
if finalize.src is not None:
self.write(")")
def visit_Output(self, node: nodes.Output, frame: Frame) -> None:
# If an extends is active, don't render outside a block.
if frame.require_output_check:
# A top-level extends is known to exist at compile time.
if self.has_known_extends:
return
self.writeline("if parent_template is None:")
self.indent()
finalize = self._make_finalize()
body: list[list[t.Any] | nodes.Expr] = []
# Evaluate constants at compile time if possible. Each item in
# body will be either a list of static data or a node to be
# evaluated at runtime.
for child in node.nodes:
try:
if not (
# If the finalize function requires runtime context,
# constants can't be evaluated at compile time.
finalize.const
# Unless it's basic template data that won't be
# finalized anyway.
or isinstance(child, nodes.TemplateData)
):
raise nodes.Impossible()
const = self._output_child_to_const(child, frame, finalize)
except (nodes.Impossible, Exception):
# The node was not constant and needs to be evaluated at
# runtime. Or another error was raised, which is easier
# to debug at runtime.
body.append(child)
continue
if body and isinstance(body[-1], list):
body[-1].append(const)
else:
body.append([const])
if frame.buffer is not None:
if len(body) == 1:
self.writeline(f"{frame.buffer}.append(")
else:
self.writeline(f"{frame.buffer}.extend((")
self.indent()
for item in body:
if isinstance(item, list):
# A group of constant data to join and output.
val = self._output_const_repr(item)
if frame.buffer is None:
self.writeline("yield " + val)
else:
self.writeline(val + ",")
else:
if frame.buffer is None:
self.writeline("yield ", item)
else:
self.newline(item)
# A node to be evaluated at runtime.
self._output_child_pre(item, frame, finalize)
self.visit(item, frame)
self._output_child_post(item, frame, finalize)
if frame.buffer is not None:
self.write(",")
if frame.buffer is not None:
self.outdent()
self.writeline(")" if len(body) == 1 else "))")
if frame.require_output_check:
self.outdent()
def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None:
self.push_assign_tracking()
# ``a.b`` is allowed for assignment, and is parsed as an NSRef. However,
# it is only valid if it references a Namespace object. Emit a check for
# that for each ref here, before assignment code is emitted. This can't
# be done in visit_NSRef as the ref could be in the middle of a tuple.
seen_refs: set[str] = set()
for nsref in node.find_all(nodes.NSRef):
if nsref.name in seen_refs:
# Only emit the check for each reference once, in case the same
# ref is used multiple times in a tuple, `ns.a, ns.b = c, d`.
continue
seen_refs.add(nsref.name)
ref = frame.symbols.ref(nsref.name)
self.writeline(f"if not isinstance({ref}, Namespace):")
self.indent()
self.writeline(
"raise TemplateRuntimeError"
'("cannot assign attribute on non-namespace object")'
)
self.outdent()
self.newline(node)
self.visit(node.target, frame)
self.write(" = ")
self.visit(node.node, frame)
self.pop_assign_tracking(frame)
def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None:
self.push_assign_tracking()
block_frame = frame.inner()
# This is a special case. Since a set block always captures we
# will disable output checks. This way one can use set blocks
# toplevel even in extended templates.
block_frame.require_output_check = False
block_frame.symbols.analyze_node(node)
self.enter_frame(block_frame)
self.buffer(block_frame)
self.blockvisit(node.body, block_frame)
self.newline(node)
self.visit(node.target, frame)
self.write(" = (Markup if context.eval_ctx.autoescape else identity)(")
if node.filter is not None:
self.visit_Filter(node.filter, block_frame)
else:
self.write(f"concat({block_frame.buffer})")
self.write(")")
self.pop_assign_tracking(frame)
self.leave_frame(block_frame)
# -- Expression Visitors
def visit_Name(self, node: nodes.Name, frame: Frame) -> None:
if node.ctx == "store" and (
frame.toplevel or frame.loop_frame or frame.block_frame
):
if self._assign_stack:
self._assign_stack[-1].add(node.name)
ref = frame.symbols.ref(node.name)
# If we are looking up a variable we might have to deal with the
# case where it's undefined. We can skip that case if the load
# instruction indicates a parameter which are always defined.
if node.ctx == "load":
load = frame.symbols.find_load(ref)
if not (
load is not None
and load[0] == VAR_LOAD_PARAMETER
and not self.parameter_is_undeclared(ref)
):
self.write(
f"(undefined(name={node.name!r}) if {ref} is missing else {ref})"
)
return
self.write(ref)
def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None:
# NSRef is a dotted assignment target a.b=c, but uses a[b]=c internally.
# visit_Assign emits code to validate that each ref is to a Namespace
# object only. That can't be emitted here as the ref could be in the
# middle of a tuple assignment.
ref = frame.symbols.ref(node.name)
self.writeline(f"{ref}[{node.attr!r}]")
def visit_Const(self, node: nodes.Const, frame: Frame) -> None:
val = node.as_const(frame.eval_ctx)
if isinstance(val, float):
self.write(str(val))
else:
self.write(repr(val))
def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None:
try:
self.write(repr(node.as_const(frame.eval_ctx)))
except nodes.Impossible:
self.write(
f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})"
)
def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None:
self.write("(")
idx = -1
for idx, item in enumerate(node.items):
if idx:
self.write(", ")
self.visit(item, frame)
self.write(",)" if idx == 0 else ")")
def visit_List(self, node: nodes.List, frame: Frame) -> None:
self.write("[")
for idx, item in enumerate(node.items):
if idx:
self.write(", ")
self.visit(item, frame)
self.write("]")
def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None:
self.write("{")
for idx, item in enumerate(node.items):
if idx:
self.write(", ")
self.visit(item.key, frame)
self.write(": ")
self.visit(item.value, frame)
self.write("}")
visit_Add = _make_binop("+")
visit_Sub = _make_binop("-")
visit_Mul = _make_binop("*")
visit_Div = _make_binop("/")
visit_FloorDiv = _make_binop("//")
visit_Pow = _make_binop("**")
visit_Mod = _make_binop("%")
visit_And = _make_binop("and")
visit_Or = _make_binop("or")
visit_Pos = _make_unop("+")
visit_Neg = _make_unop("-")
visit_Not = _make_unop("not ")
@optimizeconst
def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None:
if frame.eval_ctx.volatile:
func_name = "(markup_join if context.eval_ctx.volatile else str_join)"
elif frame.eval_ctx.autoescape:
func_name = "markup_join"
else:
func_name = "str_join"
self.write(f"{func_name}((")
for arg in node.nodes:
self.visit(arg, frame)
self.write(", ")
self.write("))")
@optimizeconst
def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None:
self.write("(")
self.visit(node.expr, frame)
for op in node.ops:
self.visit(op, frame)
self.write(")")
def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None:
self.write(f" {operators[node.op]} ")
self.visit(node.expr, frame)
@optimizeconst
def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None:
if self.environment.is_async:
self.write("(await auto_await(")
self.write("environment.getattr(")
self.visit(node.node, frame)
self.write(f", {node.attr!r})")
if self.environment.is_async:
self.write("))")
@optimizeconst
def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None:
# slices bypass the environment getitem method.
if isinstance(node.arg, nodes.Slice):
self.visit(node.node, frame)
self.write("[")
self.visit(node.arg, frame)
self.write("]")
else:
if self.environment.is_async:
self.write("(await auto_await(")
self.write("environment.getitem(")
self.visit(node.node, frame)
self.write(", ")
self.visit(node.arg, frame)
self.write(")")
if self.environment.is_async:
self.write("))")
def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None:
if node.start is not None:
self.visit(node.start, frame)
self.write(":")
if node.stop is not None:
self.visit(node.stop, frame)
if node.step is not None:
self.write(":")
self.visit(node.step, frame)
@contextmanager
def _filter_test_common(
self, node: nodes.Filter | nodes.Test, frame: Frame, is_filter: bool
) -> t.Iterator[None]:
if self.environment.is_async:
self.write("(await auto_await(")
if is_filter:
self.write(f"{self.filters[node.name]}(")
func = self.environment.filters.get(node.name)
else:
self.write(f"{self.tests[node.name]}(")
func = self.environment.tests.get(node.name)
# When inside an If or CondExpr frame, allow the filter to be
# undefined at compile time and only raise an error if it's
# actually called at runtime. See pull_dependencies.
if func is None and not frame.soft_frame:
type_name = "filter" if is_filter else "test"
self.fail(f"No {type_name} named {node.name!r}.", node.lineno)
pass_arg = {
_PassArg.context: "context",
_PassArg.eval_context: "context.eval_ctx",
_PassArg.environment: "environment",
}.get(
_PassArg.from_obj(func) # type: ignore
)
if pass_arg is not None:
self.write(f"{pass_arg}, ")
# Back to the visitor function to handle visiting the target of
# the filter or test.
yield
self.signature(node, frame)
self.write(")")
if self.environment.is_async:
self.write("))")
@optimizeconst
def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None:
with self._filter_test_common(node, frame, True):
# if the filter node is None we are inside a filter block
# and want to write to the current buffer
if node.node is not None:
self.visit(node.node, frame)
elif frame.eval_ctx.volatile:
self.write(
f"(Markup(concat({frame.buffer}))"
f" if context.eval_ctx.autoescape else concat({frame.buffer}))"
)
elif frame.eval_ctx.autoescape:
self.write(f"Markup(concat({frame.buffer}))")
else:
self.write(f"concat({frame.buffer})")
@optimizeconst
def visit_Test(self, node: nodes.Test, frame: Frame) -> None:
with self._filter_test_common(node, frame, False):
self.visit(node.node, frame)
@optimizeconst
def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None:
frame = frame.soft()
def write_expr2() -> None:
if node.expr2 is not None:
self.visit(node.expr2, frame)
return
self.write(
f'cond_expr_undefined("the inline if-expression on'
f" {self.position(node)} evaluated to false and no else"
f' section was defined.")'
)
self.write("(")
self.visit(node.expr1, frame)
self.write(" if ")
self.visit(node.test, frame)
self.write(" else ")
write_expr2()
self.write(")")
@optimizeconst
def visit_Call(
self, node: nodes.Call, frame: Frame, forward_caller: bool = False
) -> None:
if self.environment.is_async:
self.write("(await auto_await(")
if self.environment.sandboxed:
self.write("environment.call(context, ")
else:
self.write("context.call(")
self.visit(node.node, frame)
extra_kwargs = {"caller": "caller"} if forward_caller else None
loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {}
block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {}
if extra_kwargs:
extra_kwargs.update(loop_kwargs, **block_kwargs)
elif loop_kwargs or block_kwargs:
extra_kwargs = dict(loop_kwargs, **block_kwargs)
self.signature(node, frame, extra_kwargs)
self.write(")")
if self.environment.is_async:
self.write("))")
def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None:
self.write(node.key + "=")
self.visit(node.value, frame)
# -- Unused nodes for extensions
def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None:
self.write("Markup(")
self.visit(node.expr, frame)
self.write(")")
def visit_MarkSafeIfAutoescape(
self, node: nodes.MarkSafeIfAutoescape, frame: Frame
) -> None:
self.write("(Markup if context.eval_ctx.autoescape else identity)(")
self.visit(node.expr, frame)
self.write(")")
def visit_EnvironmentAttribute(
self, node: nodes.EnvironmentAttribute, frame: Frame
) -> None:
self.write("environment." + node.name)
def visit_ExtensionAttribute(
self, node: nodes.ExtensionAttribute, frame: Frame
) -> None:
self.write(f"environment.extensions[{node.identifier!r}].{node.name}")
def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None:
self.write(self.import_aliases[node.importname])
def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None:
self.write(node.name)
def visit_ContextReference(
self, node: nodes.ContextReference, frame: Frame
) -> None:
self.write("context")
def visit_DerivedContextReference(
self, node: nodes.DerivedContextReference, frame: Frame
) -> None:
self.write(self.derive_context(frame))
def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None:
self.writeline("continue", node)
def visit_Break(self, node: nodes.Break, frame: Frame) -> None:
self.writeline("break", node)
def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None:
scope_frame = frame.inner()
scope_frame.symbols.analyze_node(node)
self.enter_frame(scope_frame)
self.blockvisit(node.body, scope_frame)
self.leave_frame(scope_frame)
def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None:
ctx = self.temporary_identifier()
self.writeline(f"{ctx} = {self.derive_context(frame)}")
self.writeline(f"{ctx}.vars = ")
self.visit(node.context, frame)
self.push_context_reference(ctx)
scope_frame = frame.inner(isolated=True)
scope_frame.symbols.analyze_node(node)
self.enter_frame(scope_frame)
self.blockvisit(node.body, scope_frame)
self.leave_frame(scope_frame)
self.pop_context_reference()
def visit_EvalContextModifier(
self, node: nodes.EvalContextModifier, frame: Frame
) -> None:
for keyword in node.options:
self.writeline(f"context.eval_ctx.{keyword.key} = ")
self.visit(keyword.value, frame)
try:
val = keyword.value.as_const(frame.eval_ctx)
except nodes.Impossible:
frame.eval_ctx.volatile = True
else:
setattr(frame.eval_ctx, keyword.key, val)
def visit_ScopedEvalContextModifier(
self, node: nodes.ScopedEvalContextModifier, frame: Frame
) -> None:
old_ctx_name = self.temporary_identifier()
saved_ctx = frame.eval_ctx.save()
self.writeline(f"{old_ctx_name} = context.eval_ctx.save()")
self.visit_EvalContextModifier(node, frame)
for child in node.body:
self.visit(child, frame)
frame.eval_ctx.revert(saved_ctx)
self.writeline(f"context.eval_ctx.revert({old_ctx_name})")
================================================
FILE: src/jinja2/constants.py
================================================
#: list of lorem ipsum words used by the lipsum() helper function
LOREM_IPSUM_WORDS = """\
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
auctor augue bibendum blandit class commodo condimentum congue consectetuer
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
penatibus per pharetra phasellus placerat platea porta porttitor posuere
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
viverra volutpat vulputate"""
================================================
FILE: src/jinja2/debug.py
================================================
import sys
import typing as t
from types import CodeType
from types import TracebackType
from .exceptions import TemplateSyntaxError
from .utils import internal_code
from .utils import missing
if t.TYPE_CHECKING:
from .runtime import Context
def rewrite_traceback_stack(source: str | None = None) -> BaseException:
"""Rewrite the current exception to replace any tracebacks from
within compiled template code with tracebacks that look like they
came from the template source.
This must be called within an ``except`` block.
:param source: For ``TemplateSyntaxError``, the original source if
known.
:return: The original exception with the rewritten traceback.
"""
_, exc_value, tb = sys.exc_info()
exc_value = t.cast(BaseException, exc_value)
tb = t.cast(TracebackType, tb)
if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
exc_value.translated = True
exc_value.source = source
# Remove the old traceback, otherwise the frames from the
# compiler still show up.
exc_value.with_traceback(None)
# Outside of runtime, so the frame isn't executing template
# code, but it still needs to point at the template.
tb = fake_traceback(
exc_value, None, exc_value.filename or "", exc_value.lineno
)
else:
# Skip the frame for the render function.
tb = tb.tb_next
stack = []
# Build the stack of traceback object, replacing any in template
# code with the source file and line information.
while tb is not None:
# Skip frames decorated with @internalcode. These are internal
# calls that aren't useful in template debugging output.
if tb.tb_frame.f_code in internal_code:
tb = tb.tb_next
continue
template = tb.tb_frame.f_globals.get("__jinja_template__")
if template is not None:
lineno = template.get_corresponding_lineno(tb.tb_lineno)
fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
stack.append(fake_tb)
else:
stack.append(tb)
tb = tb.tb_next
tb_next = None
# Assign tb_next in reverse to avoid circular references.
for tb in reversed(stack):
tb.tb_next = tb_next
tb_next = tb
return exc_value.with_traceback(tb_next)
def fake_traceback( # type: ignore
exc_value: BaseException, tb: TracebackType | None, filename: str, lineno: int
) -> TracebackType:
"""Produce a new traceback object that looks like it came from the
template source instead of the compiled code. The filename, line
number, and location name will point to the template, and the local
variables will be the current template context.
:param exc_value: The original exception to be re-raised to create
the new traceback.
:param tb: The original traceback to get the local variables and
code info from.
:param filename: The template filename.
:param lineno: The line number in the template source.
"""
if tb is not None:
# Replace the real locals with the context that would be
# available at that point in the template.
locals = get_template_locals(tb.tb_frame.f_locals)
locals.pop("__jinja_exception__", None)
else:
locals = {}
globals = {
"__name__": filename,
"__file__": filename,
"__jinja_exception__": exc_value,
}
# Raise an exception at the correct line number.
code: CodeType = compile(
"\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec"
)
# Build a new code object that points to the template file and
# replaces the location with a block name.
location = "template"
if tb is not None:
function = tb.tb_frame.f_code.co_name
if function == "root":
location = "top-level template code"
elif function.startswith("block_"):
location = f"block {function[6:]!r}"
code = code.replace(co_name=location)
# Execute the new code, which is guaranteed to raise, and return
# the new traceback without this frame.
try:
exec(code, globals, locals)
except BaseException:
return sys.exc_info()[2].tb_next # type: ignore
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> dict[str, t.Any]:
"""Based on the runtime locals, get the context that would be
available at that point in the template.
"""
# Start with the current template context.
ctx: Context | None = real_locals.get("context")
if ctx is not None:
data: dict[str, t.Any] = ctx.get_all().copy()
else:
data = {}
# Might be in a derived context that only sets local variables
# rather than pushing a context. Local variables follow the scheme
# l_depth_name. Find the highest-depth local that has a value for
# each name.
local_overrides: dict[str, tuple[int, t.Any]] = {}
for name, value in real_locals.items():
if not name.startswith("l_") or value is missing:
# Not a template variable, or no longer relevant.
continue
try:
_, depth_str, name = name.split("_", 2)
depth = int(depth_str)
except ValueError:
continue
cur_depth = local_overrides.get(name, (-1,))[0]
if cur_depth < depth:
local_overrides[name] = (depth, value)
# Modify the context with any derived context.
for name, (_, value) in local_overrides.items():
if value is missing:
data.pop(name, None)
else:
data[name] = value
return data
================================================
FILE: src/jinja2/defaults.py
================================================
import typing as t
from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401
from .tests import TESTS as DEFAULT_TESTS # noqa: F401
from .utils import Cycler
from .utils import generate_lorem_ipsum
from .utils import Joiner
from .utils import Namespace
if t.TYPE_CHECKING:
import typing_extensions as te
# defaults for the parser / lexer
BLOCK_START_STRING = "{%"
BLOCK_END_STRING = "%}"
VARIABLE_START_STRING = "{{"
VARIABLE_END_STRING = "}}"
COMMENT_START_STRING = "{#"
COMMENT_END_STRING = "#}"
LINE_STATEMENT_PREFIX: str | None = None
LINE_COMMENT_PREFIX: str | None = None
TRIM_BLOCKS = False
LSTRIP_BLOCKS = False
NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n"
KEEP_TRAILING_NEWLINE = False
# default filters, tests and namespace
DEFAULT_NAMESPACE = {
"range": range,
"dict": dict,
"lipsum": generate_lorem_ipsum,
"cycler": Cycler,
"joiner": Joiner,
"namespace": Namespace,
}
# default policies
DEFAULT_POLICIES: dict[str, t.Any] = {
"compiler.ascii_str": True,
"urlize.rel": "noopener",
"urlize.target": None,
"urlize.extra_schemes": None,
"truncate.leeway": 5,
"json.dumps_function": None,
"json.dumps_kwargs": {"sort_keys": True},
"ext.i18n.trimmed": False,
}
================================================
FILE: src/jinja2/environment.py
================================================
"""Classes for managing templates and their runtime and compile time
options.
"""
import os
import typing
import typing as t
import weakref
from collections import ChainMap
from contextlib import aclosing
from functools import lru_cache
from functools import partial
from functools import reduce
from types import CodeType
from markupsafe import Markup
from . import nodes
from .compiler import CodeGenerator
from .compiler import generate
from .defaults import BLOCK_END_STRING
from .defaults import BLOCK_START_STRING
from .defaults import COMMENT_END_STRING
from .defaults import COMMENT_START_STRING
from .defaults import DEFAULT_FILTERS # type: ignore[attr-defined]
from .defaults import DEFAULT_NAMESPACE
from .defaults import DEFAULT_POLICIES
from .defaults import DEFAULT_TESTS # type: ignore[attr-defined]
from .defaults import KEEP_TRAILING_NEWLINE
from .defaults import LINE_COMMENT_PREFIX
from .defaults import LINE_STATEMENT_PREFIX
from .defaults import LSTRIP_BLOCKS
from .defaults import NEWLINE_SEQUENCE
from .defaults import TRIM_BLOCKS
from .defaults import VARIABLE_END_STRING
from .defaults import VARIABLE_START_STRING
from .exceptions import TemplateNotFound
from .exceptions import TemplateRuntimeError
from .exceptions import TemplatesNotFound
from .exceptions import TemplateSyntaxError
from .exceptions import UndefinedError
from .lexer import get_lexer
from .lexer import Lexer
from .lexer import TokenStream
from .nodes import EvalContext
from .parser import Parser
from .runtime import Context
from .runtime import new_context
from .runtime import Undefined
from .utils import _PassArg
from .utils import concat
from .utils import consume
from .utils import import_string
from .utils import internalcode
from .utils import LRUCache
from .utils import missing
if t.TYPE_CHECKING:
import typing_extensions as te
from .bccache import BytecodeCache
from .ext import Extension
from .loaders import BaseLoader
_env_bound = t.TypeVar("_env_bound", bound="Environment")
# for direct template usage we have up to ten living environments
@lru_cache(maxsize=10)
def get_spontaneous_environment(cls: type[_env_bound], *args: t.Any) -> _env_bound:
"""Return a new spontaneous environment. A spontaneous environment
is used for templates created directly rather than through an
existing environment.
:param cls: Environment class to create.
:param args: Positional arguments passed to environment.
"""
env = cls(*args)
env.shared = True
return env
def create_cache(
size: int,
) -> t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None:
"""Return the cache class for the given size."""
if size == 0:
return None
if size < 0:
return {}
return LRUCache(size) # type: ignore
def copy_cache(
cache: t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None,
) -> t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None:
"""Create an empty copy of the given cache."""
if cache is None:
return None
if type(cache) is dict: # noqa E721
return {}
return LRUCache(cache.capacity) # type: ignore
def load_extensions(
environment: "Environment",
extensions: t.Sequence[str | type["Extension"]],
) -> dict[str, "Extension"]:
"""Load the extensions from the list and bind it to the environment.
Returns a dict of instantiated extensions.
"""
result = {}
for extension in extensions:
if isinstance(extension, str):
extension = t.cast(type["Extension"], import_string(extension))
result[extension.identifier] = extension(environment)
return result
def _environment_config_check(environment: _env_bound) -> _env_bound:
"""Perform a sanity check on the environment."""
assert issubclass(environment.undefined, Undefined), (
"'undefined' must be a subclass of 'jinja2.Undefined'."
)
assert (
environment.block_start_string
!= environment.variable_start_string
!= environment.comment_start_string
), "block, variable and comment start strings must be different."
assert environment.newline_sequence in {
"\r",
"\r\n",
"\n",
}, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'."
return environment
class Environment:
r"""The core component of Jinja is the `Environment`. It contains
important shared variables like configuration, filters, tests,
globals and others. Instances of this class may be modified if
they are not shared and if no template was loaded so far.
Modifications on environments after the first template was loaded
will lead to surprising effects and undefined behavior.
Here are the possible initialization parameters:
`block_start_string`
The string marking the beginning of a block. Defaults to ``'{%'``.
`block_end_string`
The string marking the end of a block. Defaults to ``'%}'``.
`variable_start_string`
The string marking the beginning of a print statement.
Defaults to ``'{{'``.
`variable_end_string`
The string marking the end of a print statement. Defaults to
``'}}'``.
`comment_start_string`
The string marking the beginning of a comment. Defaults to ``'{#'``.
`comment_end_string`
The string marking the end of a comment. Defaults to ``'#}'``.
`line_statement_prefix`
If given and a string, this will be used as prefix for line based
statements. See also :ref:`line-statements`.
`line_comment_prefix`
If given and a string, this will be used as prefix for line based
comments. See also :ref:`line-statements`.
.. versionadded:: 2.2
`trim_blocks`
If this is set to ``True`` the first newline after a block is
removed (block, not variable tag!). Defaults to `False`.
`lstrip_blocks`
If this is set to ``True`` leading spaces and tabs are stripped
from the start of a line to a block. Defaults to `False`.
`newline_sequence`
The sequence that starts a newline. Must be one of ``'\r'``,
``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
useful default for Linux and OS X systems as well as web
applications.
`keep_trailing_newline`
Preserve the trailing newline when rendering templates.
The default is ``False``, which causes a single newline,
if present, to be stripped from the end of the template.
.. versionadded:: 2.7
`extensions`
List of Jinja extensions to use. This can either be import paths
as strings or extension classes. For more information have a
look at :ref:`the extensions documentation `.
`optimized`
should the optimizer be enabled? Default is ``True``.
`undefined`
:class:`Undefined` or a subclass of it that is used to represent
undefined values in the template.
`finalize`
A callable that can be used to process the result of a variable
expression before it is output. For example one can convert
``None`` implicitly into an empty string here.
`autoescape`
If set to ``True`` the XML/HTML autoescaping feature is enabled by
default. For more details about autoescaping see
:class:`~markupsafe.Markup`. As of Jinja 2.4 this can also
be a callable that is passed the template name and has to
return ``True`` or ``False`` depending on autoescape should be
enabled by default.
.. versionchanged:: 2.4
`autoescape` can now be a function
`loader`
The template loader for this environment.
`cache_size`
The size of the cache. Per default this is ``400`` which means
that if more than 400 templates are loaded the loader will clean
out the least recently used template. If the cache size is set to
``0`` templates are recompiled all the time, if the cache size is
``-1`` the cache will not be cleaned.
.. versionchanged:: 2.8
The cache size was increased to 400 from a low 50.
`auto_reload`
Some loaders load templates from locations where the template
sources may change (ie: file system or database). If
``auto_reload`` is set to ``True`` (default) every time a template is
requested the loader checks if the source changed and if yes, it
will reload the template. For higher performance it's possible to
disable that.
`bytecode_cache`
If set to a bytecode cache object, this object will provide a
cache for the internal Jinja bytecode so that templates don't
have to be parsed if they were not changed.
See :ref:`bytecode-cache` for more information.
`enable_async`
If set to true this enables async template execution which
allows using async functions and generators.
"""
#: if this environment is sandboxed. Modifying this variable won't make
#: the environment sandboxed though. For a real sandboxed environment
#: have a look at jinja2.sandbox. This flag alone controls the code
#: generation by the compiler.
sandboxed = False
#: True if the environment is just an overlay
overlayed = False
#: the environment this environment is linked to if it is an overlay
linked_to: t.Optional["Environment"] = None
#: shared environments have this set to `True`. A shared environment
#: must not be modified
shared = False
#: the class that is used for code generation. See
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
code_generator_class: type["CodeGenerator"] = CodeGenerator
concat = "".join
#: the context class that is used for templates. See
#: :class:`~jinja2.runtime.Context` for more information.
context_class: type[Context] = Context
template_class: type["Template"]
def __init__(
self,
block_start_string: str = BLOCK_START_STRING,
block_end_string: str = BLOCK_END_STRING,
variable_start_string: str = VARIABLE_START_STRING,
variable_end_string: str = VARIABLE_END_STRING,
comment_start_string: str = COMMENT_START_STRING,
comment_end_string: str = COMMENT_END_STRING,
line_statement_prefix: str | None = LINE_STATEMENT_PREFIX,
line_comment_prefix: str | None = LINE_COMMENT_PREFIX,
trim_blocks: bool = TRIM_BLOCKS,
lstrip_blocks: bool = LSTRIP_BLOCKS,
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
extensions: t.Sequence[str | type["Extension"]] = (),
optimized: bool = True,
undefined: type[Undefined] = Undefined,
finalize: t.Callable[..., t.Any] | None = None,
autoescape: bool | t.Callable[[str | None], bool] = False,
loader: t.Optional["BaseLoader"] = None,
cache_size: int = 400,
auto_reload: bool = True,
bytecode_cache: t.Optional["BytecodeCache"] = None,
enable_async: bool = False,
):
# !!Important notice!!
# The constructor accepts quite a few arguments that should be
# passed by keyword rather than position. However it's important to
# not change the order of arguments because it's used at least
# internally in those cases:
# - spontaneous environments (i18n extension and Template)
# - unittests
# If parameter changes are required only add parameters at the end
# and don't change the arguments (or the defaults!) of the arguments
# existing already.
# lexer / parser information
self.block_start_string = block_start_string
self.block_end_string = block_end_string
self.variable_start_string = variable_start_string
self.variable_end_string = variable_end_string
self.comment_start_string = comment_start_string
self.comment_end_string = comment_end_string
self.line_statement_prefix = line_statement_prefix
self.line_comment_prefix = line_comment_prefix
self.trim_blocks = trim_blocks
self.lstrip_blocks = lstrip_blocks
self.newline_sequence = newline_sequence
self.keep_trailing_newline = keep_trailing_newline
# runtime information
self.undefined: type[Undefined] = undefined
self.optimized = optimized
self.finalize = finalize
self.autoescape = autoescape
# defaults
self.filters = DEFAULT_FILTERS.copy()
self.tests = DEFAULT_TESTS.copy()
self.globals = DEFAULT_NAMESPACE.copy()
# set the loader provided
self.loader = loader
self.cache = create_cache(cache_size)
self.bytecode_cache = bytecode_cache
self.auto_reload = auto_reload
# configurable policies
self.policies = DEFAULT_POLICIES.copy()
# load extensions
self.extensions = load_extensions(self, extensions)
self.is_async = enable_async
_environment_config_check(self)
def add_extension(self, extension: str | type["Extension"]) -> None:
"""Adds an extension after the environment was created.
.. versionadded:: 2.5
"""
self.extensions.update(load_extensions(self, [extension]))
def extend(self, **attributes: t.Any) -> None:
"""Add the items to the instance of the environment if they do not exist
yet. This is used by :ref:`extensions ` to register
callbacks and configuration values without breaking inheritance.
"""
for key, value in attributes.items():
if not hasattr(self, key):
setattr(self, key, value)
def overlay(
self,
block_start_string: str = missing,
block_end_string: str = missing,
variable_start_string: str = missing,
variable_end_string: str = missing,
comment_start_string: str = missing,
comment_end_string: str = missing,
line_statement_prefix: str | None = missing,
line_comment_prefix: str | None = missing,
trim_blocks: bool = missing,
lstrip_blocks: bool = missing,
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
keep_trailing_newline: bool = missing,
extensions: t.Sequence[str | type["Extension"]] = missing,
optimized: bool = missing,
undefined: type[Undefined] = missing,
finalize: t.Callable[..., t.Any] | None = missing,
autoescape: bool | t.Callable[[str | None], bool] = missing,
loader: t.Optional["BaseLoader"] = missing,
cache_size: int = missing,
auto_reload: bool = missing,
bytecode_cache: t.Optional["BytecodeCache"] = missing,
enable_async: bool = missing,
) -> "te.Self":
"""Create a new overlay environment that shares all the data with the
current environment except for cache and the overridden attributes.
Extensions cannot be removed for an overlayed environment. An overlayed
environment automatically gets all the extensions of the environment it
is linked to plus optional extra extensions.
Creating overlays should happen after the initial environment was set
up completely. Not all attributes are truly linked, some are just
copied over so modifications on the original environment may not shine
through.
.. versionchanged:: 3.1.5
``enable_async`` is applied correctly.
.. versionchanged:: 3.1.2
Added the ``newline_sequence``, ``keep_trailing_newline``,
and ``enable_async`` parameters to match ``__init__``.
"""
args = dict(locals())
del args["self"], args["cache_size"], args["extensions"], args["enable_async"]
rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__)
rv.overlayed = True
rv.linked_to = self
for key, value in args.items():
if value is not missing:
setattr(rv, key, value)
if cache_size is not missing:
rv.cache = create_cache(cache_size)
else:
rv.cache = copy_cache(self.cache)
rv.extensions = {}
for key, value in self.extensions.items():
rv.extensions[key] = value.bind(rv)
if extensions is not missing:
rv.extensions.update(load_extensions(rv, extensions))
if enable_async is not missing:
rv.is_async = enable_async
return _environment_config_check(rv)
@property
def lexer(self) -> Lexer:
"""The lexer for this environment."""
return get_lexer(self)
def iter_extensions(self) -> t.Iterator["Extension"]:
"""Iterates over the extensions by priority."""
return iter(sorted(self.extensions.values(), key=lambda x: x.priority))
def getitem(self, obj: t.Any, argument: str | t.Any) -> t.Any | Undefined:
"""Get an item or attribute of an object but prefer the item."""
try:
return obj[argument]
except (AttributeError, TypeError, LookupError):
if isinstance(argument, str):
try:
attr = str(argument)
except Exception:
pass
else:
try:
return getattr(obj, attr)
except AttributeError:
pass
return self.undefined(obj=obj, name=argument)
def getattr(self, obj: t.Any, attribute: str) -> t.Any:
"""Get an item or attribute of an object but prefer the attribute.
Unlike :meth:`getitem` the attribute *must* be a string.
"""
try:
return getattr(obj, attribute)
except AttributeError:
pass
try:
return obj[attribute]
except (TypeError, LookupError, AttributeError):
return self.undefined(obj=obj, name=attribute)
def _filter_test_common(
self,
name: str | Undefined,
value: t.Any,
args: t.Sequence[t.Any] | None,
kwargs: t.Mapping[str, t.Any] | None,
context: Context | None,
eval_ctx: EvalContext | None,
is_filter: bool,
) -> t.Any:
if is_filter:
env_map = self.filters
type_name = "filter"
else:
env_map = self.tests
type_name = "test"
func = env_map.get(name) # type: ignore
if func is None:
msg = f"No {type_name} named {name!r}."
if isinstance(name, Undefined):
try:
name._fail_with_undefined_error()
except Exception as e:
msg = f"{msg} ({e}; did you forget to quote the callable name?)"
raise TemplateRuntimeError(msg)
args = [value, *(args if args is not None else ())]
kwargs = kwargs if kwargs is not None else {}
pass_arg = _PassArg.from_obj(func)
if pass_arg is _PassArg.context:
if context is None:
raise TemplateRuntimeError(
f"Attempted to invoke a context {type_name} without context."
)
args.insert(0, context)
elif pass_arg is _PassArg.eval_context:
if eval_ctx is None:
if context is not None:
eval_ctx = context.eval_ctx
else:
eval_ctx = EvalContext(self)
args.insert(0, eval_ctx)
elif pass_arg is _PassArg.environment:
args.insert(0, self)
return func(*args, **kwargs)
def call_filter(
self,
name: str,
value: t.Any,
args: t.Sequence[t.Any] | None = None,
kwargs: t.Mapping[str, t.Any] | None = None,
context: Context | None = None,
eval_ctx: EvalContext | None = None,
) -> t.Any:
"""Invoke a filter on a value the same way the compiler does.
This might return a coroutine if the filter is running from an
environment in async mode and the filter supports async
execution. It's your responsibility to await this if needed.
.. versionadded:: 2.7
"""
return self._filter_test_common(
name, value, args, kwargs, context, eval_ctx, True
)
def call_test(
self,
name: str,
value: t.Any,
args: t.Sequence[t.Any] | None = None,
kwargs: t.Mapping[str, t.Any] | None = None,
context: Context | None = None,
eval_ctx: EvalContext | None = None,
) -> t.Any:
"""Invoke a test on a value the same way the compiler does.
This might return a coroutine if the test is running from an
environment in async mode and the test supports async execution.
It's your responsibility to await this if needed.
.. versionchanged:: 3.0
Tests support ``@pass_context``, etc. decorators. Added
the ``context`` and ``eval_ctx`` parameters.
.. versionadded:: 2.7
"""
return self._filter_test_common(
name, value, args, kwargs, context, eval_ctx, False
)
@internalcode
def parse(
self,
source: str,
name: str | None = None,
filename: str | None = None,
) -> nodes.Template:
"""Parse the sourcecode and return the abstract syntax tree. This
tree of nodes is used by the compiler to convert the template into
executable source- or bytecode. This is useful for debugging or to
extract information from templates.
If you are :ref:`developing Jinja extensions `
this gives you a good overview of the node tree generated.
"""
try:
return self._parse(source, name, filename)
except TemplateSyntaxError:
self.handle_exception(source=source)
def _parse(
self, source: str, name: str | None, filename: str | None
) -> nodes.Template:
"""Internal parsing function used by `parse` and `compile`."""
return Parser(self, source, name, filename).parse()
def lex(
self,
source: str,
name: str | None = None,
filename: str | None = None,
) -> t.Iterator[tuple[int, str, str]]:
"""Lex the given sourcecode and return a generator that yields
tokens as tuples in the form ``(lineno, token_type, value)``.
This can be useful for :ref:`extension development `
and debugging templates.
This does not perform preprocessing. If you want the preprocessing
of the extensions to be applied you have to filter source through
the :meth:`preprocess` method.
"""
source = str(source)
try:
return self.lexer.tokeniter(source, name, filename)
except TemplateSyntaxError:
self.handle_exception(source=source)
def preprocess(
self,
source: str,
name: str | None = None,
filename: str | None = None,
) -> str:
"""Preprocesses the source with all extensions. This is automatically
called for all parsing and compiling methods but *not* for :meth:`lex`
because there you usually only want the actual source tokenized.
"""
return reduce(
lambda s, e: e.preprocess(s, name, filename),
self.iter_extensions(),
str(source),
)
def _tokenize(
self,
source: str,
name: str | None,
filename: str | None = None,
state: str | None = None,
) -> TokenStream:
"""Called by the parser to do the preprocessing and filtering
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
"""
source = self.preprocess(source, name, filename)
stream = self.lexer.tokenize(source, name, filename, state)
for ext in self.iter_extensions():
stream = ext.filter_stream(stream) # type: ignore
if not isinstance(stream, TokenStream):
stream = TokenStream(stream, name, filename)
return stream
def _generate(
self,
source: nodes.Template,
name: str | None,
filename: str | None,
defer_init: bool = False,
) -> str:
"""Internal hook that can be overridden to hook a different generate
method in.
.. versionadded:: 2.5
"""
return generate( # type: ignore
source,
self,
name,
filename,
defer_init=defer_init,
optimized=self.optimized,
)
def _compile(self, source: str, filename: str) -> CodeType:
"""Internal hook that can be overridden to hook a different compile
method in.
.. versionadded:: 2.5
"""
return compile(source, filename, "exec")
@typing.overload
def compile(
self,
source: str | nodes.Template,
name: str | None = None,
filename: str | None = None,
raw: "te.Literal[False]" = False,
defer_init: bool = False,
) -> CodeType: ...
@typing.overload
def compile(
self,
source: str | nodes.Template,
name: str | None = None,
filename: str | None = None,
raw: "te.Literal[True]" = ...,
defer_init: bool = False,
) -> str: ...
@internalcode
def compile(
self,
source: str | nodes.Template,
name: str | None = None,
filename: str | None = None,
raw: bool = False,
defer_init: bool = False,
) -> str | CodeType:
"""Compile a node or template source code. The `name` parameter is
the load name of the template after it was joined using
:meth:`join_path` if necessary, not the filename on the file system.
the `filename` parameter is the estimated filename of the template on
the file system. If the template came from a database or memory this
can be omitted.
The return value of this method is a python code object. If the `raw`
parameter is `True` the return value will be a string with python
code equivalent to the bytecode returned otherwise. This method is
mainly used internally.
`defer_init` is use internally to aid the module code generator. This
causes the generated code to be able to import without the global
environment variable to be set.
.. versionadded:: 2.4
`defer_init` parameter added.
"""
source_hint = None
try:
if isinstance(source, str):
source_hint = source
source = self._parse(source, name, filename)
source = self._generate(source, name, filename, defer_init=defer_init)
if raw:
return source
if filename is None:
filename = ""
return self._compile(source, filename)
except TemplateSyntaxError:
self.handle_exception(source=source_hint)
def compile_expression(
self, source: str, undefined_to_none: bool = True
) -> "TemplateExpression":
"""A handy helper method that returns a callable that accepts keyword
arguments that appear as variables in the expression. If called it
returns the result of the expression.
This is useful if applications want to use the same rules as Jinja
in template "configuration files" or similar situations.
Example usage:
>>> env = Environment()
>>> expr = env.compile_expression('foo == 42')
>>> expr(foo=23)
False
>>> expr(foo=42)
True
Per default the return value is converted to `None` if the
expression returns an undefined value. This can be changed
by setting `undefined_to_none` to `False`.
>>> env.compile_expression('var')() is None
True
>>> env.compile_expression('var', undefined_to_none=False)()
Undefined
.. versionadded:: 2.1
"""
parser = Parser(self, source, state="variable")
try:
expr = parser.parse_expression()
if not parser.stream.eos:
raise TemplateSyntaxError(
"chunk after expression", parser.stream.current.lineno, None, None
)
expr.set_environment(self)
except TemplateSyntaxError:
self.handle_exception(source=source)
body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)]
template = self.from_string(nodes.Template(body, lineno=1))
return TemplateExpression(template, undefined_to_none)
def compile_templates(
self,
target: t.Union[str, "os.PathLike[str]"],
extensions: t.Collection[str] | None = None,
filter_func: t.Callable[[str], bool] | None = None,
zip: str | None = "deflated",
log_function: t.Callable[[str], None] | None = None,
ignore_errors: bool = True,
) -> None:
"""Finds all the templates the loader can find, compiles them
and stores them in `target`. If `zip` is `None`, instead of in a
zipfile, the templates will be stored in a directory.
By default a deflate zip algorithm is used. To switch to
the stored algorithm, `zip` can be set to ``'stored'``.
`extensions` and `filter_func` are passed to :meth:`list_templates`.
Each template returned will be compiled to the target folder or
zipfile.
By default template compilation errors are ignored. In case a
log function is provided, errors are logged. If you want template
syntax errors to abort the compilation you can set `ignore_errors`
to `False` and you will get an exception on syntax errors.
.. versionadded:: 2.4
"""
from .loaders import ModuleLoader
if log_function is None:
def log_function(x: str) -> None:
pass
assert log_function is not None
assert self.loader is not None, "No loader configured."
def write_file(filename: str, data: str) -> None:
if zip:
info = ZipInfo(filename)
info.external_attr = 0o755 << 16
zip_file.writestr(info, data)
else:
with open(os.path.join(target, filename), "wb") as f:
f.write(data.encode("utf8"))
if zip is not None:
from zipfile import ZIP_DEFLATED
from zipfile import ZIP_STORED
from zipfile import ZipFile
from zipfile import ZipInfo
zip_file = ZipFile(
target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
)
log_function(f"Compiling into Zip archive {target!r}")
else:
if not os.path.isdir(target):
os.makedirs(target)
log_function(f"Compiling into folder {target!r}")
try:
for name in self.list_templates(extensions, filter_func):
source, filename, _ = self.loader.get_source(self, name)
try:
code = self.compile(source, name, filename, True, True)
except TemplateSyntaxError as e:
if not ignore_errors:
raise
log_function(f'Could not compile "{name}": {e}')
continue
filename = ModuleLoader.get_module_filename(name)
write_file(filename, code)
log_function(f'Compiled "{name}" as {filename}')
finally:
if zip:
zip_file.close()
log_function("Finished compiling templates")
def list_templates(
self,
extensions: t.Collection[str] | None = None,
filter_func: t.Callable[[str], bool] | None = None,
) -> list[str]:
"""Returns a list of templates for this environment. This requires
that the loader supports the loader's
:meth:`~BaseLoader.list_templates` method.
If there are other files in the template folder besides the
actual templates, the returned list can be filtered. There are two
ways: either `extensions` is set to a list of file extensions for
templates, or a `filter_func` can be provided which is a callable that
is passed a template name and should return `True` if it should end up
in the result list.
If the loader does not support that, a :exc:`TypeError` is raised.
.. versionadded:: 2.4
"""
assert self.loader is not None, "No loader configured."
names = self.loader.list_templates()
if extensions is not None:
if filter_func is not None:
raise TypeError(
"either extensions or filter_func can be passed, but not both"
)
def filter_func(x: str) -> bool:
return "." in x and x.rsplit(".", 1)[1] in extensions
if filter_func is not None:
names = [name for name in names if filter_func(name)]
return names
def handle_exception(self, source: str | None = None) -> "te.NoReturn":
"""Exception handling helper. This is used internally to either raise
rewritten exceptions or return a rendered traceback for the template.
"""
from .debug import rewrite_traceback_stack
raise rewrite_traceback_stack(source=source)
def join_path(self, template: str, parent: str) -> str:
"""Join a template with the parent. By default all the lookups are
relative to the loader root so this method returns the `template`
parameter unchanged, but if the paths should be relative to the
parent template, this function can be used to calculate the real
template name.
Subclasses may override this method and implement template path
joining here.
"""
return template
@internalcode
def _load_template(
self, name: str, globals: t.MutableMapping[str, t.Any] | None
) -> "Template":
if self.loader is None:
raise TypeError("no loader for this environment specified")
cache_key = (weakref.ref(self.loader), name)
if self.cache is not None:
template = self.cache.get(cache_key)
if template is not None and (
not self.auto_reload or template.is_up_to_date
):
# template.globals is a ChainMap, modifying it will only
# affect the template, not the environment globals.
if globals:
template.globals.update(globals)
return template
template = self.loader.load(self, name, self.make_globals(globals))
if self.cache is not None:
self.cache[cache_key] = template
return template
@internalcode
def get_template(
self,
name: t.Union[str, "Template"],
parent: str | None = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
"""Load a template by name with :attr:`loader` and return a
:class:`Template`. If the template does not exist a
:exc:`TemplateNotFound` exception is raised.
:param name: Name of the template to load. When loading
templates from the filesystem, "/" is used as the path
separator, even on Windows.
:param parent: The name of the parent template importing this
template. :meth:`join_path` can be used to implement name
transformations with this.
:param globals: Extend the environment :attr:`globals` with
these extra variables available for all renders of this
template. If the template has already been loaded and
cached, its globals are updated with any new items.
.. versionchanged:: 3.0
If a template is loaded from cache, ``globals`` will update
the template's globals instead of ignoring the new values.
.. versionchanged:: 2.4
If ``name`` is a :class:`Template` object it is returned
unchanged.
"""
if isinstance(name, Template):
return name
if parent is not None:
name = self.join_path(name, parent)
return self._load_template(name, globals)
@internalcode
def select_template(
self,
names: t.Iterable[t.Union[str, "Template"]],
parent: str | None = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
"""Like :meth:`get_template`, but tries loading multiple names.
If none of the names can be loaded a :exc:`TemplatesNotFound`
exception is raised.
:param names: List of template names to try loading in order.
:param parent: The name of the parent template importing this
template. :meth:`join_path` can be used to implement name
transformations with this.
:param globals: Extend the environment :attr:`globals` with
these extra variables available for all renders of this
template. If the template has already been loaded and
cached, its globals are updated with any new items.
.. versionchanged:: 3.0
If a template is loaded from cache, ``globals`` will update
the template's globals instead of ignoring the new values.
.. versionchanged:: 2.11
If ``names`` is :class:`Undefined`, an :exc:`UndefinedError`
is raised instead. If no templates were found and ``names``
contains :class:`Undefined`, the message is more helpful.
.. versionchanged:: 2.4
If ``names`` contains a :class:`Template` object it is
returned unchanged.
.. versionadded:: 2.3
"""
if isinstance(names, Undefined):
names._fail_with_undefined_error()
if not names:
raise TemplatesNotFound(
message="Tried to select from an empty list of templates."
)
for name in names:
if isinstance(name, Template):
return name
if parent is not None:
name = self.join_path(name, parent)
try:
return self._load_template(name, globals)
except (TemplateNotFound, UndefinedError):
pass
raise TemplatesNotFound(names) # type: ignore
@internalcode
def get_or_select_template(
self,
template_name_or_list: t.Union[str, "Template", list[t.Union[str, "Template"]]],
parent: str | None = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
"""Use :meth:`select_template` if an iterable of template names
is given, or :meth:`get_template` if one name is given.
.. versionadded:: 2.3
"""
if isinstance(template_name_or_list, (str, Undefined)):
return self.get_template(template_name_or_list, parent, globals)
elif isinstance(template_name_or_list, Template):
return template_name_or_list
return self.select_template(template_name_or_list, parent, globals)
def from_string(
self,
source: str | nodes.Template,
globals: t.MutableMapping[str, t.Any] | None = None,
template_class: type["Template"] | None = None,
) -> "Template":
"""Load a template from a source string without using
:attr:`loader`.
:param source: Jinja source to compile into a template.
:param globals: Extend the environment :attr:`globals` with
these extra variables available for all renders of this
template. If the template has already been loaded and
cached, its globals are updated with any new items.
:param template_class: Return an instance of this
:class:`Template` class.
"""
gs = self.make_globals(globals)
cls = template_class or self.template_class
return cls.from_code(self, self.compile(source), gs, None)
def make_globals(
self, d: t.MutableMapping[str, t.Any] | None
) -> t.MutableMapping[str, t.Any]:
"""Make the globals map for a template. Any given template
globals overlay the environment :attr:`globals`.
Returns a :class:`collections.ChainMap`. This allows any changes
to a template's globals to only affect that template, while
changes to the environment's globals are still reflected.
However, avoid modifying any globals after a template is loaded.
:param d: Dict of template-specific globals.
.. versionchanged:: 3.0
Use :class:`collections.ChainMap` to always prevent mutating
environment globals.
"""
if d is None:
d = {}
return ChainMap(d, self.globals)
class Template:
"""A compiled template that can be rendered.
Use the methods on :class:`Environment` to create or load templates.
The environment is used to configure how templates are compiled and
behave.
It is also possible to create a template object directly. This is
not usually recommended. The constructor takes most of the same
arguments as :class:`Environment`. All templates created with the
same environment arguments share the same ephemeral ``Environment``
instance behind the scenes.
A template object should be considered immutable. Modifications on
the object are not supported.
"""
#: Type of environment to create when creating a template directly
#: rather than through an existing environment.
environment_class: type[Environment] = Environment
environment: Environment
globals: t.MutableMapping[str, t.Any]
name: str | None
filename: str | None
blocks: dict[str, t.Callable[[Context], t.Iterator[str]]]
root_render_func: t.Callable[[Context], t.Iterator[str]]
_module: t.Optional["TemplateModule"]
_debug_info: str
_uptodate: t.Callable[[], bool] | None
def __new__(
cls,
source: str | nodes.Template,
block_start_string: str = BLOCK_START_STRING,
block_end_string: str = BLOCK_END_STRING,
variable_start_string: str = VARIABLE_START_STRING,
variable_end_string: str = VARIABLE_END_STRING,
comment_start_string: str = COMMENT_START_STRING,
comment_end_string: str = COMMENT_END_STRING,
line_statement_prefix: str | None = LINE_STATEMENT_PREFIX,
line_comment_prefix: str | None = LINE_COMMENT_PREFIX,
trim_blocks: bool = TRIM_BLOCKS,
lstrip_blocks: bool = LSTRIP_BLOCKS,
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
extensions: t.Sequence[str | type["Extension"]] = (),
optimized: bool = True,
undefined: type[Undefined] = Undefined,
finalize: t.Callable[..., t.Any] | None = None,
autoescape: bool | t.Callable[[str | None], bool] = False,
enable_async: bool = False,
) -> t.Any: # it returns a `Template`, but this breaks the sphinx build...
env = get_spontaneous_environment(
cls.environment_class, # type: ignore
block_start_string,
block_end_string,
variable_start_string,
variable_end_string,
comment_start_string,
comment_end_string,
line_statement_prefix,
line_comment_prefix,
trim_blocks,
lstrip_blocks,
newline_sequence,
keep_trailing_newline,
frozenset(extensions),
optimized,
undefined, # type: ignore
finalize,
autoescape,
None,
0,
False,
None,
enable_async,
)
return env.from_string(source, template_class=cls)
@classmethod
def from_code(
cls,
environment: Environment,
code: CodeType,
globals: t.MutableMapping[str, t.Any],
uptodate: t.Callable[[], bool] | None = None,
) -> "Template":
"""Creates a template object from compiled code and the globals. This
is used by the loaders and environment to create a template object.
"""
namespace = {"environment": environment, "__file__": code.co_filename}
exec(code, namespace)
rv = cls._from_namespace(environment, namespace, globals)
rv._uptodate = uptodate
return rv
@classmethod
def from_module_dict(
cls,
environment: Environment,
module_dict: t.MutableMapping[str, t.Any],
globals: t.MutableMapping[str, t.Any],
) -> "Template":
"""Creates a template object from a module. This is used by the
module loader to create a template object.
.. versionadded:: 2.4
"""
return cls._from_namespace(environment, module_dict, globals)
@classmethod
def _from_namespace(
cls,
environment: Environment,
namespace: t.MutableMapping[str, t.Any],
globals: t.MutableMapping[str, t.Any],
) -> "Template":
t: Template = object.__new__(cls)
t.environment = environment
t.globals = globals
t.name = namespace["name"]
t.filename = namespace["__file__"]
t.blocks = namespace["blocks"]
# render function and module
t.root_render_func = namespace["root"]
t._module = None
# debug and loader helpers
t._debug_info = namespace["debug_info"]
t._uptodate = None
# store the reference
namespace["environment"] = environment
namespace["__jinja_template__"] = t
return t
def render(self, *args: t.Any, **kwargs: t.Any) -> str:
"""This method accepts the same arguments as the `dict` constructor:
A dict, a dict subclass or some keyword arguments. If no arguments
are given the context will be empty. These two calls do the same::
template.render(knights='that say nih')
template.render({'knights': 'that say nih'})
This will return the rendered template as a string.
"""
if self.environment.is_async:
import asyncio
return asyncio.run(self.render_async(*args, **kwargs))
ctx = self.new_context(dict(*args, **kwargs))
try:
return self.environment.concat(self.root_render_func(ctx)) # type: ignore
except Exception:
self.environment.handle_exception()
async def render_async(self, *args: t.Any, **kwargs: t.Any) -> str:
"""This works similar to :meth:`render` but returns a coroutine
that when awaited returns the entire rendered template string. This
requires the async feature to be enabled.
Example usage::
await template.render_async(knights='that say nih; asynchronously')
"""
if not self.environment.is_async:
raise RuntimeError(
"The environment was not created with async mode enabled."
)
ctx = self.new_context(dict(*args, **kwargs))
try:
return self.environment.concat( # type: ignore
[n async for n in self.root_render_func(ctx)] # type: ignore
)
except Exception:
return self.environment.handle_exception()
def stream(self, *args: t.Any, **kwargs: t.Any) -> "TemplateStream":
"""Works exactly like :meth:`generate` but returns a
:class:`TemplateStream`.
"""
return TemplateStream(self.generate(*args, **kwargs))
def generate(self, *args: t.Any, **kwargs: t.Any) -> t.Iterator[str]:
"""For very large templates it can be useful to not render the whole
template at once but evaluate each statement after another and yield
piece for piece. This method basically does exactly that and returns
a generator that yields one item after another as strings.
It accepts the same arguments as :meth:`render`.
"""
if self.environment.is_async:
import asyncio
async def to_list() -> list[str]:
return [x async for x in self.generate_async(*args, **kwargs)]
yield from asyncio.run(to_list())
return
ctx = self.new_context(dict(*args, **kwargs))
try:
yield from self.root_render_func(ctx)
except Exception:
yield self.environment.handle_exception()
async def generate_async(
self, *args: t.Any, **kwargs: t.Any
) -> t.AsyncGenerator[str, object]:
"""An async version of :meth:`generate`. Works very similarly but
returns an async iterator instead.
"""
if not self.environment.is_async:
raise RuntimeError(
"The environment was not created with async mode enabled."
)
ctx = self.new_context(dict(*args, **kwargs))
try:
agen: t.AsyncGenerator[str, None] = self.root_render_func(ctx) # type: ignore[assignment]
async with aclosing(agen):
async for event in agen:
yield event
except Exception:
yield self.environment.handle_exception()
def new_context(
self,
vars: dict[str, t.Any] | None = None,
shared: bool = False,
locals: t.Mapping[str, t.Any] | None = None,
) -> Context:
"""Create a new :class:`Context` for this template. The vars
provided will be passed to the template. Per default the globals
are added to the context. If shared is set to `True` the data
is passed as is to the context without adding the globals.
`locals` can be a dict of local variables for internal usage.
"""
return new_context(
self.environment, self.name, self.blocks, vars, shared, self.globals, locals
)
def make_module(
self,
vars: dict[str, t.Any] | None = None,
shared: bool = False,
locals: t.Mapping[str, t.Any] | None = None,
) -> "TemplateModule":
"""This method works like the :attr:`module` attribute when called
without arguments but it will evaluate the template on every call
rather than caching it. It's also possible to provide
a dict which is then used as context. The arguments are the same
as for the :meth:`new_context` method.
"""
ctx = self.new_context(vars, shared, locals)
return TemplateModule(self, ctx)
async def make_module_async(
self,
vars: dict[str, t.Any] | None = None,
shared: bool = False,
locals: t.Mapping[str, t.Any] | None = None,
) -> "TemplateModule":
"""As template module creation can invoke template code for
asynchronous executions this method must be used instead of the
normal :meth:`make_module` one. Likewise the module attribute
becomes unavailable in async mode.
"""
ctx = self.new_context(vars, shared, locals)
return TemplateModule(
self,
ctx,
[x async for x in self.root_render_func(ctx)], # type: ignore
)
@internalcode
def _get_default_module(self, ctx: Context | None = None) -> "TemplateModule":
"""If a context is passed in, this means that the template was
imported. Imported templates have access to the current
template's globals by default, but they can only be accessed via
the context during runtime.
If there are new globals, we need to create a new module because
the cached module is already rendered and will not have access
to globals from the current context. This new module is not
cached because the template can be imported elsewhere, and it
should have access to only the current template's globals.
"""
if self.environment.is_async:
raise RuntimeError("Module is not available in async mode.")
if ctx is not None:
keys = ctx.globals_keys - self.globals.keys()
if keys:
return self.make_module({k: ctx.parent[k] for k in keys})
if self._module is None:
self._module = self.make_module()
return self._module
async def _get_default_module_async(
self, ctx: Context | None = None
) -> "TemplateModule":
if ctx is not None:
keys = ctx.globals_keys - self.globals.keys()
if keys:
return await self.make_module_async({k: ctx.parent[k] for k in keys})
if self._module is None:
self._module = await self.make_module_async()
return self._module
@property
def module(self) -> "TemplateModule":
"""The template as module. This is used for imports in the
template runtime but is also useful if one wants to access
exported template variables from the Python layer:
>>> t = Template('{% macro foo() %}42{% endmacro %}23')
>>> str(t.module)
'23'
>>> t.module.foo() == u'42'
True
This attribute is not available if async mode is enabled.
"""
return self._get_default_module()
def get_corresponding_lineno(self, lineno: int) -> int:
"""Return the source line number of a line number in the
generated bytecode as they are not in sync.
"""
for template_line, code_line in reversed(self.debug_info):
if code_line <= lineno:
return template_line
return 1
@property
def is_up_to_date(self) -> bool:
"""If this variable is `False` there is a newer version available."""
if self._uptodate is None:
return True
return self._uptodate()
@property
def debug_info(self) -> list[tuple[int, int]]:
"""The debug info mapping."""
if self._debug_info:
return [
tuple(map(int, x.split("="))) # type: ignore
for x in self._debug_info.split("&")
]
return []
def __repr__(self) -> str:
if self.name is None:
name = f"memory:{id(self):x}"
else:
name = repr(self.name)
return f"<{type(self).__name__} {name}>"
class TemplateModule:
"""Represents an imported template. All the exported names of the
template are available as attributes on this object. Additionally
converting it into a string renders the contents.
"""
def __init__(
self,
template: Template,
context: Context,
body_stream: t.Iterable[str] | None = None,
) -> None:
if body_stream is None:
if context.environment.is_async:
raise RuntimeError(
"Async mode requires a body stream to be passed to"
" a template module. Use the async methods of the"
" API you are using."
)
body_stream = list(template.root_render_func(context))
self._body_stream = body_stream
self.__dict__.update(context.get_exported())
self.__name__ = template.name
def __html__(self) -> Markup:
return Markup(concat(self._body_stream))
def __str__(self) -> str:
return concat(self._body_stream)
def __repr__(self) -> str:
if self.__name__ is None:
name = f"memory:{id(self):x}"
else:
name = repr(self.__name__)
return f"<{type(self).__name__} {name}>"
class TemplateExpression:
"""The :meth:`jinja2.Environment.compile_expression` method returns an
instance of this object. It encapsulates the expression-like access
to the template with an expression it wraps.
"""
def __init__(self, template: Template, undefined_to_none: bool) -> None:
self._template = template
self._undefined_to_none = undefined_to_none
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any | None:
context = self._template.new_context(dict(*args, **kwargs))
consume(self._template.root_render_func(context))
rv = context.vars["result"]
if self._undefined_to_none and isinstance(rv, Undefined):
rv = None
return rv
class TemplateStream:
"""A template stream works pretty much like an ordinary python generator
but it can buffer multiple items to reduce the number of total iterations.
Per default the output is unbuffered which means that for every unbuffered
instruction in the template one string is yielded.
If buffering is enabled with a buffer size of 5, five items are combined
into a new string. This is mainly useful if you are streaming
big templates to a client via WSGI which flushes after each iteration.
"""
def __init__(self, gen: t.Iterator[str]) -> None:
self._gen = gen
self.disable_buffering()
def dump(
self,
fp: str | t.IO[bytes],
encoding: str | None = None,
errors: str | None = "strict",
) -> None:
"""Dump the complete stream into a file or file-like object.
Per default strings are written, if you want to encode
before writing specify an `encoding`.
Example usage::
Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
"""
close = False
if isinstance(fp, str):
if encoding is None:
encoding = "utf-8"
real_fp: t.IO[bytes] = open(fp, "wb")
close = True
else:
real_fp = fp
try:
if encoding is not None:
iterable = (x.encode(encoding, errors) for x in self) # type: ignore
else:
iterable = self # type: ignore
if hasattr(real_fp, "writelines"):
real_fp.writelines(iterable)
else:
for item in iterable:
real_fp.write(item)
finally:
if close:
real_fp.close()
def disable_buffering(self) -> None:
"""Disable the output buffering."""
self._next = partial(next, self._gen)
self.buffered = False
def _buffered_generator(self, size: int) -> t.Iterator[str]:
buf: list[str] = []
c_size = 0
push = buf.append
while True:
try:
while c_size < size:
c = next(self._gen)
push(c)
if c:
c_size += 1
except StopIteration:
if not c_size:
return
yield concat(buf)
del buf[:]
c_size = 0
def enable_buffering(self, size: int = 5) -> None:
"""Enable buffering. Buffer `size` items before yielding them."""
if size <= 1:
raise ValueError("buffer size too small")
self.buffered = True
self._next = partial(next, self._buffered_generator(size))
def __iter__(self) -> "TemplateStream":
return self
def __next__(self) -> str:
return self._next() # type: ignore
# hook in default template class. if anyone reads this comment: ignore that
# it's possible to use custom templates ;-)
Environment.template_class = Template
================================================
FILE: src/jinja2/exceptions.py
================================================
import typing as t
if t.TYPE_CHECKING:
from .runtime import Undefined
class TemplateError(Exception):
"""Baseclass for all template errors."""
def __init__(self, message: str | None = None) -> None:
super().__init__(message)
@property
def message(self) -> str | None:
return self.args[0] if self.args else None
class TemplateNotFound(IOError, LookupError, TemplateError):
"""Raised if a template does not exist.
.. versionchanged:: 2.11
If the given name is :class:`Undefined` and no message was
provided, an :exc:`UndefinedError` is raised.
"""
# Silence the Python warning about message being deprecated since
# it's not valid here.
message: str | None = None
def __init__(
self,
name: t.Union[str, "Undefined"] | None,
message: str | None = None,
) -> None:
IOError.__init__(self, name)
if message is None:
from .runtime import Undefined
if isinstance(name, Undefined):
name._fail_with_undefined_error()
message = name
self.message = message
self.name = name
self.templates = [name]
def __str__(self) -> str:
return str(self.message)
class TemplatesNotFound(TemplateNotFound):
"""Like :class:`TemplateNotFound` but raised if multiple templates
are selected. This is a subclass of :class:`TemplateNotFound`
exception, so just catching the base exception will catch both.
.. versionchanged:: 2.11
If a name in the list of names is :class:`Undefined`, a message
about it being undefined is shown rather than the empty string.
.. versionadded:: 2.2
"""
def __init__(
self,
names: t.Sequence[t.Union[str, "Undefined"]] = (),
message: str | None = None,
) -> None:
if message is None:
from .runtime import Undefined
parts = []
for name in names:
if isinstance(name, Undefined):
parts.append(name._undefined_message)
else:
parts.append(name)
parts_str = ", ".join(map(str, parts))
message = f"none of the templates given were found: {parts_str}"
super().__init__(names[-1] if names else None, message)
self.templates = list(names)
class TemplateSyntaxError(TemplateError):
"""Raised to tell the user that there is a problem with the template."""
def __init__(
self,
message: str,
lineno: int,
name: str | None = None,
filename: str | None = None,
) -> None:
super().__init__(message)
self.lineno = lineno
self.name = name
self.filename = filename
self.source: str | None = None
# this is set to True if the debug.translate_syntax_error
# function translated the syntax error into a new traceback
self.translated = False
def __str__(self) -> str:
# for translated errors we only return the message
if self.translated:
return t.cast(str, self.message)
# otherwise attach some stuff
location = f"line {self.lineno}"
name = self.filename or self.name
if name:
location = f'File "{name}", {location}'
lines = [t.cast(str, self.message), " " + location]
# if the source is set, add the line to the output
if self.source is not None:
try:
line = self.source.splitlines()[self.lineno - 1]
except IndexError:
pass
else:
lines.append(" " + line.strip())
return "\n".join(lines)
def __reduce__(self): # type: ignore
# https://bugs.python.org/issue1692335 Exceptions that take
# multiple required arguments have problems with pickling.
# Without this, raises TypeError: __init__() missing 1 required
# positional argument: 'lineno'
return self.__class__, (self.message, self.lineno, self.name, self.filename)
class TemplateAssertionError(TemplateSyntaxError):
"""Like a template syntax error, but covers cases where something in the
template caused an error at compile time that wasn't necessarily caused
by a syntax error. However it's a direct subclass of
:exc:`TemplateSyntaxError` and has the same attributes.
"""
class TemplateRuntimeError(TemplateError):
"""A generic runtime error in the template engine. Under some situations
Jinja may raise this exception.
"""
class UndefinedError(TemplateRuntimeError):
"""Raised if a template tries to operate on :class:`Undefined`."""
class SecurityError(TemplateRuntimeError):
"""Raised if a template tries to do something insecure if the
sandbox is enabled.
"""
class FilterArgumentError(TemplateRuntimeError):
"""This error is raised if a filter was called with inappropriate
arguments
"""
================================================
FILE: src/jinja2/ext.py
================================================
"""Extension API for adding custom tags and behavior."""
import pprint
import re
import typing as t
from markupsafe import Markup
from . import defaults
from . import nodes
from .environment import Environment
from .exceptions import TemplateAssertionError
from .exceptions import TemplateSyntaxError
from .runtime import concat # type: ignore
from .runtime import Context
from .runtime import Undefined
from .utils import import_string
from .utils import pass_context
if t.TYPE_CHECKING:
import typing_extensions as te
from .lexer import Token
from .lexer import TokenStream
from .parser import Parser
class _TranslationsBasic(te.Protocol):
def gettext(self, message: str) -> str: ...
def ngettext(self, singular: str, plural: str, n: int) -> str:
pass
class _TranslationsContext(_TranslationsBasic):
def pgettext(self, context: str, message: str) -> str: ...
def npgettext(
self, context: str, singular: str, plural: str, n: int
) -> str: ...
_SupportedTranslations = _TranslationsBasic | _TranslationsContext
# I18N functions available in Jinja templates. If the I18N library
# provides ugettext, it will be assigned to gettext.
GETTEXT_FUNCTIONS: tuple[str, ...] = (
"_",
"gettext",
"ngettext",
"pgettext",
"npgettext",
)
_ws_re = re.compile(r"\s*\n\s*")
class Extension:
"""Extensions can be used to add extra functionality to the Jinja template
system at the parser level. Custom extensions are bound to an environment
but may not store environment specific data on `self`. The reason for
this is that an extension can be bound to another environment (for
overlays) by creating a copy and reassigning the `environment` attribute.
As extensions are created by the environment they cannot accept any
arguments for configuration. One may want to work around that by using
a factory function, but that is not possible as extensions are identified
by their import name. The correct way to configure the extension is
storing the configuration values on the environment. Because this way the
environment ends up acting as central configuration storage the
attributes may clash which is why extensions have to ensure that the names
they choose for configuration are not too generic. ``prefix`` for example
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
name as includes the name of the extension (fragment cache).
"""
identifier: t.ClassVar[str]
def __init_subclass__(cls) -> None:
cls.identifier = f"{cls.__module__}.{cls.__name__}"
#: if this extension parses this is the list of tags it's listening to.
tags: set[str] = set()
#: the priority of that extension. This is especially useful for
#: extensions that preprocess values. A lower value means higher
#: priority.
#:
#: .. versionadded:: 2.4
priority = 100
def __init__(self, environment: Environment) -> None:
self.environment = environment
def bind(self, environment: Environment) -> "te.Self":
"""Create a copy of this extension bound to another environment."""
rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__)
rv.environment = environment
return rv
def preprocess(
self, source: str, name: str | None, filename: str | None = None
) -> str:
"""This method is called before the actual lexing and can be used to
preprocess the source. The `filename` is optional. The return value
must be the preprocessed source.
"""
return source
def filter_stream(
self, stream: "TokenStream"
) -> t.Union["TokenStream", t.Iterable["Token"]]:
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
to filter tokens returned. This method has to return an iterable of
:class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
:class:`~jinja2.lexer.TokenStream`.
"""
return stream
def parse(self, parser: "Parser") -> nodes.Node | list[nodes.Node]:
"""If any of the :attr:`tags` matched this method is called with the
parser as first argument. The token the parser stream is pointing at
is the name token that matched. This method has to return one or a
list of multiple nodes.
"""
raise NotImplementedError()
def attr(self, name: str, lineno: int | None = None) -> nodes.ExtensionAttribute:
"""Return an attribute node for the current extension. This is useful
to pass constants on extensions to generated template code.
::
self.attr('_my_attribute', lineno=lineno)
"""
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
def call_method(
self,
name: str,
args: list[nodes.Expr] | None = None,
kwargs: list[nodes.Keyword] | None = None,
dyn_args: nodes.Expr | None = None,
dyn_kwargs: nodes.Expr | None = None,
lineno: int | None = None,
) -> nodes.Call:
"""Call a method of the extension. This is a shortcut for
:meth:`attr` + :class:`jinja2.nodes.Call`.
"""
if args is None:
args = []
if kwargs is None:
kwargs = []
return nodes.Call(
self.attr(name, lineno=lineno),
args,
kwargs,
dyn_args,
dyn_kwargs,
lineno=lineno,
)
@pass_context
def _gettext_alias(
__context: Context, *args: t.Any, **kwargs: t.Any
) -> t.Any | Undefined:
return __context.call(__context.resolve("gettext"), *args, **kwargs)
def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]:
@pass_context
def gettext(__context: Context, __string: str, **variables: t.Any) -> str:
rv = __context.call(func, __string)
if __context.eval_ctx.autoescape:
rv = Markup(rv)
# Always treat as a format string, even if there are no
# variables. This makes translation strings more consistent
# and predictable. This requires escaping
return rv % variables # type: ignore
return gettext
def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]:
@pass_context
def ngettext(
__context: Context,
__singular: str,
__plural: str,
__num: int,
**variables: t.Any,
) -> str:
variables.setdefault("num", __num)
rv = __context.call(func, __singular, __plural, __num)
if __context.eval_ctx.autoescape:
rv = Markup(rv)
# Always treat as a format string, see gettext comment above.
return rv % variables # type: ignore
return ngettext
def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]:
@pass_context
def pgettext(
__context: Context, __string_ctx: str, __string: str, **variables: t.Any
) -> str:
variables.setdefault("context", __string_ctx)
rv = __context.call(func, __string_ctx, __string)
if __context.eval_ctx.autoescape:
rv = Markup(rv)
# Always treat as a format string, see gettext comment above.
return rv % variables # type: ignore
return pgettext
def _make_new_npgettext(
func: t.Callable[[str, str, str, int], str],
) -> t.Callable[..., str]:
@pass_context
def npgettext(
__context: Context,
__string_ctx: str,
__singular: str,
__plural: str,
__num: int,
**variables: t.Any,
) -> str:
variables.setdefault("context", __string_ctx)
variables.setdefault("num", __num)
rv = __context.call(func, __string_ctx, __singular, __plural, __num)
if __context.eval_ctx.autoescape:
rv = Markup(rv)
# Always treat as a format string, see gettext comment above.
return rv % variables # type: ignore
return npgettext
class InternationalizationExtension(Extension):
"""This extension adds gettext support to Jinja."""
tags = {"trans"}
# TODO: the i18n extension is currently reevaluating values in a few
# situations. Take this example:
# {% trans count=something() %}{{ count }} foo{% pluralize
# %}{{ count }} fooss{% endtrans %}
# something is called twice here. One time for the gettext value and
# the other time for the n-parameter of the ngettext function.
def __init__(self, environment: Environment) -> None:
super().__init__(environment)
environment.globals["_"] = _gettext_alias
environment.extend(
install_gettext_translations=self._install,
install_null_translations=self._install_null,
install_gettext_callables=self._install_callables,
uninstall_gettext_translations=self._uninstall,
extract_translations=self._extract,
newstyle_gettext=False,
)
def _install(
self, translations: "_SupportedTranslations", newstyle: bool | None = None
) -> None:
# ugettext and ungettext are preferred in case the I18N library
# is providing compatibility with older Python versions.
gettext = getattr(translations, "ugettext", None)
if gettext is None:
gettext = translations.gettext
ngettext = getattr(translations, "ungettext", None)
if ngettext is None:
ngettext = translations.ngettext
pgettext = getattr(translations, "pgettext", None)
npgettext = getattr(translations, "npgettext", None)
self._install_callables(
gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
)
def _install_null(self, newstyle: bool | None = None) -> None:
import gettext
translations = gettext.NullTranslations()
self._install_callables(
gettext=translations.gettext,
ngettext=translations.ngettext,
newstyle=newstyle,
pgettext=translations.pgettext,
npgettext=translations.npgettext,
)
def _install_callables(
self,
gettext: t.Callable[[str], str],
ngettext: t.Callable[[str, str, int], str],
newstyle: bool | None = None,
pgettext: t.Callable[[str, str], str] | None = None,
npgettext: t.Callable[[str, str, str, int], str] | None = None,
) -> None:
if newstyle is not None:
self.environment.newstyle_gettext = newstyle # type: ignore
if self.environment.newstyle_gettext: # type: ignore
gettext = _make_new_gettext(gettext)
ngettext = _make_new_ngettext(ngettext)
if pgettext is not None:
pgettext = _make_new_pgettext(pgettext)
if npgettext is not None:
npgettext = _make_new_npgettext(npgettext)
self.environment.globals.update(
gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext
)
def _uninstall(self, translations: "_SupportedTranslations") -> None:
for key in ("gettext", "ngettext", "pgettext", "npgettext"):
self.environment.globals.pop(key, None)
def _extract(
self,
source: str | nodes.Template,
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...]]]:
if isinstance(source, str):
source = self.environment.parse(source)
return extract_from_ast(source, gettext_functions)
def parse(self, parser: "Parser") -> nodes.Node | list[nodes.Node]:
"""Parse a translatable tag."""
lineno = next(parser.stream).lineno
context = None
context_token = parser.stream.next_if("string")
if context_token is not None:
context = context_token.value
# find all the variables referenced. Additionally a variable can be
# defined in the body of the trans block too, but this is checked at
# a later state.
plural_expr: nodes.Expr | None = None
plural_expr_assignment: nodes.Assign | None = None
num_called_num = False
variables: dict[str, nodes.Expr] = {}
trimmed = None
while parser.stream.current.type != "block_end":
if variables:
parser.stream.expect("comma")
# skip colon for python compatibility
if parser.stream.skip_if("colon"):
break
token = parser.stream.expect("name")
if token.value in variables:
parser.fail(
f"translatable variable {token.value!r} defined twice.",
token.lineno,
exc=TemplateAssertionError,
)
# expressions
if parser.stream.current.type == "assign":
next(parser.stream)
variables[token.value] = var = parser.parse_expression()
elif trimmed is None and token.value in ("trimmed", "notrimmed"):
trimmed = token.value == "trimmed"
continue
else:
variables[token.value] = var = nodes.Name(token.value, "load")
if plural_expr is None:
if isinstance(var, nodes.Call):
plural_expr = nodes.Name("_trans", "load")
variables[token.value] = plural_expr
plural_expr_assignment = nodes.Assign(
nodes.Name("_trans", "store"), var
)
else:
plural_expr = var
num_called_num = token.value == "num"
parser.stream.expect("block_end")
plural = None
have_plural = False
referenced = set()
# now parse until endtrans or pluralize
singular_names, singular = self._parse_block(parser, True)
if singular_names:
referenced.update(singular_names)
if plural_expr is None:
plural_expr = nodes.Name(singular_names[0], "load")
num_called_num = singular_names[0] == "num"
# if we have a pluralize block, we parse that too
if parser.stream.current.test("name:pluralize"):
have_plural = True
next(parser.stream)
if parser.stream.current.type != "block_end":
token = parser.stream.expect("name")
if token.value not in variables:
parser.fail(
f"unknown variable {token.value!r} for pluralization",
token.lineno,
exc=TemplateAssertionError,
)
plural_expr = variables[token.value]
num_called_num = token.value == "num"
parser.stream.expect("block_end")
plural_names, plural = self._parse_block(parser, False)
next(parser.stream)
referenced.update(plural_names)
else:
next(parser.stream)
# register free names as simple name expressions
for name in referenced:
if name not in variables:
variables[name] = nodes.Name(name, "load")
if not have_plural:
plural_expr = None
elif plural_expr is None:
parser.fail("pluralize without variables", lineno)
if trimmed is None:
trimmed = self.environment.policies["ext.i18n.trimmed"]
if trimmed:
singular = self._trim_whitespace(singular)
if plural:
plural = self._trim_whitespace(plural)
node = self._make_node(
singular,
plural,
context,
variables,
plural_expr,
bool(referenced),
num_called_num and have_plural,
)
node.set_lineno(lineno)
if plural_expr_assignment is not None:
return [plural_expr_assignment, node]
else:
return node
def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str:
return _ws_re.sub(" ", string.strip())
def _parse_block(
self, parser: "Parser", allow_pluralize: bool
) -> tuple[list[str], str]:
"""Parse until the next block tag with a given name."""
referenced = []
buf = []
while True:
if parser.stream.current.type == "data":
buf.append(parser.stream.current.value.replace("%", "%%"))
next(parser.stream)
elif parser.stream.current.type == "variable_begin":
next(parser.stream)
name = parser.stream.expect("name").value
referenced.append(name)
buf.append(f"%({name})s")
parser.stream.expect("variable_end")
elif parser.stream.current.type == "block_begin":
next(parser.stream)
block_name = (
parser.stream.current.value
if parser.stream.current.type == "name"
else None
)
if block_name == "endtrans":
break
elif block_name == "pluralize":
if allow_pluralize:
break
parser.fail(
"a translatable section can have only one pluralize section"
)
elif block_name == "trans":
parser.fail(
"trans blocks can't be nested; did you mean `endtrans`?"
)
parser.fail(
f"control structures in translatable sections are not allowed; "
f"saw `{block_name}`"
)
elif parser.stream.eos:
parser.fail("unclosed translation block")
else:
raise RuntimeError("internal parser error")
return referenced, concat(buf)
def _make_node(
self,
singular: str,
plural: str | None,
context: str | None,
variables: dict[str, nodes.Expr],
plural_expr: nodes.Expr | None,
vars_referenced: bool,
num_called_num: bool,
) -> nodes.Output:
"""Generates a useful node from the data provided."""
newstyle = self.environment.newstyle_gettext # type: ignore
node: nodes.Expr
# no variables referenced? no need to escape for old style
# gettext invocations only if there are vars.
if not vars_referenced and not newstyle:
singular = singular.replace("%%", "%")
if plural:
plural = plural.replace("%%", "%")
func_name = "gettext"
func_args: list[nodes.Expr] = [nodes.Const(singular)]
if context is not None:
func_args.insert(0, nodes.Const(context))
func_name = f"p{func_name}"
if plural_expr is not None:
func_name = f"n{func_name}"
func_args.extend((nodes.Const(plural), plural_expr))
node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)
# in case newstyle gettext is used, the method is powerful
# enough to handle the variable expansion and autoescape
# handling itself
if newstyle:
for key, value in variables.items():
# the function adds that later anyways in case num was
# called num, so just skip it.
if num_called_num and key == "num":
continue
node.kwargs.append(nodes.Keyword(key, value))
# otherwise do that here
else:
# mark the return value as safe if we are in an
# environment with autoescaping turned on
node = nodes.MarkSafeIfAutoescape(node)
if variables:
node = nodes.Mod(
node,
nodes.Dict(
[
nodes.Pair(nodes.Const(key), value)
for key, value in variables.items()
]
),
)
return nodes.Output([node])
class ExprStmtExtension(Extension):
"""Adds a `do` tag to Jinja that works like the print statement just
that it doesn't print the return value.
"""
tags = {"do"}
def parse(self, parser: "Parser") -> nodes.ExprStmt:
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
node.node = parser.parse_tuple()
return node
class LoopControlExtension(Extension):
"""Adds break and continue to the template engine."""
tags = {"break", "continue"}
def parse(self, parser: "Parser") -> nodes.Break | nodes.Continue:
token = next(parser.stream)
if token.value == "break":
return nodes.Break(lineno=token.lineno)
return nodes.Continue(lineno=token.lineno)
class DebugExtension(Extension):
"""A ``{% debug %}`` tag that dumps the available variables,
filters, and tests.
.. code-block:: html+jinja
{% debug %}
.. code-block:: text
{'context': {'cycler': ,
...,
'namespace': },
'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
.. versionadded:: 2.11.0
"""
tags = {"debug"}
def parse(self, parser: "Parser") -> nodes.Output:
lineno = parser.stream.expect("name:debug").lineno
context = nodes.ContextReference()
result = self.call_method("_render", [context], lineno=lineno)
return nodes.Output([result], lineno=lineno)
def _render(self, context: Context) -> str:
result = {
"context": context.get_all(),
"filters": sorted(self.environment.filters.keys()),
"tests": sorted(self.environment.tests.keys()),
}
# Set the depth since the intent is to show the top few names.
return pprint.pformat(result, depth=3, compact=True)
def extract_from_ast(
ast: nodes.Template,
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
babel_style: bool = True,
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...]]]:
"""Extract localizable strings from the given template node. Per
default this function returns matches in babel style that means non string
parameters as well as keyword arguments are returned as `None`. This
allows Babel to figure out what you really meant if you are using
gettext functions that allow keyword arguments for placeholder expansion.
If you don't want that behavior set the `babel_style` parameter to `False`
which causes only strings to be returned and parameters are always stored
in tuples. As a consequence invalid gettext calls (calls without a single
string parameter or string parameters after non-string parameters) are
skipped.
This example explains the behavior:
>>> from jinja2 import Environment
>>> env = Environment()
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
>>> list(extract_from_ast(node))
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
>>> list(extract_from_ast(node, babel_style=False))
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
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), and
* ``message`` is the string, or a tuple of strings for functions
with multiple string arguments.
This extraction function operates on the AST and is because of that unable
to extract any comments. For comment support you have to use the babel
extraction interface or extract comments yourself.
"""
out: str | None | tuple[str | None, ...]
for node in ast.find_all(nodes.Call):
if (
not isinstance(node.node, nodes.Name)
or node.node.name not in gettext_functions
):
continue
strings: list[str | None] = []
for arg in node.args:
if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
strings.append(arg.value)
else:
strings.append(None)
for _ in node.kwargs:
strings.append(None)
if node.dyn_args is not None:
strings.append(None)
if node.dyn_kwargs is not None:
strings.append(None)
if not babel_style:
out = tuple(x for x in strings if x is not None)
if not out:
continue
else:
if len(strings) == 1:
out = strings[0]
else:
out = tuple(strings)
yield node.lineno, node.node.name, out
class _CommentFinder:
"""Helper class to find comments in a token stream. Can only
find comments for gettext calls forwards. Once the comment
from line 4 is found, a comment for line 1 will not return a
usable value.
"""
def __init__(
self, tokens: t.Sequence[tuple[int, str, str]], comment_tags: t.Sequence[str]
) -> None:
self.tokens = tokens
self.comment_tags = comment_tags
self.offset = 0
self.last_lineno = 0
def find_backwards(self, offset: int) -> list[str]:
try:
for _, token_type, token_value in reversed(
self.tokens[self.offset : offset]
):
if token_type in ("comment", "linecomment"):
try:
prefix, comment = token_value.split(None, 1)
except ValueError:
continue
if prefix in self.comment_tags:
return [comment.rstrip()]
return []
finally:
self.offset = offset
def find_comments(self, lineno: int) -> list[str]:
if not self.comment_tags or self.last_lineno > lineno:
return []
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
if token_lineno > lineno:
return self.find_backwards(self.offset + idx)
return self.find_backwards(len(self.tokens))
def babel_extract(
fileobj: t.BinaryIO,
keywords: t.Sequence[str],
comment_tags: t.Sequence[str],
options: dict[str, t.Any],
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...], list[str]]]:
"""Babel extraction method for Jinja templates.
.. versionchanged:: 2.3
Basic support for translation comments was added. If `comment_tags`
is now set to a list of keywords for extraction, the extractor will
try to find the best preceding comment that begins with one of the
keywords. For best results, make sure to not have more than one
gettext call in one line of code and the matching comment in the
same line or the line before.
.. versionchanged:: 2.5.1
The `newstyle_gettext` flag can be set to `True` to enable newstyle
gettext calls.
.. versionchanged:: 2.7
A `silent` option can now be provided. If set to `False` template
syntax errors are propagated instead of being ignored.
:param fileobj: the file-like object the messages should be extracted from
:param keywords: a list of keywords (i.e. function names) that should be
recognized as translation functions
:param comment_tags: a list of translator tags to search for and include
in the results.
:param options: a dictionary of additional options (optional)
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
(comments will be empty currently)
"""
extensions: dict[type[Extension], None] = {}
for extension_name in options.get("extensions", "").split(","):
extension_name = extension_name.strip()
if not extension_name:
continue
extensions[import_string(extension_name)] = None
if InternationalizationExtension not in extensions:
extensions[InternationalizationExtension] = None
def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool:
return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"}
silent = getbool(options, "silent", True)
environment = Environment(
options.get("block_start_string", defaults.BLOCK_START_STRING),
options.get("block_end_string", defaults.BLOCK_END_STRING),
options.get("variable_start_string", defaults.VARIABLE_START_STRING),
options.get("variable_end_string", defaults.VARIABLE_END_STRING),
options.get("comment_start_string", defaults.COMMENT_START_STRING),
options.get("comment_end_string", defaults.COMMENT_END_STRING),
options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX,
options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX,
getbool(options, "trim_blocks", defaults.TRIM_BLOCKS),
getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS),
defaults.NEWLINE_SEQUENCE,
getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE),
tuple(extensions),
cache_size=0,
auto_reload=False,
)
if getbool(options, "trimmed"):
environment.policies["ext.i18n.trimmed"] = True
if getbool(options, "newstyle_gettext"):
environment.newstyle_gettext = True # type: ignore
source = fileobj.read().decode(options.get("encoding", "utf-8"))
try:
node = environment.parse(source)
tokens = list(environment.lex(environment.preprocess(source)))
except TemplateSyntaxError:
if not silent:
raise
# skip templates with syntax errors
return
finder = _CommentFinder(tokens, comment_tags)
for lineno, func, message in extract_from_ast(node, keywords):
yield lineno, func, message, finder.find_comments(lineno)
#: nicer import names
i18n = InternationalizationExtension
do = ExprStmtExtension
loopcontrols = LoopControlExtension
debug = DebugExtension
================================================
FILE: src/jinja2/filters.py
================================================
"""Built-in template filters used with the ``|`` operator."""
import math
import random
import re
import typing
import typing as t
from collections import abc
from inspect import getattr_static
from itertools import chain
from itertools import groupby
from markupsafe import escape
from markupsafe import Markup
from markupsafe import soft_str
from .async_utils import async_variant
from .async_utils import auto_aiter
from .async_utils import auto_await
from .async_utils import auto_to_list
from .exceptions import FilterArgumentError
from .runtime import Undefined
from .utils import htmlsafe_json_dumps
from .utils import pass_context
from .utils import pass_environment
from .utils import pass_eval_context
from .utils import pformat
from .utils import url_quote
from .utils import urlize
if t.TYPE_CHECKING:
import typing_extensions as te
from .environment import Environment
from .nodes import EvalContext
from .runtime import Context
from .sandbox import SandboxedEnvironment # noqa: F401
class HasHTML(te.Protocol):
def __html__(self) -> str:
pass
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
K = t.TypeVar("K")
V = t.TypeVar("V")
def ignore_case(value: V) -> V:
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
to lowercase and returns other types as-is."""
if isinstance(value, str):
return t.cast(V, value.lower())
return value
def make_attrgetter(
environment: "Environment",
attribute: str | int | None,
postprocess: t.Callable[[t.Any], t.Any] | None = None,
default: t.Any | None = None,
) -> t.Callable[[t.Any], t.Any]:
"""Returns a callable that looks up the given attribute from a
passed object with the rules of the environment. Dots are allowed
to access attributes of attributes. Integer parts in paths are
looked up as integers.
"""
parts = _prepare_attribute_parts(attribute)
def attrgetter(item: t.Any) -> t.Any:
for part in parts:
item = environment.getitem(item, part)
if default is not None and isinstance(item, Undefined):
item = default
if postprocess is not None:
item = postprocess(item)
return item
return attrgetter
def make_multi_attrgetter(
environment: "Environment",
attribute: str | int | None,
postprocess: t.Callable[[t.Any], t.Any] | None = None,
) -> t.Callable[[t.Any], list[t.Any]]:
"""Returns a callable that looks up the given comma separated
attributes from a passed object with the rules of the environment.
Dots are allowed to access attributes of each attribute. Integer
parts in paths are looked up as integers.
The value returned by the returned callable is a list of extracted
attribute values.
Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
"""
if isinstance(attribute, str):
split: t.Sequence[str | int | None] = attribute.split(",")
else:
split = [attribute]
parts = [_prepare_attribute_parts(item) for item in split]
def attrgetter(item: t.Any) -> list[t.Any]:
items = [None] * len(parts)
for i, attribute_part in enumerate(parts):
item_i = item
for part in attribute_part:
item_i = environment.getitem(item_i, part)
if postprocess is not None:
item_i = postprocess(item_i)
items[i] = item_i
return items
return attrgetter
def _prepare_attribute_parts(
attr: str | int | None,
) -> list[str | int]:
if attr is None:
return []
if isinstance(attr, str):
return [int(x) if x.isdigit() else x for x in attr.split(".")]
return [attr]
def do_forceescape(value: "str | HasHTML") -> Markup:
"""Enforce HTML escaping. This will probably double escape variables."""
if hasattr(value, "__html__"):
value = t.cast("HasHTML", value).__html__()
return escape(str(value))
def do_urlencode(
value: str | t.Mapping[str, t.Any] | t.Iterable[tuple[str, t.Any]],
) -> str:
"""Quote data for use in a URL path or query using UTF-8.
Basic wrapper around :func:`urllib.parse.quote` when given a
string, or :func:`urllib.parse.urlencode` for a dict or iterable.
:param value: Data to quote. A string will be quoted directly. A
dict or iterable of ``(key, value)`` pairs will be joined as a
query string.
When given a string, "/" is not quoted. HTTP servers treat "/" and
"%2F" equivalently in paths. If you need quoted slashes, use the
``|replace("/", "%2F")`` filter.
.. versionadded:: 2.7
"""
if isinstance(value, str) or not isinstance(value, abc.Iterable):
return url_quote(value)
if isinstance(value, dict):
items: t.Iterable[tuple[str, t.Any]] = value.items()
else:
items = value # type: ignore
return "&".join(
f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items
)
@pass_eval_context
def do_replace(
eval_ctx: "EvalContext", s: str, old: str, new: str, count: int | None = None
) -> str:
"""Return a copy of the value with all occurrences of a substring
replaced with a new one. The first argument is the substring
that should be replaced, the second is the replacement string.
If the optional third argument ``count`` is given, only the first
``count`` occurrences are replaced:
.. sourcecode:: jinja
{{ "Hello World"|replace("Hello", "Goodbye") }}
-> Goodbye World
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
-> d'oh, d'oh, aaargh
"""
if count is None:
count = -1
if not eval_ctx.autoescape:
return str(s).replace(str(old), str(new), count)
if (
hasattr(old, "__html__")
or hasattr(new, "__html__")
and not hasattr(s, "__html__")
):
s = escape(s)
else:
s = soft_str(s)
return s.replace(soft_str(old), soft_str(new), count)
def do_upper(s: str) -> str:
"""Convert a value to uppercase."""
return soft_str(s).upper()
def do_lower(s: str) -> str:
"""Convert a value to lowercase."""
return soft_str(s).lower()
def do_items(value: t.Mapping[K, V] | Undefined) -> t.Iterator[tuple[K, V]]:
"""Return an iterator over the ``(key, value)`` items of a mapping.
``x|items`` is the same as ``x.items()``, except if ``x`` is
undefined an empty iterator is returned.
This filter is useful if you expect the template to be rendered with
an implementation of Jinja in another programming language that does
not have a ``.items()`` method on its mapping type.
.. code-block:: html+jinja
{% for key, value in my_dict|items %}
{{ key }}
{{ value }}
{% endfor %}
.. versionadded:: 3.1
"""
if isinstance(value, Undefined):
return
if not isinstance(value, abc.Mapping):
raise TypeError("Can only get item pairs from a mapping.")
yield from value.items()
# Check for characters that would move the parser state from key to value.
# https://html.spec.whatwg.org/#attribute-name-state
_attr_key_re = re.compile(r"[\s/>=]", flags=re.ASCII)
@pass_eval_context
def do_xmlattr(
eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
) -> str:
"""Create an SGML/XML attribute string based on the items in a dict.
**Values** that are neither ``none`` nor ``undefined`` are automatically
escaped, safely allowing untrusted user input.
User input should not be used as **keys** to this filter. If any key
contains a space, ``/`` solidus, ``>`` greater-than sign, or ``=`` equals
sign, this fails with a ``ValueError``. Regardless of this, user input
should never be used as keys to this filter, or must be separately validated
first.
.. sourcecode:: html+jinja
...
Results in something like this:
.. sourcecode:: html
...
As you can see it automatically prepends a space in front of the item
if the filter returned something unless the second parameter is false.
.. versionchanged:: 3.1.4
Keys with ``/`` solidus, ``>`` greater-than sign, or ``=`` equals sign
are not allowed.
.. versionchanged:: 3.1.3
Keys with spaces are not allowed.
"""
items = []
for key, value in d.items():
if value is None or isinstance(value, Undefined):
continue
if _attr_key_re.search(key) is not None:
raise ValueError(f"Invalid character in attribute name: {key!r}")
items.append(f'{escape(key)}="{escape(value)}"')
rv = " ".join(items)
if autospace and rv:
rv = " " + rv
if eval_ctx.autoescape:
rv = Markup(rv)
return rv
def do_capitalize(s: str) -> str:
"""Capitalize a value. The first character will be uppercase, all others
lowercase.
"""
return soft_str(s).capitalize()
_word_beginning_split_re = re.compile(r"([-\s({\[<]+)")
def do_title(s: str) -> str:
"""Return a titlecased version of the value. I.e. words will start with
uppercase letters, all remaining characters are lowercase.
"""
return "".join(
[
item[0].upper() + item[1:].lower()
for item in _word_beginning_split_re.split(soft_str(s))
if item
]
)
def do_dictsort(
value: t.Mapping[K, V],
case_sensitive: bool = False,
by: 'te.Literal["key", "value"]' = "key",
reverse: bool = False,
) -> list[tuple[K, V]]:
"""Sort a dict and yield (key, value) pairs. Python dicts may not
be in the order you want to display them in, so sort them first.
.. sourcecode:: jinja
{% for key, value in mydict|dictsort %}
sort the dict by key, case insensitive
{% for key, value in mydict|dictsort(reverse=true) %}
sort the dict by key, case insensitive, reverse order
{% for key, value in mydict|dictsort(true) %}
sort the dict by key, case sensitive
{% for key, value in mydict|dictsort(false, 'value') %}
sort the dict by value, case insensitive
"""
if by == "key":
pos = 0
elif by == "value":
pos = 1
else:
raise FilterArgumentError('You can only sort by either "key" or "value"')
def sort_func(item: tuple[t.Any, t.Any]) -> t.Any:
value = item[pos]
if not case_sensitive:
value = ignore_case(value)
return value
return sorted(value.items(), key=sort_func, reverse=reverse)
@pass_environment
def do_sort(
environment: "Environment",
value: "t.Iterable[V]",
reverse: bool = False,
case_sensitive: bool = False,
attribute: str | int | None = None,
) -> "list[V]":
"""Sort an iterable using Python's :func:`sorted`.
.. sourcecode:: jinja
{% for city in cities|sort %}
...
{% endfor %}
:param reverse: Sort descending instead of ascending.
:param case_sensitive: When sorting strings, sort upper and lower
case separately.
:param attribute: When sorting objects or dicts, an attribute or
key to sort by. Can use dot notation like ``"address.city"``.
Can be a list of attributes like ``"age,name"``.
The sort is stable, it does not change the relative order of
elements that compare equal. This makes it is possible to chain
sorts on different attributes and ordering.
.. sourcecode:: jinja
{% for user in users|sort(attribute="name")
|sort(reverse=true, attribute="age") %}
...
{% endfor %}
As a shortcut to chaining when the direction is the same for all
attributes, pass a comma separate list of attributes.
.. sourcecode:: jinja
{% for user in users|sort(attribute="age,name") %}
...
{% endfor %}
.. versionchanged:: 2.11.0
The ``attribute`` parameter can be a comma separated list of
attributes, e.g. ``"age,name"``.
.. versionchanged:: 2.6
The ``attribute`` parameter was added.
"""
key_func = make_multi_attrgetter(
environment, attribute, postprocess=ignore_case if not case_sensitive else None
)
return sorted(value, key=key_func, reverse=reverse)
@pass_environment
def sync_do_unique(
environment: "Environment",
value: "t.Iterable[V]",
case_sensitive: bool = False,
attribute: str | int | None = None,
) -> "t.Iterator[V]":
"""Returns a list of unique items from the given iterable.
.. sourcecode:: jinja
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }}
-> ['foo', 'bar', 'foobar']
The unique items are yielded in the same order as their first occurrence in
the iterable passed to the filter.
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Filter objects with unique values for this attribute.
"""
getter = make_attrgetter(
environment, attribute, postprocess=ignore_case if not case_sensitive else None
)
seen = set()
for item in value:
key = getter(item)
if key not in seen:
seen.add(key)
yield item
@async_variant(sync_do_unique) # type: ignore
async def do_unique(
environment: "Environment",
value: "t.AsyncIterable[V] | t.Iterable[V]",
case_sensitive: bool = False,
attribute: str | int | None = None,
) -> "t.Iterator[V]":
return sync_do_unique(
environment, await auto_to_list(value), case_sensitive, attribute
)
def _min_or_max(
environment: "Environment",
value: "t.Iterable[V]",
func: "t.Callable[..., V]",
case_sensitive: bool,
attribute: str | int | None,
) -> "V | Undefined":
it = iter(value)
try:
first = next(it)
except StopIteration:
return environment.undefined("No aggregated item, sequence was empty.")
key_func = make_attrgetter(
environment, attribute, postprocess=ignore_case if not case_sensitive else None
)
return func(chain([first], it), key=key_func)
@pass_environment
def do_min(
environment: "Environment",
value: "t.Iterable[V]",
case_sensitive: bool = False,
attribute: str | int | None = None,
) -> "V | Undefined":
"""Return the smallest item from the sequence.
.. sourcecode:: jinja
{{ [1, 2, 3]|min }}
-> 1
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Get the object with the min value of this attribute.
"""
return _min_or_max(environment, value, min, case_sensitive, attribute)
@pass_environment
def do_max(
environment: "Environment",
value: "t.Iterable[V]",
case_sensitive: bool = False,
attribute: str | int | None = None,
) -> "V | Undefined":
"""Return the largest item from the sequence.
.. sourcecode:: jinja
{{ [1, 2, 3]|max }}
-> 3
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Get the object with the max value of this attribute.
"""
return _min_or_max(environment, value, max, case_sensitive, attribute)
def do_default(
value: V,
default_value: V = "", # type: ignore
boolean: bool = False,
) -> V:
"""If the value is undefined it will return the passed default value,
otherwise the value of the variable:
.. sourcecode:: jinja
{{ my_variable|default('my_variable is not defined') }}
This will output the value of ``my_variable`` if the variable was
defined, otherwise ``'my_variable is not defined'``. If you want
to use default with variables that evaluate to false you have to
set the second parameter to `true`:
.. sourcecode:: jinja
{{ ''|default('the string was empty', true) }}
.. versionchanged:: 2.11
It's now possible to configure the :class:`~jinja2.Environment` with
:class:`~jinja2.ChainableUndefined` to make the `default` filter work
on nested elements and attributes that may contain undefined values
in the chain without getting an :exc:`~jinja2.UndefinedError`.
"""
if isinstance(value, Undefined) or (boolean and not value):
return default_value
return value
@pass_eval_context
def sync_do_join(
eval_ctx: "EvalContext",
value: t.Iterable[t.Any],
d: str = "",
attribute: str | int | None = None,
) -> str:
"""Return a string which is the concatenation of the strings in the
sequence. The separator between elements is an empty string per
default, you can define it with the optional parameter:
.. sourcecode:: jinja
{{ [1, 2, 3]|join('|') }}
-> 1|2|3
{{ [1, 2, 3]|join }}
-> 123
It is also possible to join certain attributes of an object:
.. sourcecode:: jinja
{{ users|join(', ', attribute='username') }}
.. versionadded:: 2.6
The `attribute` parameter was added.
"""
if attribute is not None:
value = map(make_attrgetter(eval_ctx.environment, attribute), value)
# no automatic escaping? joining is a lot easier then
if not eval_ctx.autoescape:
return str(d).join(map(str, value))
# if the delimiter doesn't have an html representation we check
# if any of the items has. If yes we do a coercion to Markup
if not hasattr(d, "__html__"):
value = list(value)
do_escape = False
for idx, item in enumerate(value):
if hasattr(item, "__html__"):
do_escape = True
else:
value[idx] = str(item)
if do_escape:
d = escape(d)
else:
d = str(d)
return d.join(value)
# no html involved, to normal joining
return soft_str(d).join(map(soft_str, value))
@async_variant(sync_do_join) # type: ignore
async def do_join(
eval_ctx: "EvalContext",
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
d: str = "",
attribute: str | int | None = None,
) -> str:
return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute)
def do_center(value: str, width: int = 80) -> str:
"""Centers the value in a field of a given width."""
return soft_str(value).center(width)
@pass_environment
def sync_do_first(environment: "Environment", seq: "t.Iterable[V]") -> "V | Undefined":
"""Return the first item of a sequence."""
try:
return next(iter(seq))
except StopIteration:
return environment.undefined("No first item, sequence was empty.")
@async_variant(sync_do_first) # type: ignore
async def do_first(
environment: "Environment", seq: "t.AsyncIterable[V] | t.Iterable[V]"
) -> "V | Undefined":
try:
return await auto_aiter(seq).__anext__()
except StopAsyncIteration:
return environment.undefined("No first item, sequence was empty.")
@pass_environment
def do_last(environment: "Environment", seq: "t.Reversible[V]") -> "V | Undefined":
"""Return the last item of a sequence.
Note: Does not work with generators. You may want to explicitly
convert it to a list:
.. sourcecode:: jinja
{{ data | selectattr('name', '==', 'Jinja') | list | last }}
"""
try:
return next(iter(reversed(seq)))
except StopIteration:
return environment.undefined("No last item, sequence was empty.")
# No async do_last, it may not be safe in async mode.
@pass_context
def do_random(context: "Context", seq: "t.Sequence[V]") -> "V | Undefined":
"""Return a random item from the sequence."""
try:
return random.choice(seq)
except IndexError:
return context.environment.undefined("No random item, sequence was empty.")
def do_filesizeformat(value: str | float | int, binary: bool = False) -> str:
"""Format the value like a 'human-readable' file size (i.e. 13 kB,
4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
Giga, etc.), if the second parameter is set to `True` the binary
prefixes are used (Mebi, Gibi).
"""
bytes = float(value)
base = 1024 if binary else 1000
prefixes = [
("KiB" if binary else "kB"),
("MiB" if binary else "MB"),
("GiB" if binary else "GB"),
("TiB" if binary else "TB"),
("PiB" if binary else "PB"),
("EiB" if binary else "EB"),
("ZiB" if binary else "ZB"),
("YiB" if binary else "YB"),
]
if bytes == 1:
return "1 Byte"
elif bytes < base:
return f"{int(bytes)} Bytes"
else:
for i, prefix in enumerate(prefixes):
unit = base ** (i + 2)
if bytes < unit:
return f"{base * bytes / unit:.1f} {prefix}"
return f"{base * bytes / unit:.1f} {prefix}"
def do_pprint(value: t.Any) -> str:
"""Pretty print a variable. Useful for debugging."""
return pformat(value)
_uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$")
@pass_eval_context
def do_urlize(
eval_ctx: "EvalContext",
value: str,
trim_url_limit: int | None = None,
nofollow: bool = False,
target: str | None = None,
rel: str | None = None,
extra_schemes: t.Iterable[str] | None = None,
) -> str:
"""Convert URLs in text into clickable links.
This may not recognize links in some situations. Usually, a more
comprehensive formatter, such as a Markdown library, is a better
choice.
Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
addresses. Links with trailing punctuation (periods, commas, closing
parentheses) and leading punctuation (opening parentheses) are
recognized excluding the punctuation. Email addresses that include
header fields are not recognized (for example,
``mailto:address@example.com?cc=copy@example.com``).
:param value: Original text containing URLs to link.
:param trim_url_limit: Shorten displayed URL values to this length.
:param nofollow: Add the ``rel=nofollow`` attribute to links.
:param target: Add the ``target`` attribute to links.
:param rel: Add the ``rel`` attribute to links.
:param extra_schemes: Recognize URLs that start with these schemes
in addition to the default behavior. Defaults to
``env.policies["urlize.extra_schemes"]``, which defaults to no
extra schemes.
.. versionchanged:: 3.0
The ``extra_schemes`` parameter was added.
.. versionchanged:: 3.0
Generate ``https://`` links for URLs without a scheme.
.. versionchanged:: 3.0
The parsing rules were updated. Recognize email addresses with
or without the ``mailto:`` scheme. Validate IP addresses. Ignore
parentheses and brackets in more cases.
.. versionchanged:: 2.8
The ``target`` parameter was added.
"""
policies = eval_ctx.environment.policies
rel_parts = set((rel or "").split())
if nofollow:
rel_parts.add("nofollow")
rel_parts.update((policies["urlize.rel"] or "").split())
rel = " ".join(sorted(rel_parts)) or None
if target is None:
target = policies["urlize.target"]
if extra_schemes is None:
extra_schemes = policies["urlize.extra_schemes"] or ()
for scheme in extra_schemes:
if _uri_scheme_re.fullmatch(scheme) is None:
raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.")
rv = urlize(
value,
trim_url_limit=trim_url_limit,
rel=rel,
target=target,
extra_schemes=extra_schemes,
)
if eval_ctx.autoescape:
rv = Markup(rv)
return rv
def do_indent(
s: str, width: int | str = 4, first: bool = False, blank: bool = False
) -> str:
"""Return a copy of the string with each line indented by 4 spaces. The
first line and blank lines are not indented by default.
:param width: Number of spaces, or a string, to indent by.
:param first: Don't skip indenting the first line.
:param blank: Don't skip indenting empty lines.
.. versionchanged:: 3.0
``width`` can be a string.
.. versionchanged:: 2.10
Blank lines are not indented by default.
Rename the ``indentfirst`` argument to ``first``.
"""
if isinstance(width, str):
indention = width
else:
indention = " " * width
newline = "\n"
if isinstance(s, Markup):
indention = Markup(indention)
newline = Markup(newline)
s += newline # this quirk is necessary for splitlines method
if blank:
rv = (newline + indention).join(s.splitlines())
else:
lines = s.splitlines()
rv = lines.pop(0)
if lines:
rv += newline + newline.join(
indention + line if line else line for line in lines
)
if first:
rv = indention + rv
return rv
@pass_environment
def do_truncate(
env: "Environment",
s: str,
length: int = 255,
killwords: bool = False,
end: str = "...",
leeway: int | None = None,
) -> str:
"""Return a truncated copy of the string. The length is specified
with the first parameter which defaults to ``255``. If the second
parameter is ``true`` the filter will cut the text at length. Otherwise
it will discard the last word. If the text was in fact
truncated it will append an ellipsis sign (``"..."``). If you want a
different ellipsis sign than ``"..."`` you can specify it using the
third parameter. Strings that only exceed the length by the tolerance
margin given in the fourth parameter will not be truncated.
.. sourcecode:: jinja
{{ "foo bar baz qux"|truncate(9) }}
-> "foo..."
{{ "foo bar baz qux"|truncate(9, True) }}
-> "foo ba..."
{{ "foo bar baz qux"|truncate(11) }}
-> "foo bar baz qux"
{{ "foo bar baz qux"|truncate(11, False, '...', 0) }}
-> "foo bar..."
The default leeway on newer Jinja versions is 5 and was 0 before but
can be reconfigured globally.
"""
if leeway is None:
leeway = env.policies["truncate.leeway"]
assert length >= len(end), f"expected length >= {len(end)}, got {length}"
assert leeway >= 0, f"expected leeway >= 0, got {leeway}"
if len(s) <= length + leeway:
return s
if killwords:
return s[: length - len(end)] + end
result = s[: length - len(end)].rsplit(" ", 1)[0]
return result + end
@pass_environment
def do_wordwrap(
environment: "Environment",
s: str,
width: int = 79,
break_long_words: bool = True,
wrapstring: str | None = None,
break_on_hyphens: bool = True,
) -> str:
"""Wrap a string to the given width. Existing newlines are treated
as paragraphs to be wrapped separately.
:param s: Original text to wrap.
:param width: Maximum length of wrapped lines.
:param break_long_words: If a word is longer than ``width``, break
it across lines.
:param break_on_hyphens: If a word contains hyphens, it may be split
across lines.
:param wrapstring: String to join each wrapped line. Defaults to
:attr:`Environment.newline_sequence`.
.. versionchanged:: 2.11
Existing newlines are treated as paragraphs wrapped separately.
.. versionchanged:: 2.11
Added the ``break_on_hyphens`` parameter.
.. versionchanged:: 2.7
Added the ``wrapstring`` parameter.
"""
import textwrap
if wrapstring is None:
wrapstring = environment.newline_sequence
# textwrap.wrap doesn't consider existing newlines when wrapping.
# If the string has a newline before width, wrap will still insert
# a newline at width, resulting in a short line. Instead, split and
# wrap each paragraph individually.
return wrapstring.join(
[
wrapstring.join(
textwrap.wrap(
line,
width=width,
expand_tabs=False,
replace_whitespace=False,
break_long_words=break_long_words,
break_on_hyphens=break_on_hyphens,
)
)
for line in s.splitlines()
]
)
_word_re = re.compile(r"\w+")
def do_wordcount(s: str) -> int:
"""Count the words in that string."""
return len(_word_re.findall(soft_str(s)))
def do_int(value: t.Any, default: int = 0, base: int = 10) -> int:
"""Convert the value into an integer. If the
conversion doesn't work it will return ``0``. You can
override this default using the first parameter. You
can also override the default base (10) in the second
parameter, which handles input with prefixes such as
0b, 0o and 0x for bases 2, 8 and 16 respectively.
The base is ignored for decimal numbers and non-string values.
"""
try:
if isinstance(value, str):
return int(value, base)
return int(value)
except (TypeError, ValueError):
# this quirk is necessary so that "42.23"|int gives 42.
try:
return int(float(value))
except (TypeError, ValueError, OverflowError):
return default
def do_float(value: t.Any, default: float = 0.0) -> float:
"""Convert the value into a floating point number. If the
conversion doesn't work it will return ``0.0``. You can
override this default using the first parameter.
"""
try:
return float(value)
except (TypeError, ValueError):
return default
def do_format(value: str, *args: t.Any, **kwargs: t.Any) -> str:
"""Apply the given values to a `printf-style`_ format string, like
``string % values``.
.. sourcecode:: jinja
{{ "%s, %s!"|format(greeting, name) }}
Hello, World!
In most cases it should be more convenient and efficient to use the
``%`` operator or :meth:`str.format`.
.. code-block:: text
{{ "%s, %s!" % (greeting, name) }}
{{ "{}, {}!".format(greeting, name) }}
.. _printf-style: https://docs.python.org/library/stdtypes.html
#printf-style-string-formatting
"""
if args and kwargs:
raise FilterArgumentError(
"can't handle positional and keyword arguments at the same time"
)
return soft_str(value) % (kwargs or args)
def do_trim(value: str, chars: str | None = None) -> str:
"""Strip leading and trailing characters, by default whitespace."""
return soft_str(value).strip(chars)
def do_striptags(value: "str | HasHTML") -> str:
"""Strip SGML/XML tags and replace adjacent whitespace by one space."""
if hasattr(value, "__html__"):
value = t.cast("HasHTML", value).__html__()
return Markup(str(value)).striptags()
def sync_do_slice(
value: "t.Collection[V]", slices: int, fill_with: "V | None" = None
) -> "t.Iterator[list[V]]":
"""Slice an iterator and return a list of lists containing
those items. Useful if you want to create a div containing
three ul tags that represent columns:
.. sourcecode:: html+jinja
{%- for column in items|slice(3) %}
{%- for item in column %}
{{ item }}
{%- endfor %}
{%- endfor %}
If you pass it a second argument it's used to fill missing
values on the last iteration.
"""
seq = list(value)
length = len(seq)
items_per_slice = length // slices
slices_with_extra = length % slices
offset = 0
for slice_number in range(slices):
start = offset + slice_number * items_per_slice
if slice_number < slices_with_extra:
offset += 1
end = offset + (slice_number + 1) * items_per_slice
tmp = seq[start:end]
if fill_with is not None and slice_number >= slices_with_extra:
tmp.append(fill_with)
yield tmp
@async_variant(sync_do_slice) # type: ignore
async def do_slice(
value: "t.AsyncIterable[V] | t.Iterable[V]",
slices: int,
fill_with: t.Any | None = None,
) -> "t.Iterator[list[V]]":
return sync_do_slice(await auto_to_list(value), slices, fill_with)
def do_batch(
value: "t.Iterable[V]", linecount: int, fill_with: "V | None" = None
) -> "t.Iterator[list[V]]":
"""
A filter that batches items. It works pretty much like `slice`
just the other way round. It returns a list of lists with the
given number of items. If you provide a second parameter this
is used to fill up missing items. See this example:
.. sourcecode:: html+jinja
{%- for row in items|batch(3, ' ') %}
{%- for column in row %}
{{ column }}
{%- endfor %}
{%- endfor %}
"""
tmp: list[V] = []
for item in value:
if len(tmp) == linecount:
yield tmp
tmp = []
tmp.append(item)
if tmp:
if fill_with is not None and len(tmp) < linecount:
tmp += [fill_with] * (linecount - len(tmp))
yield tmp
def do_round(
value: float,
precision: int = 0,
method: 'te.Literal["common", "ceil", "floor"]' = "common",
) -> float:
"""Round the number to a given precision. The first
parameter specifies the precision (default is ``0``), the
second the rounding method:
- ``'common'`` rounds either up or down
- ``'ceil'`` always rounds up
- ``'floor'`` always rounds down
If you don't specify a method ``'common'`` is used.
.. sourcecode:: jinja
{{ 42.55|round }}
-> 43.0
{{ 42.55|round(1, 'floor') }}
-> 42.5
Note that even if rounded to 0 precision, a float is returned. If
you need a real integer, pipe it through `int`:
.. sourcecode:: jinja
{{ 42.55|round|int }}
-> 43
"""
if method not in {"common", "ceil", "floor"}:
raise FilterArgumentError("method must be common, ceil or floor")
if method == "common":
return round(value, precision)
func = getattr(math, method)
return t.cast(float, func(value * (10**precision)) / (10**precision))
class _GroupTuple(t.NamedTuple):
grouper: t.Any
list: list[t.Any]
# Use the regular tuple repr to hide this subclass if users print
# out the value during debugging.
def __repr__(self) -> str:
return tuple.__repr__(self)
def __str__(self) -> str:
return tuple.__str__(self)
@pass_environment
def sync_do_groupby(
environment: "Environment",
value: "t.Iterable[V]",
attribute: str | int,
default: t.Any | None = None,
case_sensitive: bool = False,
) -> "list[_GroupTuple]":
"""Group a sequence of objects by an attribute using Python's
:func:`itertools.groupby`. The attribute can use dot notation for
nested access, like ``"address.city"``. Unlike Python's ``groupby``,
the values are sorted first so only one group is returned for each
unique value.
For example, a list of ``User`` objects with a ``city`` attribute
can be rendered in groups. In this example, ``grouper`` refers to
the ``city`` value of the group.
.. sourcecode:: html+jinja
{% for city, items in users|groupby("city") %}
{{ city }}
{% for user in items %}
{{ user.name }}
{% endfor %}
{% endfor %}
``groupby`` yields namedtuples of ``(grouper, list)``, which
can be used instead of the tuple unpacking above. ``grouper`` is the
value of the attribute, and ``list`` is the items with that value.
.. sourcecode:: html+jinja
You can specify a ``default`` value to use if an object in the list
does not have the given attribute.
.. sourcecode:: jinja
{% for city, items in users|groupby("city", default="NY") %}
{{ city }}: {{ items|map(attribute="name")|join(", ") }}
{% endfor %}
Like the :func:`~jinja-filters.sort` filter, sorting and grouping is
case-insensitive by default. The ``key`` for each group will have
the case of the first item in that group of values. For example, if
a list of users has cities ``["CA", "NY", "ca"]``, the "CA" group
will have two values. This can be disabled by passing
``case_sensitive=True``.
.. versionchanged:: 3.1
Added the ``case_sensitive`` parameter. Sorting and grouping is
case-insensitive by default, matching other filters that do
comparisons.
.. versionchanged:: 3.0
Added the ``default`` parameter.
.. versionchanged:: 2.6
The attribute supports dot notation for nested access.
"""
expr = make_attrgetter(
environment,
attribute,
postprocess=ignore_case if not case_sensitive else None,
default=default,
)
out = [
_GroupTuple(key, list(values))
for key, values in groupby(sorted(value, key=expr), expr)
]
if not case_sensitive:
# Return the real key from the first value instead of the lowercase key.
output_expr = make_attrgetter(environment, attribute, default=default)
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out]
return out
@async_variant(sync_do_groupby) # type: ignore
async def do_groupby(
environment: "Environment",
value: "t.AsyncIterable[V] | t.Iterable[V]",
attribute: str | int,
default: t.Any | None = None,
case_sensitive: bool = False,
) -> "list[_GroupTuple]":
expr = make_attrgetter(
environment,
attribute,
postprocess=ignore_case if not case_sensitive else None,
default=default,
)
out = [
_GroupTuple(key, await auto_to_list(values))
for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr)
]
if not case_sensitive:
# Return the real key from the first value instead of the lowercase key.
output_expr = make_attrgetter(environment, attribute, default=default)
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out]
return out
@pass_environment
def sync_do_sum(
environment: "Environment",
iterable: "t.Iterable[V]",
attribute: str | int | None = None,
start: V = 0, # type: ignore
) -> V:
"""Returns the sum of a sequence of numbers plus the value of parameter
'start' (which defaults to 0). When the sequence is empty it returns
start.
It is also possible to sum up only certain attributes:
.. sourcecode:: jinja
Total: {{ items|sum(attribute='price') }}
.. versionchanged:: 2.6
The ``attribute`` parameter was added to allow summing up over
attributes. Also the ``start`` parameter was moved on to the right.
"""
if attribute is not None:
iterable = map(make_attrgetter(environment, attribute), iterable)
return sum(iterable, start) # type: ignore[no-any-return, call-overload]
@async_variant(sync_do_sum) # type: ignore
async def do_sum(
environment: "Environment",
iterable: "t.AsyncIterable[V] | t.Iterable[V]",
attribute: str | int | None = None,
start: V = 0, # type: ignore
) -> V:
rv = start
if attribute is not None:
func = make_attrgetter(environment, attribute)
else:
def func(x: V) -> V:
return x
async for item in auto_aiter(iterable):
rv += func(item)
return rv
def sync_do_list(value: "t.Iterable[V]") -> "list[V]":
"""Convert the value into a list. If it was a string the returned list
will be a list of characters.
"""
return list(value)
@async_variant(sync_do_list) # type: ignore
async def do_list(value: "t.AsyncIterable[V] | t.Iterable[V]") -> "list[V]":
return await auto_to_list(value)
def do_mark_safe(value: str) -> Markup:
"""Mark the value as safe which means that in an environment with automatic
escaping enabled this variable will not be escaped.
"""
return Markup(value)
def do_mark_unsafe(value: str) -> str:
"""Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
return str(value)
@typing.overload
def do_reverse(value: str) -> str: ...
@typing.overload
def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]": ...
def do_reverse(value: str | t.Iterable[V]) -> str | t.Iterable[V]:
"""Reverse the object or return an iterator that iterates over it the other
way round.
"""
if isinstance(value, str):
return value[::-1]
try:
return reversed(value) # type: ignore
except TypeError:
try:
rv = list(value)
rv.reverse()
return rv
except TypeError as e:
raise FilterArgumentError("argument must be iterable") from e
@pass_environment
def do_attr(environment: "Environment", obj: t.Any, name: str) -> Undefined | t.Any:
"""Get an attribute of an object. ``foo|attr("bar")`` works like
``foo.bar``, but returns undefined instead of falling back to ``foo["bar"]``
if the attribute doesn't exist.
See :ref:`Notes on subscriptions ` for more details.
"""
# Environment.getattr will fall back to obj[name] if obj.name doesn't exist.
# But we want to call env.getattr to get behavior such as sandboxing.
# Determine if the attr exists first, so we know the fallback won't trigger.
try:
# This avoids executing properties/descriptors, but misses __getattr__
# and __getattribute__ dynamic attrs.
getattr_static(obj, name)
except AttributeError:
# This finds dynamic attrs, and we know it's not a descriptor at this point.
if not hasattr(obj, name):
return environment.undefined(obj=obj, name=name)
return environment.getattr(obj, name)
@typing.overload
def sync_do_map(
context: "Context",
value: t.Iterable[t.Any],
name: str,
*args: t.Any,
**kwargs: t.Any,
) -> t.Iterable[t.Any]: ...
@typing.overload
def sync_do_map(
context: "Context",
value: t.Iterable[t.Any],
*,
attribute: str = ...,
default: t.Any | None = None,
) -> t.Iterable[t.Any]: ...
@pass_context
def sync_do_map(
context: "Context", value: t.Iterable[t.Any], *args: t.Any, **kwargs: t.Any
) -> t.Iterable[t.Any]:
"""Applies a filter on a sequence of objects or looks up an attribute.
This is useful when dealing with lists of objects but you are really
only interested in a certain value of it.
The basic usage is mapping on an attribute. Imagine you have a list
of users but you are only interested in a list of usernames:
.. sourcecode:: jinja
Users on this page: {{ users|map(attribute='username')|join(', ') }}
You can specify a ``default`` value to use if an object in the list
does not have the given attribute.
.. sourcecode:: jinja
{{ users|map(attribute="username", default="Anonymous")|join(", ") }}
Alternatively you can let it invoke a filter by passing the name of the
filter and the arguments afterwards. A good example would be applying a
text conversion filter on a sequence:
.. sourcecode:: jinja
Users on this page: {{ titles|map('lower')|join(', ') }}
Similar to a generator comprehension such as:
.. code-block:: python
(u.username for u in users)
(getattr(u, "username", "Anonymous") for u in users)
(do_lower(x) for x in titles)
.. versionchanged:: 2.11.0
Added the ``default`` parameter.
.. versionadded:: 2.7
"""
if value:
func = prepare_map(context, args, kwargs)
for item in value:
yield func(item)
@typing.overload
def do_map(
context: "Context",
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
name: str,
*args: t.Any,
**kwargs: t.Any,
) -> t.Iterable[t.Any]: ...
@typing.overload
def do_map(
context: "Context",
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
*,
attribute: str = ...,
default: t.Any | None = None,
) -> t.Iterable[t.Any]: ...
@async_variant(sync_do_map) # type: ignore
async def do_map(
context: "Context",
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
*args: t.Any,
**kwargs: t.Any,
) -> t.AsyncIterable[t.Any]:
if value:
func = prepare_map(context, args, kwargs)
async for item in auto_aiter(value):
yield await auto_await(func(item))
@pass_context
def sync_do_select(
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
) -> "t.Iterator[V]":
"""Filters a sequence of objects by applying a test to each object,
and only selecting the objects with the test succeeding.
If no test is specified, each object will be evaluated as a boolean.
Example usage:
.. sourcecode:: jinja
{{ numbers|select("odd") }}
{{ numbers|select("odd") }}
{{ numbers|select("divisibleby", 3) }}
{{ numbers|select("lessthan", 42) }}
{{ strings|select("equalto", "mystring") }}
Similar to a generator comprehension such as:
.. code-block:: python
(n for n in numbers if test_odd(n))
(n for n in numbers if test_divisibleby(n, 3))
.. versionadded:: 2.7
"""
return select_or_reject(context, value, args, kwargs, lambda x: x, False)
@async_variant(sync_do_select) # type: ignore
async def do_select(
context: "Context",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
return async_select_or_reject(context, value, args, kwargs, lambda x: x, False)
@pass_context
def sync_do_reject(
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
) -> "t.Iterator[V]":
"""Filters a sequence of objects by applying a test to each object,
and rejecting the objects with the test succeeding.
If no test is specified, each object will be evaluated as a boolean.
Example usage:
.. sourcecode:: jinja
{{ numbers|reject("odd") }}
Similar to a generator comprehension such as:
.. code-block:: python
(n for n in numbers if not test_odd(n))
.. versionadded:: 2.7
"""
return select_or_reject(context, value, args, kwargs, lambda x: not x, False)
@async_variant(sync_do_reject) # type: ignore
async def do_reject(
context: "Context",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
return async_select_or_reject(context, value, args, kwargs, lambda x: not x, False)
@pass_context
def sync_do_selectattr(
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
) -> "t.Iterator[V]":
"""Filters a sequence of objects by applying a test to the specified
attribute of each object, and only selecting the objects with the
test succeeding.
If no test is specified, the attribute's value will be evaluated as
a boolean.
Example usage:
.. sourcecode:: jinja
{{ users|selectattr("is_active") }}
{{ users|selectattr("email", "none") }}
Similar to a generator comprehension such as:
.. code-block:: python
(user for user in users if user.is_active)
(user for user in users if test_none(user.email))
.. versionadded:: 2.7
"""
return select_or_reject(context, value, args, kwargs, lambda x: x, True)
@async_variant(sync_do_selectattr) # type: ignore
async def do_selectattr(
context: "Context",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
return async_select_or_reject(context, value, args, kwargs, lambda x: x, True)
@pass_context
def sync_do_rejectattr(
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
) -> "t.Iterator[V]":
"""Filters a sequence of objects by applying a test to the specified
attribute of each object, and rejecting the objects with the test
succeeding.
If no test is specified, the attribute's value will be evaluated as
a boolean.
.. sourcecode:: jinja
{{ users|rejectattr("is_active") }}
{{ users|rejectattr("email", "none") }}
Similar to a generator comprehension such as:
.. code-block:: python
(user for user in users if not user.is_active)
(user for user in users if not test_none(user.email))
.. versionadded:: 2.7
"""
return select_or_reject(context, value, args, kwargs, lambda x: not x, True)
@async_variant(sync_do_rejectattr) # type: ignore
async def do_rejectattr(
context: "Context",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
return async_select_or_reject(context, value, args, kwargs, lambda x: not x, True)
@pass_eval_context
def do_tojson(
eval_ctx: "EvalContext", value: t.Any, indent: int | None = None
) -> Markup:
"""Serialize an object to a string of JSON, and mark it safe to
render in HTML. This filter is only for use in HTML documents.
The returned string is safe to render in HTML documents and
``