'merge upstream/3.1.3'

This commit is contained in:
tangtingting 2025-06-26 13:22:49 +08:00
parent deaad03a73
commit cc7dc937df
36 changed files with 556 additions and 171 deletions

View File

@ -1,5 +1,17 @@
.. currentmodule:: jinja2
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:`1916`
Version 3.1.2
-------------

View File

@ -1,10 +1,8 @@
Metadata-Version: 2.1
Name: Jinja2
Version: 3.1.2
Version: 3.1.3
Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
@ -13,9 +11,7 @@ Project-URL: Documentation, https://jinja.palletsprojects.com/
Project-URL: Changes, https://jinja.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/jinja/
Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
@ -26,8 +22,10 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
Provides-Extra: i18n
License-File: LICENSE.rst
Requires-Dist: MarkupSafe>=2.0
Provides-Extra: i18n
Requires-Dist: Babel>=2.7; extra == "i18n"
Jinja
=====
@ -104,8 +102,4 @@ Links
- PyPI Releases: https://pypi.org/project/Jinja2/
- Source Code: https://github.com/pallets/jinja/
- Issue Tracker: https://github.com/pallets/jinja/issues/
- Website: https://palletsprojects.com/p/jinja/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

View File

@ -73,6 +73,4 @@ Links
- PyPI Releases: https://pypi.org/project/Jinja2/
- Source Code: https://github.com/pallets/jinja/
- Issue Tracker: https://github.com/pallets/jinja/issues/
- Website: https://palletsprojects.com/p/jinja/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
jinja2 (3.1.3-ok1) nile; urgency=medium
Initial release for 3.1.3
-- tangtingting <tangtingting@kylinos.cn> Thu, 26 Jun 2025 13:22:48 +0800
jinja2 (3.1.2-ok2) nile; urgency=medium
* SECURITY UPDATE: Cross-Site scripting in xmlattr filter -

View File

@ -0,0 +1,22 @@
From: =?utf-8?q?Piotr_O=C5=BCarowski?= <piotr@debian.org>
Date: Thu, 2 Apr 2020 13:41:14 +0200
Subject: docs: disable sphinxcontrib.log_cabinet
it's not packaged in Debian yet
---
docs/conf.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/conf.py b/docs/conf.py
index f65d462..43bcfda 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -15,7 +15,7 @@ extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"pallets_sphinx_themes",
- "sphinxcontrib.log_cabinet",
+ # "sphinxcontrib.log_cabinet",
"sphinx_issues",
]
autodoc_typehints = "description"

View File

@ -0,0 +1,38 @@
From: =?utf-8?q?Piotr_O=C5=BCarowski?= <piotr@debian.org>
Date: Fri, 24 Feb 2023 16:06:57 +0100
Subject: fix nose leftovers
taken from upstream repo
---
tests/test_loader.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 04c921d..77d686e 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -183,6 +183,7 @@ class TestFileSystemLoader:
class TestModuleLoader:
archive = None
+ mod_env = None
def compile_down(self, prefix_loader, zip="deflated"):
log = []
@@ -196,13 +197,14 @@ class TestModuleLoader:
self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
return "".join(log)
- def teardown(self):
- if hasattr(self, "mod_env"):
+ def teardown_method(self):
+ if self.archive is not None:
if os.path.isfile(self.archive):
os.remove(self.archive)
else:
shutil.rmtree(self.archive)
self.archive = None
+ self.mod_env = None
def test_log(self, prefix_loader):
log = self.compile_down(prefix_loader)

View File

@ -0,0 +1,103 @@
From: liubo0711 <1191322237@qq.com>
Date: Mon, 4 Nov 2024 16:45:25 +0800
Subject: SECURITY UPDATE
---
CHANGES.rst | 6 ++++++
src/jinja2/filters.py | 22 +++++++++++++++++-----
tests/test_filters.py | 11 ++++++-----
3 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index fede06e..6fd1262 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,6 +9,12 @@ Released 2022-04-28
:issue:`1645`
- Handle race condition in ``FileSystemBytecodeCache``. :issue:`1654`
+- 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.1
-------------
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index c7ecc9b..bdf6f22 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -248,7 +248,9 @@ def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K
yield from value.items()
-_space_re = re.compile(r"\s", flags=re.ASCII)
+# 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
@@ -257,8 +259,14 @@ def do_xmlattr(
) -> str:
"""Create an SGML/XML attribute string based on the items in a dict.
- If any key contains a space, this fails with a ``ValueError``. Values that
- are neither ``none`` nor ``undefined`` are automatically escaped.
+ **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
@@ -278,6 +286,10 @@ def do_xmlattr(
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.
"""
@@ -287,8 +299,8 @@ def do_xmlattr(
if value is None or isinstance(value, Undefined):
continue
- if _space_re.search(key) is not None:
- raise ValueError(f"Spaces are not allowed in attributes: '{key}'")
+ 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)}"')
diff --git a/tests/test_filters.py b/tests/test_filters.py
index a184649..c9ec7da 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -474,11 +474,12 @@ class TestFilter:
assert 'bar="23"' in out
assert 'blub:blub="&lt;?&gt;"' in out
- def test_xmlattr_key_with_spaces(self, env):
- with pytest.raises(ValueError, match="Spaces are not allowed"):
- env.from_string(
- "{{ {'src=1 onerror=alert(1)': 'my_class'}|xmlattr }}"
- ).render()
+ @pytest.mark.parametrize("sep", ("\t", "\n", "\f", " ", "/", ">", "="))
+ def test_xmlattr_key_invalid(self, env: Environment, sep: str) -> None:
+ with pytest.raises(ValueError, match="Invalid character"):
+ env.from_string("{{ {key: 'my_class'}|xmlattr }}").render(
+ key=f"class{sep}onclick=alert(1)"
+ )
def test_sort1(self, env):
tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}")

78
debian/patches/CVE-2024-22195.patch vendored Normal file
View File

@ -0,0 +1,78 @@
From: Calum Hutton <calum.hutton@snyk.io>
Date: Thu, 26 Oct 2023 12:08:53 +0100
Subject: [PATCH] xmlattr filter disallows keys with spaces
---
src/jinja2/filters.py | 28 +++++++++++++++++++++-------
tests/test_filters.py | 6 ++++++
2 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index ed07c4c..c7ecc9b 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -248,13 +248,17 @@ def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K
yield from value.items()
+_space_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.
- All values that are neither `none` nor `undefined` are automatically
- escaped:
+
+ If any key contains a space, this fails with a ``ValueError``. Values that
+ are neither ``none`` nor ``undefined`` are automatically escaped.
.. sourcecode:: html+jinja
@@ -273,12 +277,22 @@ def do_xmlattr(
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.3
+ Keys with spaces are not allowed.
"""
- rv = " ".join(
- f'{escape(key)}="{escape(value)}"'
- for key, value in d.items()
- if value is not None and not isinstance(value, Undefined)
- )
+ items = []
+
+ for key, value in d.items():
+ if value is None or isinstance(value, Undefined):
+ continue
+
+ if _space_re.search(key) is not None:
+ raise ValueError(f"Spaces are not allowed in attributes: '{key}'")
+
+ items.append(f'{escape(key)}="{escape(value)}"')
+
+ rv = " ".join(items)
if autospace and rv:
rv = " " + rv
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 73f0f0b..a184649 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -474,6 +474,12 @@ class TestFilter:
assert 'bar="23"' in out
assert 'blub:blub="&lt;?&gt;"' in out
+ def test_xmlattr_key_with_spaces(self, env):
+ with pytest.raises(ValueError, match="Spaces are not allowed"):
+ env.from_string(
+ "{{ {'src=1 onerror=alert(1)': 'my_class'}|xmlattr }}"
+ ).render()
+
def test_sort1(self, env):
tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}")
assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]"

View File

@ -0,0 +1,104 @@
From: Thomas Goirand <zigo@debian.org>
Date: Wed, 1 Apr 2020 14:08:47 +0200
Subject: Python 3.9: fix collections import
Bug-Debian: https://bugs.debian.org/949018
Forwarded: no
Last-Update: 2020-02-27
As collections has moved to collections.abc, this produces a warning which
may lead to unit testing errors: this is the case when building Rally.
This patch attempts to import from collections.abc, and if it fails, falls
back to collections. This should be harmless.
Note that this patch is probably useless with future version, as hopefully,
upstream will fix it (I didn't check). However, I didn't dare upgrading the
package to the major upstream release 3.x.
---
src/jinja2/lexer.py | 5 ++++-
src/jinja2/nodes.py | 5 ++++-
src/jinja2/sandbox.py | 5 ++++-
src/jinja2/utils.py | 5 ++++-
tests/test_utils.py | 5 ++++-
5 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/src/jinja2/lexer.py b/src/jinja2/lexer.py
index aff7e9f..37ef342 100644
--- a/src/jinja2/lexer.py
+++ b/src/jinja2/lexer.py
@@ -6,7 +6,10 @@ template code and python code in expressions.
import re
import typing as t
from ast import literal_eval
-from collections import deque
+try:
+ from collections.abc import deque
+except ImportError:
+ from collections import deque
from sys import intern
from ._identifier import pattern as name_re
diff --git a/src/jinja2/nodes.py b/src/jinja2/nodes.py
index b2f88d9..53da1e9 100644
--- a/src/jinja2/nodes.py
+++ b/src/jinja2/nodes.py
@@ -5,7 +5,10 @@ to normalize nodes.
import inspect
import operator
import typing as t
-from collections import deque
+try:
+ from collections.abc import deque
+except ImportError:
+ from collections import deque
from markupsafe import Markup
diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py
index 06d7414..f443c18 100644
--- a/src/jinja2/sandbox.py
+++ b/src/jinja2/sandbox.py
@@ -6,7 +6,10 @@ import types
import typing as t
from _string import formatter_field_name_split # type: ignore
from collections import abc
-from collections import deque
+try:
+ from collections.abc import deque
+except ImportError:
+ from collections import deque
from string import Formatter
from markupsafe import EscapeFormatter
diff --git a/src/jinja2/utils.py b/src/jinja2/utils.py
index 9b5f5a5..205b2af 100644
--- a/src/jinja2/utils.py
+++ b/src/jinja2/utils.py
@@ -4,7 +4,10 @@ import os
import re
import typing as t
from collections import abc
-from collections import deque
+try:
+ from collections.abc import deque
+except ImportError:
+ from collections import deque
from random import choice
from random import randrange
from threading import Lock
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 7b58af1..9013d7c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,6 +1,9 @@
import pickle
import random
-from collections import deque
+try:
+ from collections.abc import deque
+except ImportError:
+ from collections import deque
from copy import copy as shallow_copy
import pytest

View File

@ -751,8 +751,8 @@ Now it can be used in templates:
{% endif %}
Some decorators are available to tell Jinja to pass extra information to
the filter. The object is passed as the first argument, making the value
being filtered the second argument.
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`.

View File

@ -32,8 +32,6 @@ html_context = {
ProjectLink("PyPI Releases", "https://pypi.org/project/Jinja2/"),
ProjectLink("Source Code", "https://github.com/pallets/jinja/"),
ProjectLink("Issue Tracker", "https://github.com/pallets/jinja/issues/"),
ProjectLink("Website", "https://palletsprojects.com/p/jinja/"),
ProjectLink("Twitter", "https://twitter.com/PalletsTeam"),
ProjectLink("Chat", "https://discord.gg/pallets"),
]
}

View File

@ -91,4 +91,4 @@ this add this to ``config/environment.py``:
config['pylons.strict_c'] = True
.. _Pylons: https://pylonshq.com/
.. _Pylons: https://pylonsproject.org/

View File

@ -84,7 +84,7 @@ In Django, the special variable for the loop context is called
.. code-block:: django
{% for item in items %}
{{ item }}
{{ forloop.counter }}. {{ item }}
{% empty %}
No items!
{% endfor %}
@ -95,7 +95,7 @@ and the ``else`` block is used for no loop items.
.. code-block:: jinja
{% for item in items %}
{{ loop.index}}. {{ item }}
{{ loop.index }}. {{ item }}
{% else %}
No items!
{% endfor %}

View File

@ -599,7 +599,8 @@ first and pass it in to ``render``.
else:
layout = env.get_template("layout.html")
user_detail = env.get_template("user/detail.html", layout=layout)
user_detail = env.get_template("user/detail.html")
return user_detail.render(layout=layout)
.. code-block:: jinja
@ -1014,6 +1015,9 @@ template data. Just wrap the code in the special `filter` section::
This text becomes uppercase
{% endfilter %}
Filters that accept arguments can be called like this::
{% filter center(100) %}Center this{% endfilter %}
.. _assignments:
@ -1168,7 +1172,7 @@ none of the templates exist.
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}
A variable, with either a template name or template object, can also be
passed to the statment.
passed to the statement.
.. _import:
@ -1607,8 +1611,7 @@ The following functions are available in the global scope by default:
.. versionadded:: 2.1
.. method:: current
:property:
.. property:: current
Return the current item. Equivalent to the item that will be
returned next time :meth:`next` is called.

15
requirements/build.txt Normal file
View File

@ -0,0 +1,15 @@
# SHA1:80754af91bfb6d1073585b046fe0a474ce868509
#
# This file is autogenerated by pip-compile-multi
# To update, run:
#
# pip-compile-multi
#
build==0.10.0
# via -r requirements/build.in
packaging==23.1
# via build
pyproject-hooks==1.0.0
# via build
tomli==2.0.1
# via build

View File

@ -8,51 +8,55 @@
-r docs.txt
-r tests.txt
-r typing.txt
build==0.10.0
# via pip-tools
cachetools==5.3.1
# via tox
cfgv==3.3.1
# via pre-commit
click==8.1.2
chardet==5.1.0
# via tox
click==8.1.3
# via
# pip-compile-multi
# pip-tools
distlib==0.3.4
colorama==0.4.6
# via tox
distlib==0.3.6
# via virtualenv
filelock==3.6.0
filelock==3.12.2
# via
# tox
# virtualenv
identify==2.5.0
identify==2.5.24
# via pre-commit
nodeenv==1.6.0
nodeenv==1.8.0
# via pre-commit
pep517==0.12.0
# via pip-tools
pip-compile-multi==2.4.5
pip-compile-multi==2.6.3
# via -r requirements/dev.in
pip-tools==6.6.0
pip-tools==6.13.0
# via pip-compile-multi
platformdirs==2.5.2
# via virtualenv
pre-commit==2.18.1
platformdirs==3.8.0
# via
# tox
# virtualenv
pre-commit==3.3.3
# via -r requirements/dev.in
pyproject-api==1.5.2
# via tox
pyproject-hooks==1.0.0
# via build
pyyaml==6.0
# via pre-commit
six==1.16.0
# via
# tox
# virtualenv
toml==0.10.2
# via
# pre-commit
# tox
toposort==1.7
toposort==1.10
# via pip-compile-multi
tox==3.25.0
tox==4.6.3
# via -r requirements/dev.in
virtualenv==20.14.1
virtualenv==20.23.1
# via
# pre-commit
# tox
wheel==0.37.1
wheel==0.40.0
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:

View File

@ -5,41 +5,37 @@
#
# pip-compile-multi
#
alabaster==0.7.12
alabaster==0.7.13
# via sphinx
babel==2.10.1
babel==2.12.1
# via sphinx
certifi==2021.10.8
certifi==2023.5.7
# via requests
charset-normalizer==2.0.12
charset-normalizer==3.1.0
# via requests
docutils==0.17.1
docutils==0.20.1
# via sphinx
idna==3.3
idna==3.4
# via requests
imagesize==1.3.0
imagesize==1.4.1
# via sphinx
jinja2==3.1.1
jinja2==3.1.2
# via sphinx
markupsafe==2.1.1
markupsafe==2.1.3
# via jinja2
packaging==21.3
packaging==23.1
# via
# pallets-sphinx-themes
# sphinx
pallets-sphinx-themes==2.0.2
pallets-sphinx-themes==2.1.1
# via -r requirements/docs.in
pygments==2.12.0
pygments==2.15.1
# via sphinx
pyparsing==3.0.8
# via packaging
pytz==2022.1
# via babel
requests==2.27.1
requests==2.31.0
# via sphinx
snowballstemmer==2.2.0
# via sphinx
sphinx==4.5.0
sphinx==7.0.1
# via
# -r requirements/docs.in
# pallets-sphinx-themes
@ -47,11 +43,11 @@ sphinx==4.5.0
# sphinxcontrib-log-cabinet
sphinx-issues==3.0.1
# via -r requirements/docs.in
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-applehelp==1.0.4
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-htmlhelp==2.0.1
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
@ -61,5 +57,5 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
urllib3==1.26.9
urllib3==2.0.3
# via requests

View File

@ -5,19 +5,15 @@
#
# pip-compile-multi
#
attrs==21.4.0
exceptiongroup==1.1.1
# via pytest
iniconfig==1.1.1
iniconfig==2.0.0
# via pytest
packaging==21.3
packaging==23.1
# via pytest
pluggy==1.0.0
pluggy==1.2.0
# via pytest
py==1.11.0
# via pytest
pyparsing==3.0.8
# via packaging
pytest==7.1.2
pytest==7.4.0
# via -r requirements/tests.in
tomli==2.0.1
# via pytest

View File

@ -5,11 +5,11 @@
#
# pip-compile-multi
#
mypy==0.950
mypy==1.4.1
# via -r requirements/typing.in
mypy-extensions==0.4.3
mypy-extensions==1.0.0
# via mypy
tomli==2.0.1
# via mypy
typing-extensions==4.2.0
typing-extensions==4.6.3
# via mypy

View File

@ -8,12 +8,9 @@ project_urls =
Changes = https://jinja.palletsprojects.com/changes/
Source Code = https://github.com/pallets/jinja/
Issue Tracker = https://github.com/pallets/jinja/issues/
Twitter = https://twitter.com/PalletsTeam
Chat = https://discord.gg/pallets
license = BSD-3-Clause
license_files = LICENSE.rst
author = Armin Ronacher
author_email = armin.ronacher@active-4.com
maintainer = Pallets
maintainer_email = contact@palletsprojects.com
description = A very fast and expressive template engine.
@ -46,7 +43,6 @@ babel.extractors =
testpaths = tests
filterwarnings =
error
ignore:The loop argument:DeprecationWarning:asyncio[.]base_events:542
[coverage:run]
branch = True
@ -59,17 +55,6 @@ source =
src
*/site-packages
[flake8]
select = B, E, F, W, B9, ISC
ignore =
E203
E501
E722
W503
max-line-length = 80
per-file-ignores =
src/jinja2/__init__.py: F401
[mypy]
files = src/jinja2
python_version = 3.7

View File

@ -1,10 +1,8 @@
Metadata-Version: 2.1
Name: Jinja2
Version: 3.1.2
Version: 3.1.3
Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
@ -13,9 +11,7 @@ Project-URL: Documentation, https://jinja.palletsprojects.com/
Project-URL: Changes, https://jinja.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/jinja/
Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
@ -26,8 +22,10 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
Provides-Extra: i18n
License-File: LICENSE.rst
Requires-Dist: MarkupSafe>=2.0
Provides-Extra: i18n
Requires-Dist: Babel>=2.7; extra == "i18n"
Jinja
=====
@ -104,8 +102,4 @@ Links
- PyPI Releases: https://pypi.org/project/Jinja2/
- Source Code: https://github.com/pallets/jinja/
- Issue Tracker: https://github.com/pallets/jinja/issues/
- Website: https://palletsprojects.com/p/jinja/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

View File

@ -35,6 +35,7 @@ examples/basic/test_loop_filter.py
examples/basic/translate.py
examples/basic/templates/broken.html
examples/basic/templates/subbroken.html
requirements/build.txt
requirements/dev.txt
requirements/docs.txt
requirements/tests.txt

View File

@ -34,4 +34,4 @@ 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
__version__ = "3.1.2"
__version__ = "3.1.3"

View File

@ -74,7 +74,7 @@ async def auto_aiter(
async for item in t.cast("t.AsyncIterable[V]", iterable):
yield item
else:
for item in t.cast("t.Iterable[V]", iterable):
for item in iterable:
yield item

View File

@ -993,7 +993,6 @@ class CodeGenerator(NodeVisitor):
# 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
@ -1407,7 +1406,7 @@ class CodeGenerator(NodeVisitor):
if pass_arg is None:
def finalize(value: t.Any) -> t.Any:
def finalize(value: t.Any) -> t.Any: # noqa: F811
return default(env_finalize(value))
else:
@ -1415,7 +1414,7 @@ class CodeGenerator(NodeVisitor):
if pass_arg == "environment":
def finalize(value: t.Any) -> t.Any:
def finalize(value: t.Any) -> t.Any: # noqa: F811
return default(env_finalize(self.environment, value))
self._finalize = self._FinalizeInfo(finalize, src)

View File

@ -701,7 +701,7 @@ class Environment:
.. versionadded:: 2.5
"""
return compile(source, filename, "exec") # type: ignore
return compile(source, filename, "exec")
@typing.overload
def compile( # type: ignore
@ -920,7 +920,7 @@ class Environment:
)
def filter_func(x: str) -> bool:
return "." in x and x.rsplit(".", 1)[1] in extensions # type: ignore
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)]
@ -1253,7 +1253,7 @@ class Template:
t.blocks = namespace["blocks"]
# render function and module
t.root_render_func = namespace["root"] # type: ignore
t.root_render_func = namespace["root"]
t._module = None
# debug and loader helpers
@ -1349,7 +1349,7 @@ class Template:
ctx = self.new_context(dict(*args, **kwargs))
try:
yield from self.root_render_func(ctx) # type: ignore
yield from self.root_render_func(ctx)
except Exception:
yield self.environment.handle_exception()
@ -1532,7 +1532,7 @@ class TemplateModule:
" API you are using."
)
body_stream = list(template.root_render_func(context)) # type: ignore
body_stream = list(template.root_render_func(context))
self._body_stream = body_stream
self.__dict__.update(context.get_exported())
@ -1564,7 +1564,7 @@ class TemplateExpression:
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
context = self._template.new_context(dict(*args, **kwargs))
consume(self._template.root_render_func(context)) # type: ignore
consume(self._template.root_render_func(context))
rv = context.vars["result"]
if self._undefined_to_none and isinstance(rv, Undefined):
rv = None

View File

@ -291,14 +291,14 @@ class InternationalizationExtension(Extension):
if hasattr(translations, "pgettext"):
# Python < 3.8
pgettext = translations.pgettext # type: ignore
pgettext = translations.pgettext
else:
def pgettext(c: str, s: str) -> str:
return s
if hasattr(translations, "npgettext"):
npgettext = translations.npgettext # type: ignore
npgettext = translations.npgettext
else:
def npgettext(c: str, s: str, p: str, n: int) -> str:
@ -495,16 +495,26 @@ class InternationalizationExtension(Extension):
parser.stream.expect("variable_end")
elif parser.stream.current.type == "block_begin":
next(parser.stream)
if parser.stream.current.test("name:endtrans"):
block_name = (
parser.stream.current.value
if parser.stream.current.type == "name"
else None
)
if block_name == "endtrans":
break
elif parser.stream.current.test("name:pluralize"):
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(
"control structures in translatable sections are not allowed"
f"control structures in translatable sections are not allowed; "
f"saw `{block_name}`"
)
elif parser.stream.eos:
parser.fail("unclosed translation block")

View File

@ -15,7 +15,6 @@ from types import ModuleType
from .exceptions import TemplateNotFound
from .utils import internalcode
from .utils import open_if_exists
if t.TYPE_CHECKING:
from .environment import Environment
@ -193,29 +192,30 @@ class FileSystemLoader(BaseLoader):
self, environment: "Environment", template: str
) -> t.Tuple[str, str, t.Callable[[], bool]]:
pieces = split_template_path(template)
for searchpath in self.searchpath:
# Use posixpath even on Windows to avoid "drive:" or UNC
# segments breaking out of the search directory.
filename = posixpath.join(searchpath, *pieces)
f = open_if_exists(filename)
if f is None:
continue
if os.path.isfile(filename):
break
else:
raise TemplateNotFound(template)
with open(filename, encoding=self.encoding) as f:
contents = f.read()
mtime = os.path.getmtime(filename)
def uptodate() -> bool:
try:
contents = f.read().decode(self.encoding)
finally:
f.close()
return os.path.getmtime(filename) == mtime
except OSError:
return False
mtime = os.path.getmtime(filename)
def uptodate() -> bool:
try:
return os.path.getmtime(filename) == mtime
except OSError:
return False
# Use normpath to convert Windows altsep to sep.
return contents, os.path.normpath(filename), uptodate
raise TemplateNotFound(template)
# Use normpath to convert Windows altsep to sep.
return contents, os.path.normpath(filename), uptodate
def list_templates(self) -> t.List[str]:
found = set()
@ -392,7 +392,7 @@ class PackageLoader(BaseLoader):
)
offset = len(prefix)
for name in self._loader._files.keys(): # type: ignore
for name in self._loader._files.keys():
# Find names under the templates directory that aren't directories.
if name.startswith(prefix) and name[-1] != os.path.sep:
results.append(name[offset:].replace(os.path.sep, "/"))

View File

@ -106,7 +106,7 @@ class NativeTemplate(Template):
try:
return self.environment_class.concat( # type: ignore
self.root_render_func(ctx) # type: ignore
self.root_render_func(ctx)
)
except Exception:
return self.environment.handle_exception()

View File

@ -311,12 +311,14 @@ class Parser:
# enforce that required blocks only contain whitespace or comments
# by asserting that the body, if not empty, is just TemplateData nodes
# with whitespace data
if node.required and not all(
isinstance(child, nodes.TemplateData) and child.data.isspace()
for body in node.body
for child in body.nodes # type: ignore
):
self.fail("Required blocks can only contain comments or whitespace")
if node.required:
for body_node in node.body:
if not isinstance(body_node, nodes.Output) or any(
not isinstance(output_node, nodes.TemplateData)
or not output_node.data.isspace()
for output_node in body_node.nodes
):
self.fail("Required blocks can only contain comments or whitespace")
self.stream.skip_if("name:" + node.name)
return node
@ -857,7 +859,7 @@ class Parser:
else:
args.append(None)
return nodes.Slice(lineno=lineno, *args)
return nodes.Slice(lineno=lineno, *args) # noqa: B026
def parse_call_args(self) -> t.Tuple:
token = self.stream.expect("lparen")

View File

@ -272,9 +272,9 @@ class Context:
# Allow callable classes to take a context
if (
hasattr(__obj, "__call__") # noqa: B004
and _PassArg.from_obj(__obj.__call__) is not None # type: ignore
and _PassArg.from_obj(__obj.__call__) is not None
):
__obj = __obj.__call__ # type: ignore
__obj = __obj.__call__
pass_arg = _PassArg.from_obj(__obj)
@ -927,9 +927,7 @@ def make_logging_undefined(
logger.addHandler(logging.StreamHandler(sys.stderr))
def _log_message(undef: Undefined) -> None:
logger.warning( # type: ignore
"Template variable warning: %s", undef._undefined_message
)
logger.warning("Template variable warning: %s", undef._undefined_message)
class LoggingUndefined(base): # type: ignore
__slots__ = ()

View File

@ -185,7 +185,7 @@ def object_type_repr(obj: t.Any) -> str:
def pformat(obj: t.Any) -> str:
"""Format an object using :func:`pprint.pformat`."""
from pprint import pformat # type: ignore
from pprint import pformat
return pformat(obj)
@ -262,7 +262,7 @@ def urlize(
if trim_url_limit is not None:
def trim_url(x: str) -> str:
if len(x) > trim_url_limit: # type: ignore
if len(x) > trim_url_limit:
return f"{x[:trim_url_limit]}..."
return x

View File

@ -7,6 +7,7 @@ from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import nodes
from jinja2 import pass_context
from jinja2 import TemplateSyntaxError
from jinja2.exceptions import TemplateAssertionError
from jinja2.ext import Extension
from jinja2.lexer import count_newlines
@ -468,6 +469,18 @@ class TestInternationalization:
(3, "npgettext", ("babel", "%(users)s user", "%(users)s users", None), []),
]
def test_nested_trans_error(self):
s = "{% trans %}foo{% trans %}{% endtrans %}"
with pytest.raises(TemplateSyntaxError) as excinfo:
i18n_env.from_string(s)
assert "trans blocks can't be nested" in str(excinfo.value)
def test_trans_block_error(self):
s = "{% trans %}foo{% wibble bar %}{% endwibble %}{% endtrans %}"
with pytest.raises(TemplateSyntaxError) as excinfo:
i18n_env.from_string(s)
assert "saw `wibble`" in str(excinfo.value)
class TestScope:
def test_basic_scope_behavior(self):

View File

@ -877,4 +877,6 @@ class TestFilter:
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
t1.render(x=42)
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
t2.render(x=42)

View File

@ -287,26 +287,34 @@ class TestInheritance:
env = Environment(
loader=DictLoader(
{
"default": "{% block x required %}data {# #}{% endblock %}",
"default1": "{% block x required %}{% block y %}"
"{% endblock %} {% endblock %}",
"default2": "{% block x required %}{% if true %}"
"{% endif %} {% endblock %}",
"level1": "{% if default %}{% extends default %}"
"{% else %}{% extends 'default' %}{% endif %}"
"{%- block x %}CHILD{% endblock %}",
"empty": "{% block x required %}{% endblock %}",
"blank": "{% block x required %} {# c #}{% endblock %}",
"text": "{% block x required %}data {# c #}{% endblock %}",
"block": "{% block x required %}{% block y %}"
"{% endblock %}{% endblock %}",
"if": "{% block x required %}{% if true %}"
"{% endif %}{% endblock %}",
"top": "{% extends t %}{% block x %}CHILD{% endblock %}",
}
)
)
t = env.get_template("level1")
t = env.get_template("top")
assert t.render(t="empty") == "CHILD"
assert t.render(t="blank") == "CHILD"
with pytest.raises(
required_block_check = pytest.raises(
TemplateSyntaxError,
match="Required blocks can only contain comments or whitespace",
):
assert t.render(default="default")
assert t.render(default="default2")
assert t.render(default="default3")
)
with required_block_check:
t.render(t="text")
with required_block_check:
t.render(t="block")
with required_block_check:
t.render(t="if")
def test_required_with_scope(self, env):
env = Environment(
@ -347,8 +355,11 @@ class TestInheritance:
)
)
tmpl = env.get_template("child")
with pytest.raises(TemplateSyntaxError):
tmpl.render(default="default1", seq=list(range(3)))
with pytest.raises(TemplateSyntaxError):
tmpl.render(default="default2", seq=list(range(3)))

View File

@ -1,19 +1,22 @@
[tox]
envlist =
py3{11,10,9,8,7},pypy3{8,7}
py3{12,11,10,9,8,7}
pypy310
style
typing
docs
skip_missing_interpreters = true
[testenv]
package = wheel
wheel_build_env = .pkg
deps = -r requirements/tests.txt
commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs}
[testenv:style]
deps = pre-commit
skip_install = true
commands = pre-commit run --all-files --show-diff-on-failure
commands = pre-commit run --all-files
[testenv:typing]
deps = -r requirements/typing.txt