mirror of https://gitee.com/openkylin/jinja2.git
New upstream version 3.1.3
This commit is contained in:
parent
65adae8035
commit
9bcffab58c
12
CHANGES.rst
12
CHANGES.rst
|
@ -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
|
||||
-------------
|
||||
|
||||
|
|
14
PKG-INFO
14
PKG-INFO
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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"),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -91,4 +91,4 @@ this add this to ``config/environment.py``:
|
|||
|
||||
config['pylons.strict_c'] = True
|
||||
|
||||
.. _Pylons: https://pylonshq.com/
|
||||
.. _Pylons: https://pylonsproject.org/
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
15
setup.cfg
15
setup.cfg
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "/"))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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__ = ()
|
||||
|
|
|
@ -182,7 +182,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)
|
||||
|
||||
|
@ -259,7 +259,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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -474,6 +474,12 @@ class TestFilter:
|
|||
assert 'bar="23"' in out
|
||||
assert 'blub:blub="<?>"' 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]"
|
||||
|
@ -870,4 +876,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)
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
7
tox.ini
7
tox.ini
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue