Import Upstream version 3.1.2

This commit is contained in:
luoyaoming 2024-05-07 15:42:49 +08:00
parent 648aeedbb8
commit 65adae8035
64 changed files with 803 additions and 1477 deletions

View File

@ -1,5 +1,65 @@
.. currentmodule:: jinja2 .. currentmodule:: jinja2
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 Version 3.0.3
------------- -------------

View File

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: Jinja2 Name: Jinja2
Version: 3.0.3 Version: 3.1.2
Summary: A very fast and expressive template engine. Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/ Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher Author: Armin Ronacher
@ -24,7 +24,7 @@ Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.6 Requires-Python: >=3.7
Description-Content-Type: text/x-rst Description-Content-Type: text/x-rst
Provides-Extra: i18n Provides-Extra: i18n
License-File: LICENSE.rst License-File: LICENSE.rst

17
debian/changelog vendored
View File

@ -1,17 +0,0 @@
jinja2 (3.0.3-ok3) yangtze; urgency=medium
* Add python3-sphinxcontrib-log-cabinet to Build-Depends.
-- sufang <sufang@kylinos.cn> Fri, 17 Mar 2023 14:19:01 +0800
jinja2 (3.0.3-ok2) yangtze; urgency=medium
* Update version info.
-- sufang <sufang@kylinos.cn> Fri, 17 Mar 2023 11:05:53 +0800
jinja2 (3.0.3-ok1) yangtze; urgency=medium
* Build for openkylin.
-- sufang <sufang@kylinos.cn> Tue, 15 Nov 2022 09:30:53 +0800

69
debian/control vendored
View File

@ -1,69 +0,0 @@
Source: jinja2
Section: python
Priority: optional
Maintainer: OpenKylin Developers <packaging@lists.openkylin.top>
Build-Depends:
debhelper-compat (= 12),
dh-python,
python3-all,
python3-babel,
python3-markupsafe (>= 2.0),
python3-pallets-sphinx-themes,
python3-pygments,
python3-setuptools,
python3-sphinx,
python3-sphinx-issues,
python3-sphinxcontrib-log-cabinet
Standards-Version: 4.5.0
Homepage: http://jinja.pocoo.org/
Vcs-Git: https://gitee.com/openkylin/jinja2.git
Vcs-Browser: https://gitee.com/openkylin/jinja2
Package: python-jinja2-doc
Section: doc
Architecture: all
Depends:
${misc:Depends},
${sphinxdoc:Depends},
Recommends:
python3-jinja2,
Multi-Arch: foreign
Description: documentation for the Jinja2 Python library
Jinja2 is a small but fast and easy to use stand-alone template engine
.
This package contains the documentation for Jinja2 in HTML and
reStructuredText formats.
Package: python3-jinja2
Architecture: all
Depends:
python3-markupsafe (>= 2.0),
${misc:Depends},
${python3:Depends},
Recommends:
python3-babel,
python3-pkg-resources,
Suggests:
python-jinja2-doc,
Breaks:
python-jinja2 (<< 2.11.1-1),
Replaces:
python-jinja2 (<< 2.11.1-1),
Description: small but fast and easy to use stand-alone template engine
Jinja2 is a template engine written in pure Python. It provides a Django
inspired non-XML syntax but supports inline expressions and an optional
sandboxed environment.
.
The key-features are:
* Configurable syntax. If you are generating LaTeX or other formats with
Jinja2 you can change the delimiters to something that integrates better
into the LaTeX markup.
* Fast. While performance is not the primarily target of Jinja2 its
surprisingly fast. The overhead compared to regular Python code was reduced
to the very minimum.
* Easy to debug. Jinja2 integrates directly into the Python traceback system
which allows you to debug Jinja2 templates with regular Python debugging
helpers.
* Secure. Its possible to evaluate untrusted template code if the optional
sandbox is enabled. This allows Jinja2 to be used as templating language
for applications where users may modify the template design.

194
debian/copyright vendored
View File

@ -1,194 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: jinja2
Source: <url://example.com>
#
# Please double check copyright with the licensecheck(1) command.
Files: CHANGES.rst
MANIFEST.in
PKG-INFO
README.rst
docs/Makefile
docs/_static/jinja-logo-sidebar.png
docs/_static/jinja-logo.png
docs/api.rst
docs/changes.rst
docs/conf.py
docs/examples/cache_extension.py
docs/examples/inline_gettext_extension.py
docs/faq.rst
docs/index.rst
docs/integration.rst
docs/intro.rst
docs/license.rst
docs/make.bat
docs/nativetypes.rst
docs/sandbox.rst
docs/switching.rst
docs/templates.rst
docs/tricks.rst
examples/basic/cycle.py
examples/basic/debugger.py
examples/basic/inheritance.py
examples/basic/templates/broken.html
examples/basic/templates/subbroken.html
examples/basic/test.py
examples/basic/test_filter_and_linestatements.py
examples/basic/test_loop_filter.py
examples/basic/translate.py
requirements/dev.txt
requirements/docs.txt
requirements/tests.txt
requirements/typing.txt
setup.cfg
setup.py
src/Jinja2.egg-info/PKG-INFO
src/Jinja2.egg-info/SOURCES.txt
src/Jinja2.egg-info/dependency_links.txt
src/Jinja2.egg-info/entry_points.txt
src/Jinja2.egg-info/requires.txt
src/Jinja2.egg-info/top_level.txt
src/jinja2/__init__.py
src/jinja2/_identifier.py
src/jinja2/async_utils.py
src/jinja2/bccache.py
src/jinja2/compiler.py
src/jinja2/constants.py
src/jinja2/debug.py
src/jinja2/defaults.py
src/jinja2/environment.py
src/jinja2/exceptions.py
src/jinja2/ext.py
src/jinja2/filters.py
src/jinja2/idtracking.py
src/jinja2/lexer.py
src/jinja2/loaders.py
src/jinja2/meta.py
src/jinja2/nativetypes.py
src/jinja2/nodes.py
src/jinja2/optimizer.py
src/jinja2/parser.py
src/jinja2/py.typed
src/jinja2/runtime.py
src/jinja2/sandbox.py
src/jinja2/tests.py
src/jinja2/utils.py
src/jinja2/visitor.py
tests/conftest.py
tests/res/__init__.py
tests/res/package.zip
tests/res/templates/broken.html
tests/res/templates/foo/test.html
tests/res/templates/mojibake.txt
tests/res/templates/syntaxerror.html
tests/res/templates/test.html
tests/res/templates2/foo
tests/test_api.py
tests/test_async.py
tests/test_async_filters.py
tests/test_bytecode_cache.py
tests/test_compile.py
tests/test_core_tags.py
tests/test_debug.py
tests/test_ext.py
tests/test_features.py
tests/test_filters.py
tests/test_idtracking.py
tests/test_imports.py
tests/test_inheritance.py
tests/test_lexnparse.py
tests/test_loader.py
tests/test_nativetypes.py
tests/test_nodes.py
tests/test_regression.py
tests/test_runtime.py
tests/test_security.py
tests/test_tests.py
tests/test_utils.py
tox.ini
Copyright: __NO_COPYRIGHT_NOR_LICENSE__
License: __NO_COPYRIGHT_NOR_LICENSE__
Files: docs/extensions.rst
Copyright: __NO_COPYRIGHT__ in: docs/extensions.rst
License: __UNKNOWN__
- 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
Files: LICENSE.rst
Copyright: 2007 Pallets
License: BSD-3-Clause
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.
.
On Debian systems, the complete text of the BSD 3-clause "New" or "Revised"
License can be found in `/usr/share/common-licenses/BSD'.
#----------------------------------------------------------------------------
# xml and html files (skipped):
# artwork/jinjalogo.svg
#----------------------------------------------------------------------------
# Files marked as NO_LICENSE_TEXT_FOUND may be covered by the following
# license/copyright files.
#----------------------------------------------------------------------------
# License file: LICENSE.rst
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.

135
debian/jinja.vim vendored
View File

@ -1,135 +0,0 @@
" Vim syntax file
" Language: Jinja template
" Maintainer: Armin Ronacher <armin.ronacher@active-4.com>
" Last Change: 2008 May 9
" Version: 1.1
"
" Known Bugs:
" because of odd limitations dicts and the modulo operator
" appear wrong in the template.
"
" Changes:
"
" 2008 May 9: Added support for Jinja2 changes (new keyword rules)
" .vimrc variable to disable html highlighting
if !exists('g:jinja_syntax_html')
let g:jinja_syntax_html=1
endif
" For version 5.x: Clear all syntax items
" For version 6.x: Quit when a syntax file was already loaded
if !exists("main_syntax")
if version < 600
syntax clear
elseif exists("b:current_syntax")
finish
endif
let main_syntax = 'jinja'
endif
" Pull in the HTML syntax.
if g:jinja_syntax_html
if version < 600
so <sfile>:p:h/html.vim
else
runtime! syntax/html.vim
unlet b:current_syntax
endif
endif
syntax case match
" Jinja template built-in tags and parameters (without filter, macro, is and raw, they
" have special threatment)
syn keyword jinjaStatement containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained and if else in not or recursive as import
syn keyword jinjaStatement containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained is filter skipwhite nextgroup=jinjaFilter
syn keyword jinjaStatement containedin=jinjaTagBlock contained macro skipwhite nextgroup=jinjaFunction
syn keyword jinjaStatement containedin=jinjaTagBlock contained block skipwhite nextgroup=jinjaBlockName
" Variable Names
syn match jinjaVariable containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[a-zA-Z_][a-zA-Z0-9_]*/
syn keyword jinjaSpecial containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained false true none False True None loop super caller varargs kwargs
" Filters
syn match jinjaOperator "|" containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained skipwhite nextgroup=jinjaFilter
syn match jinjaFilter contained /[a-zA-Z_][a-zA-Z0-9_]*/
syn match jinjaFunction contained /[a-zA-Z_][a-zA-Z0-9_]*/
syn match jinjaBlockName contained /[a-zA-Z_][a-zA-Z0-9_]*/
" Jinja template constants
syn region jinjaString containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained start=/"/ skip=/\(\\\)\@<!\(\(\\\\\)\@>\)*\\"/ end=/"/
syn region jinjaString containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained start=/'/ skip=/\(\\\)\@<!\(\(\\\\\)\@>\)*\\'/ end=/'/
syn match jinjaNumber containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[0-9]\+\(\.[0-9]\+\)\?/
" Operators
syn match jinjaOperator containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[+\-*\/<>=!,:]/
syn match jinjaPunctuation containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[()\[\]]/
syn match jinjaOperator containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /\./ nextgroup=jinjaAttribute
syn match jinjaAttribute contained /[a-zA-Z_][a-zA-Z0-9_]*/
" Jinja template tag and variable blocks
syn region jinjaNested matchgroup=jinjaOperator start="(" end=")" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained
syn region jinjaNested matchgroup=jinjaOperator start="\[" end="\]" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained
syn region jinjaNested matchgroup=jinjaOperator start="{" end="}" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained
syn region jinjaTagBlock matchgroup=jinjaTagDelim start=/{%-\?/ end=/-\?%}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment
syn region jinjaVarBlock matchgroup=jinjaVarDelim start=/{{-\?/ end=/-\?}}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment
" Jinja template 'raw' tag
syn region jinjaRaw matchgroup=jinjaRawDelim start="{%\s*raw\s*%}" end="{%\s*endraw\s*%}" containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaString,jinjaComment
" Jinja comments
syn region jinjaComment matchgroup=jinjaCommentDelim start="{#" end="#}" containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaString,jinjaComment
" Block start keywords. A bit tricker. We only highlight at the start of a
" tag block and only if the name is not followed by a comma or equals sign
" which usually means that we have to deal with an assignment.
syn match jinjaStatement containedin=jinjaTagBlock contained /\({%-\?\s*\)\@<=\<[a-zA-Z_][a-zA-Z0-9_]*\>\(\s*[,=]\)\@!/
" and context modifiers
syn match jinjaStatement containedin=jinjaTagBlock contained /\<with\(out\)\?\s\+context\>/
" Define the default highlighting.
" For version 5.7 and earlier: only when not done already
" For version 5.8 and later: only when an item doesn't have highlighting yet
if version >= 508 || !exists("did_jinja_syn_inits")
if version < 508
let did_jinja_syn_inits = 1
command -nargs=+ HiLink hi link <args>
else
command -nargs=+ HiLink hi def link <args>
endif
HiLink jinjaPunctuation jinjaOperator
HiLink jinjaAttribute jinjaVariable
HiLink jinjaFunction jinjaFilter
HiLink jinjaTagDelim jinjaTagBlock
HiLink jinjaVarDelim jinjaVarBlock
HiLink jinjaCommentDelim jinjaComment
HiLink jinjaRawDelim jinja
HiLink jinjaSpecial Special
HiLink jinjaOperator Normal
HiLink jinjaRaw Normal
HiLink jinjaTagBlock PreProc
HiLink jinjaVarBlock PreProc
HiLink jinjaStatement Statement
HiLink jinjaFilter Function
HiLink jinjaBlockName Function
HiLink jinjaVariable Identifier
HiLink jinjaString Constant
HiLink jinjaNumber Constant
HiLink jinjaComment Comment
delcommand HiLink
endif
let b:current_syntax = "jinja"
if main_syntax == 'jinja'
unlet main_syntax
endif

4
debian/jinja.yaml vendored
View File

@ -1,4 +0,0 @@
addon: jinja
description: "allow syntax highlighting for Jinja templates"
files:
- syntax/jinja.vim

View File

@ -1 +0,0 @@
# You must remove unused comment lines for the released package.

View File

@ -1 +0,0 @@
docs/_build/html

View File

@ -1 +0,0 @@
examples/*

View File

@ -1,5 +0,0 @@
/usr/share/doc/python-jinja2-doc/examples /usr/share/doc/python3-jinja2/examples
/usr/share/doc/python-jinja2-doc/html /usr/share/doc/python-jinja2/html
/usr/share/doc/python-jinja2-doc/html /usr/share/doc/python3-jinja2/html
/usr/share/doc/python-jinja2-doc/html/_sources /usr/share/doc/python-jinja2/rst
/usr/share/doc/python-jinja2-doc/html/_sources /usr/share/doc/python3-jinja2/rst

View File

@ -1,2 +0,0 @@
jinja2/asyncfilters.py
jinja2/asyncsupport.py

View File

@ -1 +0,0 @@
re|-3.6|/usr/lib/python3/dist-packages/jinja2|.*/async(support|filters).py

View File

@ -1,2 +0,0 @@
/usr/share/vim/addons/syntax/
/usr/share/vim/registry

View File

@ -1,2 +0,0 @@
debian/jinja.yaml /usr/share/vim/registry
debian/jinja.vim /usr/share/vim/addons/syntax/

29
debian/rules vendored
View File

@ -1,29 +0,0 @@
#!/usr/bin/make -f
#export DH_VERBOSE=1
export PYBUILD_NAME=jinja2
export SETUPTOOLS_USE_DISTUTILS=stdlib
%:
dh $@ --with python3,sphinxdoc --buildsystem=pybuild
override_dh_auto_clean:
make -C docs clean
dh_auto_clean
override_dh_auto_build-indep:
PYTHONPATH=$(CURDIR)/src:$(CURDIR)/docs make -C docs html
override_dh_installdocs:
# install into -doc package and keep the symlink
dh_installdocs -p python-jinja2-doc --doc-main-package python-jinja2-doc
dh_installdocs --remaining
override_dh_installexamples:
# install into -doc package and keep the symlink
dh_installexamples -p python-jinja2-doc --doc-main-package python-jinja2-doc
dh_installexamples --remaining
override_dh_installchangelogs:
dh_installchangelogs CHANGES.rst

View File

@ -1 +0,0 @@
3.0 (native)

3
debian/watch vendored
View File

@ -1,3 +0,0 @@
version=3
opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
https://pypi.debian.net/Jinja2/Jinja2-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))

View File

@ -273,7 +273,7 @@ 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 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 function into the filter dict and call it `to.str`. The regular
expression for filter and test identifiers is expression for filter and test identifiers is
``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*```. ``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*``.
Undefined Types Undefined Types
@ -410,16 +410,19 @@ The Context
.. automethod:: jinja2.runtime.Context.call(callable, \*args, \**kwargs) .. automethod:: jinja2.runtime.Context.call(callable, \*args, \**kwargs)
.. admonition:: Implementation 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.
Context is immutable for the same reason Python's frame locals are Instead of modifying the context directly, a function should return
immutable inside functions. Both Jinja and Python are not using the a value that can be assigned to a variable within the template itself.
context / frame locals as data storage for variables but only as primary
data source.
When a template accesses a variable the template does not define, Jinja .. code-block:: jinja
looks up the variable in the context, after that the variable is treated
as if it was defined in the template. {% set comments = get_latest_comments() %}
.. _loaders: .. _loaders:
@ -597,18 +600,6 @@ functions to a Jinja environment.
.. autofunction:: jinja2.pass_environment .. autofunction:: jinja2.pass_environment
.. autofunction:: jinja2.contextfilter
.. autofunction:: jinja2.evalcontextfilter
.. autofunction:: jinja2.environmentfilter
.. autofunction:: jinja2.contextfunction
.. autofunction:: jinja2.evalcontextfunction
.. autofunction:: jinja2.environmentfunction
.. autofunction:: jinja2.clear_caches .. autofunction:: jinja2.clear_caches
.. autofunction:: jinja2.is_undefined .. autofunction:: jinja2.is_undefined

View File

@ -1,175 +1,75 @@
Frequently Asked Questions Frequently Asked Questions
========================== ==========================
This page answers some of the often asked questions about Jinja.
.. highlight:: html+jinja
Why is it called Jinja? Why is it called Jinja?
----------------------- -----------------------
The name Jinja was chosen because it's the name of a Japanese temple and "Jinja" is a Japanese `Shinto shrine`_, or temple, and temple and
temple and template share a similar pronunciation. It is not named after template share a similar English pronunciation. It is not named after
the city in Uganda. the `city in Uganda`_.
How fast is it? .. _Shinto shrine: https://en.wikipedia.org/wiki/Shinto_shrine
--------------- .. _city in Uganda: https://en.wikipedia.org/wiki/Jinja%2C_Uganda
We really hate benchmarks especially since they don't reflect much. The
performance of a template depends on many factors and you would have to
benchmark different engines in different situations. The benchmarks from the
testsuite show that Jinja has a similar performance to `Mako`_ and is between
10 and 20 times faster than Django's template engine or Genshi. These numbers
should be taken with tons of salt as the benchmarks that took these numbers
only test a few performance related situations such as looping. Generally
speaking the performance of a template engine doesn't matter much as the
usual bottleneck in a web application is either the database or the application
code.
.. _Mako: https://www.makotemplates.org/ How fast is Jinja?
------------------
How Compatible is Jinja with Django? 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.
The default syntax of Jinja matches Django syntax in many ways. However Jinja also makes extensive use of caching. Templates are cached by name
this similarity doesn't mean that you can use a Django template unmodified after loading, so future uses of the template avoid loading. The
in Jinja. For example filter arguments use a function call syntax rather template loading itself uses a bytecode cache to avoid repeated
than a colon to separate filter name and arguments. Additionally the compiling. The caches can be external to persist across restarts.
extension interface in Jinja is fundamentally different from the Django one Templates can also be precompiled and loaded as fast Python imports.
which means that your custom tags won't work any longer.
Generally speaking you will use much less custom extensions as the Jinja We dislike benchmarks because they don't reflect real use. Performance
template system allows you to use a certain subset of Python expressions depends on many factors. Different engines have different default
which can replace most Django extensions. For example instead of using configurations and tradeoffs that make it unclear how to set up a useful
something like this:: comparison. Often, database access, API calls, and data processing have
a much larger effect on performance than the template engine.
{% load comments %}
{% get_latest_comments 10 as latest_comments %}
{% for comment in latest_comments %}
...
{% endfor %}
You will most likely provide an object with attributes to retrieve Isn't it a bad idea to put logic in templates?
comments from the database:: ----------------------------------------------
{% for comment in models.comments.latest(10) %}
...
{% endfor %}
Or directly provide the model for quick testing::
{% for comment in Comment.objects.order_by('-pub_date')[:10] %}
...
{% endfor %}
Please keep in mind that even though you may put such things into templates
it still isn't a good idea. Queries should go into the view code and not
the template!
Isn't it a terrible idea to put Logic into Templates?
-----------------------------------------------------
Without a doubt you should try to remove as much logic from templates as Without a doubt you should try to remove as much logic from templates as
possible. But templates without any logic mean that you have to do all possible. With less logic, the template is easier to understand, has
the processing in the code which is boring and stupid. A template engine fewer potential side effects, and is faster to compile and render. But a
that does that is shipped with Python and called `string.Template`. Comes template without any logic means processing must be done in code before
without loops and if conditions and is by far the fastest template engine rendering. A template engine that does that is shipped with Python,
you can get for Python. called :class:`string.Template`, and while it's definitely fast it's not
convenient.
So some amount of logic is required in templates to keep everyone happy. Jinja's features such as blocks, statements, filters, and function calls
And Jinja leaves it pretty much to you how much logic you want to put into make it much easier to write expressive templates, with very few
templates. There are some restrictions in what you can do and what not. 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.
Jinja neither allows you to put arbitrary Python code into templates nor Some amount of logic is required in templates to keep everyone happy.
does it allow all Python expressions. The operators are limited to the Too much logic in the template can make it complex to reason about and
most common ones and more advanced expressions such as list comprehensions maintain. It's up to you to decide how your application will work and
and generator expressions are not supported. This keeps the template engine balance how much logic you want to put in the template.
easier to maintain and templates more readable.
Why is Autoescaping not the Default?
------------------------------------
There are multiple reasons why automatic escaping is not the default mode Why is HTML escaping not the default?
and also not the recommended one. While automatic escaping of variables
means that you will less likely have an XSS problem it also causes a huge
amount of extra processing in the template engine which can cause serious
performance problems. As Python doesn't provide a way to mark strings as
unsafe Jinja has to hack around that limitation by providing a custom
string class (the :class:`Markup` string) that safely interacts with safe
and unsafe strings.
With explicit escaping however the template engine doesn't have to perform
any safety checks on variables. Also a human knows not to escape integers
or strings that may never contain characters one has to escape or already
HTML markup. For example when iterating over a list over a table of
integers and floats for a table of statistics the template designer can
omit the escaping because he knows that integers or floats don't contain
any unsafe parameters.
Additionally Jinja is a general purpose template engine and not only used
for HTML/XML generation. For example you may generate LaTeX, emails,
CSS, JavaScript, or configuration files.
Why is the Context immutable?
-----------------------------
When writing a :func:`pass_context` function, you may have noticed that
the context tries to stop you from modifying it. If you have managed to
modify the context by using an internal context API you may have noticed
that changes in the context don't seem to be visible in the template.
The reason for this is that Jinja uses the context only as primary data
source for template variables for performance reasons.
If you want to modify the context write a function that returns a variable
instead that one can assign to a variable by using set::
{% set comments = get_latest_comments() %}
My tracebacks look weird. What's happening?
-------------------------------------------
Jinja can rewrite tracebacks so they show the template lines numbers and
source rather than the underlying compiled code, but this requires
special Python support. CPython <3.7 requires ``ctypes``, and PyPy
requires transparent proxy support.
If you are using Google App Engine, ``ctypes`` is not available. You can
make it available in development, but not in production.
.. code-block:: python
import os
if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'):
from google.appengine.tools.devappserver2.python import sandbox
sandbox._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
Credit for this snippet goes to `Thomas Johansson
<https://stackoverflow.com/questions/3086091/debug-jinja2-in-google-app-engine/3694434#3694434>`_
My Macros are overridden by something
------------------------------------- -------------------------------------
In some situations the Jinja scoping appears arbitrary: Jinja provides a feature that can be enabled to escape HTML syntax in
rendered templates. However, it is disabled by default.
layout.tmpl: 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.
.. sourcecode:: jinja While automatic escaping means that you are less likely have an XSS
problem, it also requires significant extra processing during compiling
{% macro foo() %}LAYOUT{% endmacro %} and rendering, which can reduce performance. Jinja uses MarkupSafe for
{% block body %}{% endblock %} escaping, which provides optimized C code for speed, but it still
introduces overhead to track escaping across methods and formatting.
child.tmpl:
.. sourcecode:: jinja
{% extends 'layout.tmpl' %}
{% macro foo() %}CHILD{% endmacro %}
{% block body %}{{ foo() }}{% endblock %}
This will print ``LAYOUT`` in Jinja. This is a side effect of having
the parent template evaluated after the child one. This allows child
templates passing information to the parent template. To avoid this
issue rename the macro or variable in the parent template to have an
uncommon prefix.
.. _Jinja 1: https://pypi.org/project/Jinja/

View File

@ -1,6 +1,25 @@
Integration 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-integration:
Babel Babel

View File

@ -30,7 +30,7 @@ Installation
------------ ------------
We recommend using the latest version of Python. Jinja supports Python We recommend using the latest version of Python. Jinja supports Python
3.6 and newer. We also recommend using a `virtual environment`_ in order 3.7 and newer. We also recommend using a `virtual environment`_ in order
to isolate your project dependencies from other projects and the system. to isolate your project dependencies from other projects and the system.
.. _virtual environment: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments .. _virtual environment: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments

View File

@ -1,18 +1,56 @@
Sandbox Sandbox
======= =======
The Jinja sandbox can be used to evaluate untrusted code. Access to unsafe The Jinja sandbox can be used to render untrusted templates. Access to
attributes and methods is prohibited. attributes, method calls, operators, mutating data structures, and
string formatting can be intercepted and prohibited.
Assuming `env` is a :class:`SandboxedEnvironment` in the default configuration .. code-block:: pycon
the following piece of code shows how it works:
>>> env.from_string("{{ func.func_code }}").render(func=lambda:None) >>> from jinja2.sandbox import SandboxedEnvironment
u'' >>> env = SandboxedEnvironment()
>>> env.from_string("{{ func.func_code.do_something }}").render(func=lambda:None) >>> func = lambda: "Hello, Sandbox!"
Traceback (most recent call last): >>> 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 'func_code' of 'function' object is unsafe. 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 API
--- ---
@ -34,61 +72,40 @@ API
.. autofunction:: modifies_known_mutable .. autofunction:: modifies_known_mutable
.. admonition:: Note
The Jinja sandbox alone is no solution for perfect security. Especially
for web applications you have to keep in mind that users may create
templates with arbitrary HTML in so it's crucial to ensure that (if you
are running multiple users on the same server) they can't harm each other
via JavaScript insertions and much more.
Also the sandbox is only as good as the configuration. We strongly
recommend only passing non-shared resources to the template and use
some sort of whitelisting for attributes.
Also keep in mind that templates may raise runtime or compile time errors,
so make sure to catch them.
Operator Intercepting Operator Intercepting
--------------------- ---------------------
.. versionadded:: 2.6 For performance, Jinja outputs operators directly when compiling. This
means it's not possible to intercept operator behavior by overriding
:meth:`SandboxEnvironment.call <Environment.call>` by default, because
operator special methods are handled by the Python interpreter, and
might not correspond with exactly one method depending on the operator's
use.
For maximum performance Jinja will let operators call directly the type The sandbox can instruct the compiler to output a function to intercept
specific callback methods. This means that it's not possible to have this certain operators instead. Override
intercepted by overriding :meth:`Environment.call`. Furthermore a :attr:`SandboxedEnvironment.intercepted_binops` and
conversion from operator to special method is not always directly possible :attr:`SandboxedEnvironment.intercepted_unops` with the operator symbols
due to how operators work. For instance for divisions more than one you want to intercept. The compiler will replace the symbols with calls
special method exist. 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.
With Jinja 2.6 there is now support for explicit operator intercepting. For example, the power (``**``) operator can be disabled:
This can be used to customize specific operators as necessary. In order
to intercept an operator one has to override the
:attr:`SandboxedEnvironment.intercepted_binops` attribute. Once the
operator that needs to be intercepted is added to that set Jinja will
generate bytecode that calls the :meth:`SandboxedEnvironment.call_binop`
function. For unary operators the `unary` attributes and methods have to
be used instead.
The default implementation of :attr:`SandboxedEnvironment.call_binop` .. code-block:: python
will use the :attr:`SandboxedEnvironment.binop_table` to translate
operator symbols into callbacks performing the default operator behavior.
This example shows how the power (``**``) operator can be disabled in
Jinja::
from jinja2.sandbox import SandboxedEnvironment from jinja2.sandbox import SandboxedEnvironment
class MyEnvironment(SandboxedEnvironment): class MyEnvironment(SandboxedEnvironment):
intercepted_binops = frozenset(['**']) intercepted_binops = frozenset(["**"])
def call_binop(self, context, operator, left, right): def call_binop(self, context, operator, left, right):
if operator == '**': if operator == "**":
return self.undefined('the power operator is unavailable') return self.undefined("The power (**) operator is unavailable.")
return SandboxedEnvironment.call_binop(self, context,
operator, left, right)
Make sure to always call into the super method, even if you are not return super().call_binop(self, context, operator, left, right)
intercepting the call. Jinja might internally call the method to
evaluate expressions.

View File

@ -1,141 +1,73 @@
Switching from other Template Engines Switching From Other Template Engines
===================================== =====================================
.. highlight:: html+jinja This is a brief guide on some of the differences between Jinja syntax
and other template languages. See :doc:`/templates` for a comprehensive
If you have used a different template engine in the past and want to switch guide to Jinja syntax and features.
to Jinja here is a small guide that shows the basic syntactic and semantic
changes between some common, similar text template engines for Python.
Jinja 1
-------
Jinja 2 is mostly compatible with Jinja 1 in terms of API usage and template
syntax. The differences between Jinja 1 and 2 are explained in the following
list.
API
~~~
Loaders
Jinja 2 uses a different loader API. Because the internal representation
of templates changed there is no longer support for external caching
systems such as memcached. The memory consumed by templates is comparable
with regular Python modules now and external caching doesn't give any
advantage. If you have used a custom loader in the past have a look at
the new :ref:`loader API <loaders>`.
Loading templates from strings
In the past it was possible to generate templates from a string with the
default environment configuration by using `jinja.from_string`. Jinja 2
provides a :class:`Template` class that can be used to do the same, but
with optional additional configuration.
Automatic unicode conversion
Jinja 1 performed automatic conversion of bytes in a given encoding
into unicode objects. This conversion is no longer implemented as it
was inconsistent as most libraries are using the regular Python
ASCII bytes to Unicode conversion. An application powered by Jinja 2
*has to* use unicode internally everywhere or make sure that Jinja 2
only gets unicode strings passed.
i18n
Jinja 1 used custom translators for internationalization. i18n is now
available as Jinja 2 extension and uses a simpler, more gettext friendly
interface and has support for babel. For more details see
:ref:`i18n-extension`.
Internal methods
Jinja 1 exposed a few internal methods on the environment object such
as `call_function`, `get_attribute` and others. While they were marked
as being an internal method it was possible to override them. Jinja 2
doesn't have equivalent methods.
Sandbox
Jinja 1 was running sandbox mode by default. Few applications actually
used that feature so it became optional in Jinja 2. For more details
about the sandboxed execution see :class:`SandboxedEnvironment`.
Context
Jinja 1 had a stacked context as storage for variables passed to the
environment. In Jinja 2 a similar object exists but it doesn't allow
modifications nor is it a singleton. As inheritance is dynamic now
multiple context objects may exist during template evaluation.
Filters and Tests
Filters and tests are regular functions now. It's no longer necessary
and allowed to use factory functions.
Templates
~~~~~~~~~
Jinja 2 has mostly the same syntax as Jinja 1. What's different is that
macros require parentheses around the argument list now.
Additionally Jinja 2 allows dynamic inheritance now and dynamic includes.
The old helper function `rendertemplate` is gone now, `include` can be used
instead. Includes no longer import macros and variable assignments, for
that the new `import` tag is used. This concept is explained in the
:ref:`import` documentation.
Another small change happened in the `for`-tag. The special loop variable
doesn't have a `parent` attribute, instead you have to alias the loop
yourself. See :ref:`accessing-the-parent-loop` for more details.
Django Django
------ ------
If you have previously worked with Django templates, you should find If you have previously worked with Django templates, you should find
Jinja very familiar. In fact, most of the syntax elements look and Jinja very familiar. Many of the syntax elements look and work the same.
work the same. However, Jinja provides some more syntax elements, and some work a bit
differently.
However, Jinja provides some more syntax elements covered in the This section covers the template changes. The API, including extension
documentation and some work a bit different. 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.
This section covers the template changes. As the API is fundamentally
different we won't cover it here.
Method Calls Method Calls
~~~~~~~~~~~~ ~~~~~~~~~~~~
In Django method calls work implicitly, while Jinja requires the explicit In Django, methods are called implicitly, without parentheses.
Python syntax. Thus this Django code::
.. code-block:: django
{% for page in user.get_created_pages %} {% for page in user.get_created_pages %}
... ...
{% endfor %} {% endfor %}
...looks like this in Jinja:: 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() %} {% for page in user.get_created_pages() %}
... ...
{% endfor %} {% endfor %}
This allows you to pass variables to the method, which is not possible in
Django. This syntax is also used for macros.
Filter Arguments Filter Arguments
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
Jinja provides more than one argument for filters. Also the syntax for In Django, one literal value can be passed to a filter after a colon.
argument passing is different. A template that looks like this in Django::
.. code-block:: django
{{ items|join:", " }} {{ items|join:", " }}
looks like this in Jinja:: 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.
{{ items|join(', ') }} .. code-block:: jinja
{{ items|join(", ") }}
It is a bit more verbose, but it allows different types of arguments -
including variables - and more than one of them.
Tests Tests
~~~~~ ~~~~~
In addition to filters there also are tests you can perform using the is In addition to filters, Jinja also has "tests" used with the ``is``
operator. Here are some examples:: operator. This operator is not the same as the Python operator.
.. code-block:: jinja
{% if user.user_id is odd %} {% if user.user_id is odd %}
{{ user.username|e }} is odd {{ user.username|e }} is odd
@ -146,11 +78,10 @@ operator. Here are some examples::
Loops Loops
~~~~~ ~~~~~
For loops work very similarly to Django, but notably the Jinja special In Django, the special variable for the loop context is called
variable for the loop context is called `loop`, not `forloop` as in Django. ``forloop``, and the ``empty`` is used for no loop items.
In addition, the Django `empty` argument is called `else` in Jinja. For .. code-block:: django
example, the Django template::
{% for item in items %} {% for item in items %}
{{ item }} {{ item }}
@ -158,52 +89,74 @@ example, the Django template::
No items! No items!
{% endfor %} {% endfor %}
...looks like this in Jinja:: 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 %} {% for item in items %}
{{ item }} {{ loop.index}}. {{ item }}
{% else %} {% else %}
No items! No items!
{% endfor %} {% endfor %}
Cycle Cycle
~~~~~ ~~~~~
The ``{% cycle %}`` tag does not exist in Jinja; however, you can achieve the In Django, the ``{% cycle %}`` can be used in a for loop to alternate
same output by using the `cycle` method on the loop context special variable. between values per loop.
The following Django template:: .. code-block:: django
{% for user in users %} {% for user in users %}
<li class="{% cycle 'odd' 'even' %}">{{ user }}</li> <li class="{% cycle 'odd' 'even' %}">{{ user }}</li>
{% endfor %} {% endfor %}
...looks like this in Jinja:: In Jinja, the ``loop`` context has a ``cycle`` method.
.. code-block:: jinja
{% for user in users %} {% for user in users %}
<li class="{{ loop.cycle('odd', 'even') }}">{{ user }}</li> <li class="{{ loop.cycle('odd', 'even') }}">{{ user }}</li>
{% endfor %} {% endfor %}
There is no equivalent of ``{% cycle ... as variable %}``. A cycler can also be assigned to a variable and used outside or across
loops with the ``cycle()`` global function.
Mako Mako
---- ----
.. highlight:: html+mako You can configure Jinja to look more like Mako:
If you have used Mako so far and want to switch to Jinja you can configure .. code-block:: python
Jinja to look more like Mako:
.. sourcecode:: 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="##",
)
env = Environment('<%', '%>', '${', '}', '<%doc>', '</%doc>', '%', '##') With an environment configured like that, Jinja should be able to
interpret a small subset of Mako templates without any changes.
With an environment configured like that, Jinja should be able to interpret Jinja does not support embedded Python code, so you would have to move
a small subset of Mako templates. Jinja does not support embedded Python that out of the template. You could either process the data with the
code, so you would have to move that out of the template. The syntax for defs same code before rendering, or add a global function or filter to the
(which are called macros in Jinja) and template inheritance is different too. Jinja environment.
The following Mako template::
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" /> <%inherit file="layout.html" />
<%def name="title()">Page Title</%def> <%def name="title()">Page Title</%def>
@ -213,7 +166,9 @@ The following Mako template::
% endfor % endfor
</ul> </ul>
Looks like this in Jinja with the above configuration:: Looks like this in Jinja with the above configuration:
.. code-block:: jinja
<% extends "layout.html" %> <% extends "layout.html" %>
<% block title %>Page Title<% endblock %> <% block title %>Page Title<% endblock %>

View File

@ -938,6 +938,23 @@ are available on a macro object:
If a macro name starts with an underscore, it's not exported and can't If a macro name starts with an underscore, it's not exported and can't
be imported. 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:
@ -1113,42 +1130,45 @@ at the same time. They are documented in detail in the
Include Include
~~~~~~~ ~~~~~~~
The `include` tag is useful to include a template and return the The ``include`` tag renders another template and outputs the result into
rendered contents of that file into the current namespace:: the current template.
.. code-block:: jinja
{% include 'header.html' %} {% include 'header.html' %}
Body Body goes here.
{% include 'footer.html' %} {% include 'footer.html' %}
Included templates have access to the variables of the active context by The included template has access to context of the current template by
default. For more details about context behavior of imports and includes, default. Use ``without context`` to use a separate context instead.
see :ref:`import-visibility`. ``with context`` is also valid, but is the default behavior. See
:ref:`import-visibility`.
From Jinja 2.2 onwards, you can mark an include with ``ignore missing``; in The included template can ``extend`` another template and override
which case Jinja will ignore the statement if the template to be included blocks in that template. However, the current template cannot override
does not exist. When combined with ``with`` or ``without context``, it must any blocks that the included template outputs.
be placed *before* the context visibility statement. Here are some valid
examples::
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 %}
{% include "sidebar.html" ignore missing with context %} {% include "sidebar.html" ignore missing with context %}
{% include "sidebar.html" ignore missing without context %} {% include "sidebar.html" ignore missing without context %}
.. versionadded:: 2.2 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.
You can also provide a list of templates that are checked for existence .. code-block:: jinja
before inclusion. The first template that exists will be included. If
`ignore missing` is given, it will fall back to rendering nothing if
none of the templates exist, otherwise it will raise an exception.
Example::
{% include ['page_detailed.html', 'page.html'] %} {% include ['page_detailed.html', 'page.html'] %}
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %} {% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}
.. versionchanged:: 2.4 A variable, with either a template name or template object, can also be
If a template object was passed to the template context, you can passed to the statment.
include that object using `include`.
.. _import: .. _import:
@ -1732,11 +1752,35 @@ to disable it for a block.
.. versionadded:: 2.10 .. versionadded:: 2.10
The ``trimmed`` and ``notrimmed`` modifiers have been added. 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: It's possible to translate strings in expressions with these functions:
- ``gettext``: translate a single string - ``_(message)``: Alias for ``gettext``.
- ``ngettext``: translate a pluralizable string - ``gettext(message)``: Translate a message.
- ``_``: alias for ``gettext`` - ``ngettext(singluar, 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: You can print a translated string like this:

View File

@ -26,7 +26,7 @@ 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:: `standalone` set to `True` a very basic HTML skeleton is added::
{% if not standalone %}{% extends 'default.html' %}{% endif -%} {% if not standalone %}{% extends 'default.html' %}{% endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <!DOCTYPE html>
<title>{% block title %}The Page Title{% endblock %}</title> <title>{% block title %}The Page Title{% endblock %}</title>
<link rel="stylesheet" href="style.css" type="text/css"> <link rel="stylesheet" href="style.css" type="text/css">
{% block body %} {% block body %}
@ -74,8 +74,8 @@ sense to define a default for that variable::
... ...
<ul id="navigation"> <ul id="navigation">
{% for href, id, caption in navigation_bar %} {% for href, id, caption in navigation_bar %}
<li{% if id == active_page %} class="active"{% endif <li{% if id == active_page %} class="active"{% endif %}>
%}><a href="{{ href|e }}">{{ caption|e }}</a></li> <a href="{{ href|e }}">{{ caption|e }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
... ...

View File

@ -1,134 +1,58 @@
# SHA1:54b5b77ec8c7a0064ffa93b2fd16cb0130ba177c
# #
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile-multi
# To update, run: # To update, run:
# #
# pip-compile requirements/dev.in # pip-compile-multi
# #
alabaster==0.7.12 -r docs.txt
# via sphinx -r tests.txt
attrs==21.2.0 -r typing.txt
# via pytest
babel==2.9.1
# via sphinx
backports.entry-points-selectable==1.1.0
# via virtualenv
certifi==2021.10.8
# via requests
cfgv==3.3.1 cfgv==3.3.1
# via pre-commit # via pre-commit
charset-normalizer==2.0.7 click==8.1.2
# via requests # via
click==8.0.3 # pip-compile-multi
# via pip-tools # pip-tools
distlib==0.3.3 distlib==0.3.4
# via virtualenv # via virtualenv
docutils==0.17.1 filelock==3.6.0
# via sphinx
filelock==3.3.2
# via # via
# tox # tox
# virtualenv # virtualenv
identify==2.3.3 identify==2.5.0
# via pre-commit # via pre-commit
idna==3.3
# via requests
imagesize==1.2.0
# via sphinx
iniconfig==1.1.1
# via pytest
jinja2==3.0.2
# via sphinx
markupsafe==2.0.1
# via jinja2
mypy==0.910
# via -r requirements/typing.in
mypy-extensions==0.4.3
# via mypy
nodeenv==1.6.0 nodeenv==1.6.0
# via pre-commit # via pre-commit
packaging==21.2
# via
# pallets-sphinx-themes
# pytest
# sphinx
# tox
pallets-sphinx-themes==2.0.1
# via -r requirements/docs.in
pep517==0.12.0 pep517==0.12.0
# via pip-tools # via pip-tools
pip-tools==6.4.0 pip-compile-multi==2.4.5
# via -r requirements/dev.in # via -r requirements/dev.in
platformdirs==2.4.0 pip-tools==6.6.0
# via pip-compile-multi
platformdirs==2.5.2
# via virtualenv # via virtualenv
pluggy==1.0.0 pre-commit==2.18.1
# via
# pytest
# tox
pre-commit==2.15.0
# via -r requirements/dev.in # via -r requirements/dev.in
py==1.11.0
# via
# pytest
# tox
pygments==2.10.0
# via sphinx
pyparsing==2.4.7
# via packaging
pytest==6.2.5
# via -r requirements/tests.in
pytz==2021.3
# via babel
pyyaml==6.0 pyyaml==6.0
# via pre-commit # via pre-commit
requests==2.26.0
# via sphinx
six==1.16.0 six==1.16.0
# via # via
# tox # tox
# virtualenv # virtualenv
snowballstemmer==2.1.0
# via sphinx
sphinx==4.2.0
# via
# -r requirements/docs.in
# pallets-sphinx-themes
# sphinx-issues
# sphinxcontrib-log-cabinet
sphinx-issues==1.2.0
# via -r requirements/docs.in
sphinxcontrib-applehelp==1.0.2
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
sphinxcontrib-htmlhelp==2.0.0
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-log-cabinet==1.0.1
# via -r requirements/docs.in
sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
toml==0.10.2 toml==0.10.2
# via # via
# mypy
# pre-commit # pre-commit
# pytest
# tox # tox
tomli==1.2.2 toposort==1.7
# via pep517 # via pip-compile-multi
tox==3.24.4 tox==3.25.0
# via -r requirements/dev.in # via -r requirements/dev.in
typing-extensions==3.10.0.2 virtualenv==20.14.1
# via mypy
urllib3==1.26.7
# via requests
virtualenv==20.10.0
# via # via
# pre-commit # pre-commit
# tox # tox
wheel==0.37.0 wheel==0.37.1
# via pip-tools # via pip-tools
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:

View File

@ -1,50 +1,51 @@
# SHA1:45c590f97fe95b8bdc755eef796e91adf5fbe4ea
# #
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile-multi
# To update, run: # To update, run:
# #
# pip-compile requirements/docs.in # pip-compile-multi
# #
alabaster==0.7.12 alabaster==0.7.12
# via sphinx # via sphinx
babel==2.9.1 babel==2.10.1
# via sphinx # via sphinx
certifi==2021.10.8 certifi==2021.10.8
# via requests # via requests
charset-normalizer==2.0.7 charset-normalizer==2.0.12
# via requests # via requests
docutils==0.17.1 docutils==0.17.1
# via sphinx # via sphinx
idna==3.3 idna==3.3
# via requests # via requests
imagesize==1.2.0 imagesize==1.3.0
# via sphinx # via sphinx
jinja2==3.0.2 jinja2==3.1.1
# via sphinx # via sphinx
markupsafe==2.0.1 markupsafe==2.1.1
# via jinja2 # via jinja2
packaging==21.2 packaging==21.3
# via # via
# pallets-sphinx-themes # pallets-sphinx-themes
# sphinx # sphinx
pallets-sphinx-themes==2.0.1 pallets-sphinx-themes==2.0.2
# via -r requirements/docs.in # via -r requirements/docs.in
pygments==2.10.0 pygments==2.12.0
# via sphinx # via sphinx
pyparsing==2.4.7 pyparsing==3.0.8
# via packaging # via packaging
pytz==2021.3 pytz==2022.1
# via babel # via babel
requests==2.26.0 requests==2.27.1
# via sphinx # via sphinx
snowballstemmer==2.1.0 snowballstemmer==2.2.0
# via sphinx # via sphinx
sphinx==4.2.0 sphinx==4.5.0
# via # via
# -r requirements/docs.in # -r requirements/docs.in
# pallets-sphinx-themes # pallets-sphinx-themes
# sphinx-issues # sphinx-issues
# sphinxcontrib-log-cabinet # sphinxcontrib-log-cabinet
sphinx-issues==1.2.0 sphinx-issues==3.0.1
# via -r requirements/docs.in # via -r requirements/docs.in
sphinxcontrib-applehelp==1.0.2 sphinxcontrib-applehelp==1.0.2
# via sphinx # via sphinx
@ -60,8 +61,5 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx # via sphinx
sphinxcontrib-serializinghtml==1.1.5 sphinxcontrib-serializinghtml==1.1.5
# via sphinx # via sphinx
urllib3==1.26.7 urllib3==1.26.9
# via requests # via requests
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View File

@ -1,22 +1,23 @@
# SHA1:0eaa389e1fdb3a1917c0f987514bd561be5718ee
# #
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile-multi
# To update, run: # To update, run:
# #
# pip-compile requirements/tests.in # pip-compile-multi
# #
attrs==21.2.0 attrs==21.4.0
# via pytest # via pytest
iniconfig==1.1.1 iniconfig==1.1.1
# via pytest # via pytest
packaging==21.2 packaging==21.3
# via pytest # via pytest
pluggy==1.0.0 pluggy==1.0.0
# via pytest # via pytest
py==1.11.0 py==1.11.0
# via pytest # via pytest
pyparsing==2.4.7 pyparsing==3.0.8
# via packaging # via packaging
pytest==6.2.5 pytest==7.1.2
# via -r requirements/tests.in # via -r requirements/tests.in
toml==0.10.2 tomli==2.0.1
# via pytest # via pytest

View File

@ -1,14 +1,15 @@
# SHA1:7983aaa01d64547827c20395d77e248c41b2572f
# #
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile-multi
# To update, run: # To update, run:
# #
# pip-compile requirements/typing.in # pip-compile-multi
# #
mypy==0.910 mypy==0.950
# via -r requirements/typing.in # via -r requirements/typing.in
mypy-extensions==0.4.3 mypy-extensions==0.4.3
# via mypy # via mypy
toml==0.10.2 tomli==2.0.1
# via mypy # via mypy
typing-extensions==3.10.0.2 typing-extensions==4.2.0
# via mypy # via mypy

View File

@ -32,8 +32,8 @@ classifiers =
[options] [options]
packages = find: packages = find:
package_dir = = src package_dir = = src
include_package_data = true include_package_data = True
python_requires = >= 3.6 python_requires = >= 3.7
[options.packages.find] [options.packages.find]
where = src where = src
@ -72,7 +72,8 @@ per-file-ignores =
[mypy] [mypy]
files = src/jinja2 files = src/jinja2
python_version = 3.6 python_version = 3.7
show_error_codes = True
disallow_subclassing_any = True disallow_subclassing_any = True
disallow_untyped_calls = True disallow_untyped_calls = True
disallow_untyped_defs = True disallow_untyped_defs = True

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
from setuptools import setup from setuptools import setup
# Metadata goes in setup.cfg. These are here for GitHub's dependency graph. # Metadata goes in setup.cfg. These are here for GitHub's dependency graph.

View File

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: Jinja2 Name: Jinja2
Version: 3.0.3 Version: 3.1.2
Summary: A very fast and expressive template engine. Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/ Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher Author: Armin Ronacher
@ -24,7 +24,7 @@ Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.6 Requires-Python: >=3.7
Description-Content-Type: text/x-rst Description-Content-Type: text/x-rst
Provides-Extra: i18n Provides-Extra: i18n
License-File: LICENSE.rst License-File: LICENSE.rst

View File

@ -80,7 +80,6 @@ tests/test_compile.py
tests/test_core_tags.py tests/test_core_tags.py
tests/test_debug.py tests/test_debug.py
tests/test_ext.py tests/test_ext.py
tests/test_features.py
tests/test_filters.py tests/test_filters.py
tests/test_idtracking.py tests/test_idtracking.py
tests/test_imports.py tests/test_imports.py
@ -89,6 +88,7 @@ tests/test_lexnparse.py
tests/test_loader.py tests/test_loader.py
tests/test_nativetypes.py tests/test_nativetypes.py
tests/test_nodes.py tests/test_nodes.py
tests/test_pickle.py
tests/test_regression.py tests/test_regression.py
tests/test_runtime.py tests/test_runtime.py
tests/test_security.py tests/test_security.py

View File

@ -1,3 +1,2 @@
[babel.extractors] [babel.extractors]
jinja2 = jinja2.ext:babel_extract [i18n] jinja2 = jinja2.ext:babel_extract[i18n]

View File

@ -14,9 +14,6 @@ from .exceptions import TemplateRuntimeError as TemplateRuntimeError
from .exceptions import TemplatesNotFound as TemplatesNotFound from .exceptions import TemplatesNotFound as TemplatesNotFound
from .exceptions import TemplateSyntaxError as TemplateSyntaxError from .exceptions import TemplateSyntaxError as TemplateSyntaxError
from .exceptions import UndefinedError as UndefinedError from .exceptions import UndefinedError as UndefinedError
from .filters import contextfilter
from .filters import environmentfilter
from .filters import evalcontextfilter
from .loaders import BaseLoader as BaseLoader from .loaders import BaseLoader as BaseLoader
from .loaders import ChoiceLoader as ChoiceLoader from .loaders import ChoiceLoader as ChoiceLoader
from .loaders import DictLoader as DictLoader from .loaders import DictLoader as DictLoader
@ -31,15 +28,10 @@ from .runtime import make_logging_undefined as make_logging_undefined
from .runtime import StrictUndefined as StrictUndefined from .runtime import StrictUndefined as StrictUndefined
from .runtime import Undefined as Undefined from .runtime import Undefined as Undefined
from .utils import clear_caches as clear_caches from .utils import clear_caches as clear_caches
from .utils import contextfunction
from .utils import environmentfunction
from .utils import escape
from .utils import evalcontextfunction
from .utils import is_undefined as is_undefined from .utils import is_undefined as is_undefined
from .utils import Markup
from .utils import pass_context as pass_context from .utils import pass_context as pass_context
from .utils import pass_environment as pass_environment from .utils import pass_environment as pass_environment
from .utils import pass_eval_context as pass_eval_context from .utils import pass_eval_context as pass_eval_context
from .utils import select_autoescape as select_autoescape from .utils import select_autoescape as select_autoescape
__version__ = "3.0.3" __version__ = "3.1.2"

View File

@ -2,5 +2,5 @@ import re
# generated by scripts/generate_identifier_pattern.py # generated by scripts/generate_identifier_pattern.py
pattern = re.compile( pattern = re.compile(
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
) )

View File

@ -1,5 +1,6 @@
import inspect import inspect
import typing as t import typing as t
from functools import WRAPPER_ASSIGNMENTS
from functools import wraps from functools import wraps
from .utils import _PassArg from .utils import _PassArg
@ -23,7 +24,15 @@ def async_variant(normal_func): # type: ignore
def is_async(args: t.Any) -> bool: def is_async(args: t.Any) -> bool:
return t.cast(bool, args[0].environment.is_async) return t.cast(bool, args[0].environment.is_async)
@wraps(normal_func) # 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 def wrapper(*args, **kwargs): # type: ignore
b = is_async(args) b = is_async(args)

View File

@ -79,7 +79,7 @@ class Bucket:
self.reset() self.reset()
return return
def write_bytecode(self, f: t.BinaryIO) -> None: def write_bytecode(self, f: t.IO[bytes]) -> None:
"""Dump the bytecode into the file or file like object passed.""" """Dump the bytecode into the file or file like object passed."""
if self.code is None: if self.code is None:
raise TypeError("can't write empty bucket") raise TypeError("can't write empty bucket")
@ -262,13 +262,55 @@ class FileSystemBytecodeCache(BytecodeCache):
def load_bytecode(self, bucket: Bucket) -> None: def load_bytecode(self, bucket: Bucket) -> None:
filename = self._get_cache_filename(bucket) filename = self._get_cache_filename(bucket)
if os.path.exists(filename): # Don't test for existence before opening the file, since the
with open(filename, "rb") as f: # 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) bucket.load_bytecode(f)
def dump_bytecode(self, bucket: Bucket) -> None: def dump_bytecode(self, bucket: Bucket) -> None:
with open(self._get_cache_filename(bucket), "wb") as f: # 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) 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: def clear(self) -> None:
# imported lazily here because google app-engine doesn't support # imported lazily here because google app-engine doesn't support

View File

@ -218,7 +218,7 @@ class Frame:
def copy(self) -> "Frame": def copy(self) -> "Frame":
"""Create a copy of the current one.""" """Create a copy of the current one."""
rv = t.cast(Frame, object.__new__(self.__class__)) rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__) rv.__dict__.update(self.__dict__)
rv.symbols = self.symbols.copy() rv.symbols = self.symbols.copy()
return rv return rv
@ -724,6 +724,7 @@ class CodeGenerator(NodeVisitor):
""" """
self.writeline("resolve = context.resolve_or_missing") self.writeline("resolve = context.resolve_or_missing")
self.writeline("undefined = environment.undefined") self.writeline("undefined = environment.undefined")
self.writeline("concat = environment.concat")
# always use the standard Undefined class for the implicit else of # always use the standard Undefined class for the implicit else of
# conditional expressions # conditional expressions
self.writeline("cond_expr_undefined = Undefined") self.writeline("cond_expr_undefined = Undefined")
@ -835,7 +836,6 @@ class CodeGenerator(NodeVisitor):
else: else:
exported_names = sorted(exported) exported_names = sorted(exported)
self.writeline("from __future__ import generator_stop") # Python < 3.7
self.writeline("from jinja2.runtime import " + ", ".join(exported_names)) self.writeline("from jinja2.runtime import " + ", ".join(exported_names))
# if we want a deferred initialization we cannot move the # if we want a deferred initialization we cannot move the
@ -1755,7 +1755,7 @@ class CodeGenerator(NodeVisitor):
self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool
) -> t.Iterator[None]: ) -> t.Iterator[None]:
if self.environment.is_async: if self.environment.is_async:
self.write("await auto_await(") self.write("(await auto_await(")
if is_filter: if is_filter:
self.write(f"{self.filters[node.name]}(") self.write(f"{self.filters[node.name]}(")
@ -1790,7 +1790,7 @@ class CodeGenerator(NodeVisitor):
self.write(")") self.write(")")
if self.environment.is_async: if self.environment.is_async:
self.write(")") self.write("))")
@optimizeconst @optimizeconst
def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None:
@ -1842,7 +1842,7 @@ class CodeGenerator(NodeVisitor):
self, node: nodes.Call, frame: Frame, forward_caller: bool = False self, node: nodes.Call, frame: Frame, forward_caller: bool = False
) -> None: ) -> None:
if self.environment.is_async: if self.environment.is_async:
self.write("await auto_await(") self.write("(await auto_await(")
if self.environment.sandboxed: if self.environment.sandboxed:
self.write("environment.call(context, ") self.write("environment.call(context, ")
else: else:
@ -1858,7 +1858,7 @@ class CodeGenerator(NodeVisitor):
self.signature(node, frame, extra_kwargs) self.signature(node, frame, extra_kwargs)
self.write(")") self.write(")")
if self.environment.is_async: if self.environment.is_async:
self.write(")") self.write("))")
def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None: def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None:
self.write(node.key + "=") self.write(node.key + "=")

View File

@ -1,4 +1,3 @@
import platform
import sys import sys
import typing as t import typing as t
from types import CodeType from types import CodeType
@ -68,7 +67,8 @@ def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
# Assign tb_next in reverse to avoid circular references. # Assign tb_next in reverse to avoid circular references.
for tb in reversed(stack): for tb in reversed(stack):
tb_next = tb_set_next(tb, tb_next) tb.tb_next = tb_next
tb_next = tb
return exc_value.with_traceback(tb_next) return exc_value.with_traceback(tb_next)
@ -189,71 +189,3 @@ def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any
data[name] = value data[name] = value
return data return data
if sys.version_info >= (3, 7):
# tb_next is directly assignable as of Python 3.7
def tb_set_next(
tb: TracebackType, tb_next: t.Optional[TracebackType]
) -> TracebackType:
tb.tb_next = tb_next
return tb
elif platform.python_implementation() == "PyPy":
# PyPy might have special support, and won't work with ctypes.
try:
import tputil # type: ignore
except ImportError:
# Without tproxy support, use the original traceback.
def tb_set_next(
tb: TracebackType, tb_next: t.Optional[TracebackType]
) -> TracebackType:
return tb
else:
# With tproxy support, create a proxy around the traceback that
# returns the new tb_next.
def tb_set_next(
tb: TracebackType, tb_next: t.Optional[TracebackType]
) -> TracebackType:
def controller(op): # type: ignore
if op.opname == "__getattribute__" and op.args[0] == "tb_next":
return tb_next
return op.delegate()
return tputil.make_proxy(controller, obj=tb) # type: ignore
else:
# Use ctypes to assign tb_next at the C level since it's read-only
# from Python.
import ctypes
class _CTraceback(ctypes.Structure):
_fields_ = [
# Extra PyObject slots when compiled with Py_TRACE_REFS.
("PyObject_HEAD", ctypes.c_byte * object().__sizeof__()),
# Only care about tb_next as an object, not a traceback.
("tb_next", ctypes.py_object),
]
def tb_set_next(
tb: TracebackType, tb_next: t.Optional[TracebackType]
) -> TracebackType:
c_tb = _CTraceback.from_address(id(tb))
# Clear out the old tb_next.
if tb.tb_next is not None:
c_tb_next = ctypes.py_object(tb.tb_next)
c_tb.tb_next = ctypes.py_object()
ctypes.pythonapi.Py_DecRef(c_tb_next)
# Assign the new tb_next.
if tb_next is not None:
c_tb_next = ctypes.py_object(tb_next)
ctypes.pythonapi.Py_IncRef(c_tb_next)
c_tb.tb_next = c_tb_next
return tb

View File

@ -2,7 +2,6 @@
options. options.
""" """
import os import os
import sys
import typing import typing
import typing as t import typing as t
import weakref import weakref
@ -282,6 +281,8 @@ class Environment:
#: :class:`~jinja2.compiler.CodeGenerator` for more information. #: :class:`~jinja2.compiler.CodeGenerator` for more information.
code_generator_class: t.Type["CodeGenerator"] = CodeGenerator code_generator_class: t.Type["CodeGenerator"] = CodeGenerator
concat = "".join
#: the context class that is used for templates. See #: the context class that is used for templates. See
#: :class:`~jinja2.runtime.Context` for more information. #: :class:`~jinja2.runtime.Context` for more information.
context_class: t.Type[Context] = Context context_class: t.Type[Context] = Context
@ -392,6 +393,8 @@ class Environment:
line_comment_prefix: t.Optional[str] = missing, line_comment_prefix: t.Optional[str] = missing,
trim_blocks: bool = missing, trim_blocks: bool = missing,
lstrip_blocks: bool = missing, lstrip_blocks: bool = missing,
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
keep_trailing_newline: bool = missing,
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing, extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
optimized: bool = missing, optimized: bool = missing,
undefined: t.Type[Undefined] = missing, undefined: t.Type[Undefined] = missing,
@ -401,6 +404,7 @@ class Environment:
cache_size: int = missing, cache_size: int = missing,
auto_reload: bool = missing, auto_reload: bool = missing,
bytecode_cache: t.Optional["BytecodeCache"] = missing, bytecode_cache: t.Optional["BytecodeCache"] = missing,
enable_async: bool = False,
) -> "Environment": ) -> "Environment":
"""Create a new overlay environment that shares all the data with the """Create a new overlay environment that shares all the data with the
current environment except for cache and the overridden attributes. current environment except for cache and the overridden attributes.
@ -412,9 +416,13 @@ class Environment:
up completely. Not all attributes are truly linked, some are just up completely. Not all attributes are truly linked, some are just
copied over so modifications on the original environment may not shine copied over so modifications on the original environment may not shine
through. through.
.. versionchanged:: 3.1.2
Added the ``newline_sequence``,, ``keep_trailing_newline``,
and ``enable_async`` parameters to match ``__init__``.
""" """
args = dict(locals()) args = dict(locals())
del args["self"], args["cache_size"], args["extensions"] del args["self"], args["cache_size"], args["extensions"], args["enable_async"]
rv = object.__new__(self.__class__) rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__) rv.__dict__.update(self.__dict__)
@ -436,6 +444,9 @@ class Environment:
if extensions is not missing: if extensions is not missing:
rv.extensions.update(load_extensions(rv, extensions)) rv.extensions.update(load_extensions(rv, extensions))
if enable_async is not missing:
rv.is_async = enable_async
return _environment_config_check(rv) return _environment_config_check(rv)
@property @property
@ -938,7 +949,7 @@ class Environment:
@internalcode @internalcode
def _load_template( def _load_template(
self, name: str, globals: t.Optional[t.Mapping[str, t.Any]] self, name: str, globals: t.Optional[t.MutableMapping[str, t.Any]]
) -> "Template": ) -> "Template":
if self.loader is None: if self.loader is None:
raise TypeError("no loader for this environment specified") raise TypeError("no loader for this environment specified")
@ -966,13 +977,15 @@ class Environment:
self, self,
name: t.Union[str, "Template"], name: t.Union[str, "Template"],
parent: t.Optional[str] = None, parent: t.Optional[str] = None,
globals: t.Optional[t.Mapping[str, t.Any]] = None, globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
) -> "Template": ) -> "Template":
"""Load a template by name with :attr:`loader` and return a """Load a template by name with :attr:`loader` and return a
:class:`Template`. If the template does not exist a :class:`Template`. If the template does not exist a
:exc:`TemplateNotFound` exception is raised. :exc:`TemplateNotFound` exception is raised.
:param name: Name of the template to load. :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 :param parent: The name of the parent template importing this
template. :meth:`join_path` can be used to implement name template. :meth:`join_path` can be used to implement name
transformations with this. transformations with this.
@ -1001,7 +1014,7 @@ class Environment:
self, self,
names: t.Iterable[t.Union[str, "Template"]], names: t.Iterable[t.Union[str, "Template"]],
parent: t.Optional[str] = None, parent: t.Optional[str] = None,
globals: t.Optional[t.Mapping[str, t.Any]] = None, globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
) -> "Template": ) -> "Template":
"""Like :meth:`get_template`, but tries loading multiple names. """Like :meth:`get_template`, but tries loading multiple names.
If none of the names can be loaded a :exc:`TemplatesNotFound` If none of the names can be loaded a :exc:`TemplatesNotFound`
@ -1057,7 +1070,7 @@ class Environment:
str, "Template", t.List[t.Union[str, "Template"]] str, "Template", t.List[t.Union[str, "Template"]]
], ],
parent: t.Optional[str] = None, parent: t.Optional[str] = None,
globals: t.Optional[t.Mapping[str, t.Any]] = None, globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
) -> "Template": ) -> "Template":
"""Use :meth:`select_template` if an iterable of template names """Use :meth:`select_template` if an iterable of template names
is given, or :meth:`get_template` if one name is given. is given, or :meth:`get_template` if one name is given.
@ -1073,7 +1086,7 @@ class Environment:
def from_string( def from_string(
self, self,
source: t.Union[str, nodes.Template], source: t.Union[str, nodes.Template],
globals: t.Optional[t.Mapping[str, t.Any]] = None, globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
template_class: t.Optional[t.Type["Template"]] = None, template_class: t.Optional[t.Type["Template"]] = None,
) -> "Template": ) -> "Template":
"""Load a template from a source string without using """Load a template from a source string without using
@ -1092,7 +1105,7 @@ class Environment:
return cls.from_code(self, self.compile(source), gs, None) return cls.from_code(self, self.compile(source), gs, None)
def make_globals( def make_globals(
self, d: t.Optional[t.Mapping[str, t.Any]] self, d: t.Optional[t.MutableMapping[str, t.Any]]
) -> t.MutableMapping[str, t.Any]: ) -> t.MutableMapping[str, t.Any]:
"""Make the globals map for a template. Any given template """Make the globals map for a template. Any given template
globals overlay the environment :attr:`globals`. globals overlay the environment :attr:`globals`.
@ -1268,9 +1281,6 @@ class Template:
close = False close = False
if sys.version_info < (3, 7):
loop = asyncio.get_event_loop()
else:
try: try:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
except RuntimeError: except RuntimeError:
@ -1286,7 +1296,7 @@ class Template:
ctx = self.new_context(dict(*args, **kwargs)) ctx = self.new_context(dict(*args, **kwargs))
try: try:
return concat(self.root_render_func(ctx)) # type: ignore return self.environment.concat(self.root_render_func(ctx)) # type: ignore
except Exception: except Exception:
self.environment.handle_exception() self.environment.handle_exception()
@ -1307,7 +1317,9 @@ class Template:
ctx = self.new_context(dict(*args, **kwargs)) ctx = self.new_context(dict(*args, **kwargs))
try: try:
return concat([n async for n in self.root_render_func(ctx)]) # type: ignore return self.environment.concat( # type: ignore
[n async for n in self.root_render_func(ctx)] # type: ignore
)
except Exception: except Exception:
return self.environment.handle_exception() return self.environment.handle_exception()
@ -1331,13 +1343,7 @@ class Template:
async def to_list() -> t.List[str]: async def to_list() -> t.List[str]:
return [x async for x in self.generate_async(*args, **kwargs)] return [x async for x in self.generate_async(*args, **kwargs)]
if sys.version_info < (3, 7): yield from asyncio.run(to_list())
loop = asyncio.get_event_loop()
out = loop.run_until_complete(to_list())
else:
out = asyncio.run(to_list())
yield from out
return return
ctx = self.new_context(dict(*args, **kwargs)) ctx = self.new_context(dict(*args, **kwargs))

View File

@ -2,7 +2,6 @@
import pprint import pprint
import re import re
import typing as t import typing as t
import warnings
from markupsafe import Markup from markupsafe import Markup
@ -91,7 +90,7 @@ class Extension:
def bind(self, environment: Environment) -> "Extension": def bind(self, environment: Environment) -> "Extension":
"""Create a copy of this extension bound to another environment.""" """Create a copy of this extension bound to another environment."""
rv = t.cast(Extension, object.__new__(self.__class__)) rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__) rv.__dict__.update(self.__dict__)
rv.environment = environment rv.environment = environment
return rv return rv
@ -355,13 +354,19 @@ class InternationalizationExtension(Extension):
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]: def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
"""Parse a translatable tag.""" """Parse a translatable tag."""
lineno = next(parser.stream).lineno lineno = next(parser.stream).lineno
num_called_num = False
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 # find all the variables referenced. Additionally a variable can be
# defined in the body of the trans block too, but this is checked at # defined in the body of the trans block too, but this is checked at
# a later state. # a later state.
plural_expr: t.Optional[nodes.Expr] = None plural_expr: t.Optional[nodes.Expr] = None
plural_expr_assignment: t.Optional[nodes.Assign] = None plural_expr_assignment: t.Optional[nodes.Assign] = None
num_called_num = False
variables: t.Dict[str, nodes.Expr] = {} variables: t.Dict[str, nodes.Expr] = {}
trimmed = None trimmed = None
while parser.stream.current.type != "block_end": while parser.stream.current.type != "block_end":
@ -456,6 +461,7 @@ class InternationalizationExtension(Extension):
node = self._make_node( node = self._make_node(
singular, singular,
plural, plural,
context,
variables, variables,
plural_expr, plural_expr,
bool(referenced), bool(referenced),
@ -511,6 +517,7 @@ class InternationalizationExtension(Extension):
self, self,
singular: str, singular: str,
plural: t.Optional[str], plural: t.Optional[str],
context: t.Optional[str],
variables: t.Dict[str, nodes.Expr], variables: t.Dict[str, nodes.Expr],
plural_expr: t.Optional[nodes.Expr], plural_expr: t.Optional[nodes.Expr],
vars_referenced: bool, vars_referenced: bool,
@ -527,21 +534,18 @@ class InternationalizationExtension(Extension):
if plural: if plural:
plural = plural.replace("%%", "%") plural = plural.replace("%%", "%")
# singular only: func_name = "gettext"
if plural_expr is None: func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
gettext = nodes.Name("gettext", "load")
node = nodes.Call(gettext, [nodes.Const(singular)], [], None, None)
# singular and plural if context is not None:
else: func_args.insert(0, nodes.Const(context))
ngettext = nodes.Name("ngettext", "load") func_name = f"p{func_name}"
node = nodes.Call(
ngettext, if plural_expr is not None:
[nodes.Const(singular), nodes.Const(plural), plural_expr], func_name = f"n{func_name}"
[], func_args.extend((nodes.Const(plural), plural_expr))
None,
None, node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)
)
# in case newstyle gettext is used, the method is powerful # in case newstyle gettext is used, the method is powerful
# enough to handle the variable expansion and autoescape # enough to handle the variable expansion and autoescape
@ -597,28 +601,6 @@ class LoopControlExtension(Extension):
return nodes.Continue(lineno=token.lineno) return nodes.Continue(lineno=token.lineno)
class WithExtension(Extension):
def __init__(self, environment: Environment) -> None:
super().__init__(environment)
warnings.warn(
"The 'with' extension is deprecated and will be removed in"
" Jinja 3.1. This is built in now.",
DeprecationWarning,
stacklevel=3,
)
class AutoEscapeExtension(Extension):
def __init__(self, environment: Environment) -> None:
super().__init__(environment)
warnings.warn(
"The 'autoescape' extension is deprecated and will be"
" removed in Jinja 3.1. This is built in now.",
DeprecationWarning,
stacklevel=3,
)
class DebugExtension(Extension): class DebugExtension(Extension):
"""A ``{% debug %}`` tag that dumps the available variables, """A ``{% debug %}`` tag that dumps the available variables,
filters, and tests. filters, and tests.
@ -874,6 +856,4 @@ def babel_extract(
i18n = InternationalizationExtension i18n = InternationalizationExtension
do = ExprStmtExtension do = ExprStmtExtension
loopcontrols = LoopControlExtension loopcontrols = LoopControlExtension
with_ = WithExtension
autoescape = AutoEscapeExtension
debug = DebugExtension debug = DebugExtension

View File

@ -4,7 +4,6 @@ import random
import re import re
import typing import typing
import typing as t import typing as t
import warnings
from collections import abc from collections import abc
from itertools import chain from itertools import chain
from itertools import groupby from itertools import groupby
@ -44,58 +43,6 @@ K = t.TypeVar("K")
V = t.TypeVar("V") V = t.TypeVar("V")
def contextfilter(f: F) -> F:
"""Pass the context as the first argument to the decorated function.
.. deprecated:: 3.0
Will be removed in Jinja 3.1. Use :func:`~jinja2.pass_context`
instead.
"""
warnings.warn(
"'contextfilter' is renamed to 'pass_context', the old name"
" will be removed in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return pass_context(f)
def evalcontextfilter(f: F) -> F:
"""Pass the eval context as the first argument to the decorated
function.
.. deprecated:: 3.0
Will be removed in Jinja 3.1. Use
:func:`~jinja2.pass_eval_context` instead.
.. versionadded:: 2.4
"""
warnings.warn(
"'evalcontextfilter' is renamed to 'pass_eval_context', the old"
" name will be removed in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return pass_eval_context(f)
def environmentfilter(f: F) -> F:
"""Pass the environment as the first argument to the decorated
function.
.. deprecated:: 3.0
Will be removed in Jinja 3.1. Use
:func:`~jinja2.pass_environment` instead.
"""
warnings.warn(
"'environmentfilter' is renamed to 'pass_environment', the old"
" name will be removed in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return pass_environment(f)
def ignore_case(value: V) -> V: def ignore_case(value: V) -> V:
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings """For use as a postprocessor for :func:`make_attrgetter`. Converts strings
to lowercase and returns other types as-is.""" to lowercase and returns other types as-is."""
@ -271,6 +218,36 @@ def do_lower(s: str) -> str:
return soft_str(s).lower() return soft_str(s).lower()
def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.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
<dl>
{% for key, value in my_dict|items %}
<dt>{{ key }}
<dd>{{ value }}
{% endfor %}
</dl>
.. 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()
@pass_eval_context @pass_eval_context
def do_xmlattr( def do_xmlattr(
eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
@ -415,7 +392,7 @@ def do_sort(
.. sourcecode:: jinja .. sourcecode:: jinja
{% for user users|sort(attribute="age,name") %} {% for user in users|sort(attribute="age,name") %}
... ...
{% endfor %} {% endfor %}
@ -1164,7 +1141,7 @@ def do_round(
return round(value, precision) return round(value, precision)
func = getattr(math, method) func = getattr(math, method)
return t.cast(float, func(value * (10 ** precision)) / (10 ** precision)) return t.cast(float, func(value * (10**precision)) / (10**precision))
class _GroupTuple(t.NamedTuple): class _GroupTuple(t.NamedTuple):
@ -1186,7 +1163,8 @@ def sync_do_groupby(
value: "t.Iterable[V]", value: "t.Iterable[V]",
attribute: t.Union[str, int], attribute: t.Union[str, int],
default: t.Optional[t.Any] = None, default: t.Optional[t.Any] = None,
) -> "t.List[t.Tuple[t.Any, t.List[V]]]": case_sensitive: bool = False,
) -> "t.List[_GroupTuple]":
"""Group a sequence of objects by an attribute using Python's """Group a sequence of objects by an attribute using Python's
:func:`itertools.groupby`. The attribute can use dot notation for :func:`itertools.groupby`. The attribute can use dot notation for
nested access, like ``"address.city"``. Unlike Python's ``groupby``, nested access, like ``"address.city"``. Unlike Python's ``groupby``,
@ -1226,18 +1204,42 @@ def sync_do_groupby(
<li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li> <li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li>
{% endfor %}</ul> {% endfor %}</ul>
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 .. versionchanged:: 3.0
Added the ``default`` parameter. Added the ``default`` parameter.
.. versionchanged:: 2.6 .. versionchanged:: 2.6
The attribute supports dot notation for nested access. The attribute supports dot notation for nested access.
""" """
expr = make_attrgetter(environment, attribute, default=default) expr = make_attrgetter(
return [ environment,
attribute,
postprocess=ignore_case if not case_sensitive else None,
default=default,
)
out = [
_GroupTuple(key, list(values)) _GroupTuple(key, list(values))
for key, values in groupby(sorted(value, key=expr), expr) 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_variant(sync_do_groupby) # type: ignore
async def do_groupby( async def do_groupby(
@ -1245,13 +1247,26 @@ async def do_groupby(
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
attribute: t.Union[str, int], attribute: t.Union[str, int],
default: t.Optional[t.Any] = None, default: t.Optional[t.Any] = None,
) -> "t.List[t.Tuple[t.Any, t.List[V]]]": case_sensitive: bool = False,
expr = make_attrgetter(environment, attribute, default=default) ) -> "t.List[_GroupTuple]":
return [ expr = make_attrgetter(
environment,
attribute,
postprocess=ignore_case if not case_sensitive else None,
default=default,
)
out = [
_GroupTuple(key, await auto_to_list(values)) _GroupTuple(key, await auto_to_list(values))
for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr) 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 @pass_environment
def sync_do_sum( def sync_do_sum(
@ -1271,13 +1286,13 @@ def sync_do_sum(
Total: {{ items|sum(attribute='price') }} Total: {{ items|sum(attribute='price') }}
.. versionchanged:: 2.6 .. versionchanged:: 2.6
The `attribute` parameter was added to allow suming up over The ``attribute`` parameter was added to allow summing up over
attributes. Also the `start` parameter was moved on to the right. attributes. Also the ``start`` parameter was moved on to the right.
""" """
if attribute is not None: if attribute is not None:
iterable = map(make_attrgetter(environment, attribute), iterable) iterable = map(make_attrgetter(environment, attribute), iterable)
return sum(iterable, start) return sum(iterable, start) # type: ignore[no-any-return, call-overload]
@async_variant(sync_do_sum) # type: ignore @async_variant(sync_do_sum) # type: ignore
@ -1792,6 +1807,7 @@ FILTERS = {
"length": len, "length": len,
"list": do_list, "list": do_list,
"lower": do_lower, "lower": do_lower,
"items": do_items,
"map": do_map, "map": do_map,
"min": do_min, "min": do_min,
"max": do_max, "max": do_max,

View File

@ -84,7 +84,7 @@ class Symbols:
return rv return rv
def copy(self) -> "Symbols": def copy(self) -> "Symbols":
rv = t.cast(Symbols, object.__new__(self.__class__)) rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__) rv.__dict__.update(self.__dict__)
rv.refs = self.refs.copy() rv.refs = self.refs.copy()
rv.loads = self.loads.copy() rv.loads = self.loads.copy()

View File

@ -507,19 +507,17 @@ class Lexer:
# block suffix if trimming is enabled # block suffix if trimming is enabled
block_suffix_re = "\\n?" if environment.trim_blocks else "" block_suffix_re = "\\n?" if environment.trim_blocks else ""
# If lstrip is enabled, it should not be applied if there is any self.lstrip_blocks = environment.lstrip_blocks
# non-whitespace between the newline and block.
self.lstrip_unless_re = c(r"[^ \t]") if environment.lstrip_blocks else None
self.newline_sequence = environment.newline_sequence self.newline_sequence = environment.newline_sequence
self.keep_trailing_newline = environment.keep_trailing_newline self.keep_trailing_newline = environment.keep_trailing_newline
root_raw_re = ( root_raw_re = (
fr"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*" rf"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
fr"(?:\-{block_end_re}\s*|{block_end_re}))" rf"(?:\-{block_end_re}\s*|{block_end_re}))"
) )
root_parts_re = "|".join( root_parts_re = "|".join(
[root_raw_re] + [fr"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules] [root_raw_re] + [rf"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules]
) )
# global lexing rules # global lexing rules
@ -527,7 +525,7 @@ class Lexer:
"root": [ "root": [
# directives # directives
_Rule( _Rule(
c(fr"(.*?)(?:{root_parts_re})"), c(rf"(.*?)(?:{root_parts_re})"),
OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore
"#bygroup", "#bygroup",
), ),
@ -538,8 +536,8 @@ class Lexer:
TOKEN_COMMENT_BEGIN: [ TOKEN_COMMENT_BEGIN: [
_Rule( _Rule(
c( c(
fr"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*" rf"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
fr"|{comment_end_re}{block_suffix_re}))" rf"|{comment_end_re}{block_suffix_re}))"
), ),
(TOKEN_COMMENT, TOKEN_COMMENT_END), (TOKEN_COMMENT, TOKEN_COMMENT_END),
"#pop", "#pop",
@ -550,8 +548,8 @@ class Lexer:
TOKEN_BLOCK_BEGIN: [ TOKEN_BLOCK_BEGIN: [
_Rule( _Rule(
c( c(
fr"(?:\+{block_end_re}|\-{block_end_re}\s*" rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
fr"|{block_end_re}{block_suffix_re})" rf"|{block_end_re}{block_suffix_re})"
), ),
TOKEN_BLOCK_END, TOKEN_BLOCK_END,
"#pop", "#pop",
@ -561,7 +559,7 @@ class Lexer:
# variables # variables
TOKEN_VARIABLE_BEGIN: [ TOKEN_VARIABLE_BEGIN: [
_Rule( _Rule(
c(fr"\-{variable_end_re}\s*|{variable_end_re}"), c(rf"\-{variable_end_re}\s*|{variable_end_re}"),
TOKEN_VARIABLE_END, TOKEN_VARIABLE_END,
"#pop", "#pop",
) )
@ -571,9 +569,9 @@ class Lexer:
TOKEN_RAW_BEGIN: [ TOKEN_RAW_BEGIN: [
_Rule( _Rule(
c( c(
fr"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*" rf"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
fr"(?:\+{block_end_re}|\-{block_end_re}\s*" rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
fr"|{block_end_re}{block_suffix_re}))" rf"|{block_end_re}{block_suffix_re}))"
), ),
OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore
"#pop", "#pop",
@ -697,7 +695,6 @@ class Lexer:
statetokens = self.rules[stack[-1]] statetokens = self.rules[stack[-1]]
source_length = len(source) source_length = len(source)
balancing_stack: t.List[str] = [] balancing_stack: t.List[str] = []
lstrip_unless_re = self.lstrip_unless_re
newlines_stripped = 0 newlines_stripped = 0
line_starting = True line_starting = True
@ -723,7 +720,7 @@ class Lexer:
# tuples support more options # tuples support more options
if isinstance(tokens, tuple): if isinstance(tokens, tuple):
groups = m.groups() groups: t.Sequence[str] = m.groups()
if isinstance(tokens, OptionalLStrip): if isinstance(tokens, OptionalLStrip):
# Rule supports lstrip. Match will look like # Rule supports lstrip. Match will look like
@ -743,7 +740,7 @@ class Lexer:
# Not marked for preserving whitespace. # Not marked for preserving whitespace.
strip_sign != "+" strip_sign != "+"
# lstrip is enabled. # lstrip is enabled.
and lstrip_unless_re is not None and self.lstrip_blocks
# Not a variable expression. # Not a variable expression.
and not m.groupdict().get(TOKEN_VARIABLE_BEGIN) and not m.groupdict().get(TOKEN_VARIABLE_BEGIN)
): ):
@ -753,7 +750,7 @@ class Lexer:
if l_pos > 0 or line_starting: if l_pos > 0 or line_starting:
# If there's only whitespace between the newline and the # If there's only whitespace between the newline and the
# tag, strip it. # tag, strip it.
if not lstrip_unless_re.search(text, l_pos): if whitespace_re.fullmatch(text, l_pos):
groups = [text[:l_pos], *groups[1:]] groups = [text[:l_pos], *groups[1:]]
for idx, token in enumerate(tokens): for idx, token in enumerate(tokens):

View File

@ -3,6 +3,7 @@ sources.
""" """
import importlib.util import importlib.util
import os import os
import posixpath
import sys import sys
import typing as t import typing as t
import weakref import weakref
@ -193,7 +194,9 @@ class FileSystemLoader(BaseLoader):
) -> t.Tuple[str, str, t.Callable[[], bool]]: ) -> t.Tuple[str, str, t.Callable[[], bool]]:
pieces = split_template_path(template) pieces = split_template_path(template)
for searchpath in self.searchpath: for searchpath in self.searchpath:
filename = os.path.join(searchpath, *pieces) # 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) f = open_if_exists(filename)
if f is None: if f is None:
continue continue
@ -210,7 +213,8 @@ class FileSystemLoader(BaseLoader):
except OSError: except OSError:
return False return False
return contents, filename, uptodate # Use normpath to convert Windows altsep to sep.
return contents, os.path.normpath(filename), uptodate
raise TemplateNotFound(template) raise TemplateNotFound(template)
def list_templates(self) -> t.List[str]: def list_templates(self) -> t.List[str]:
@ -296,7 +300,7 @@ class PackageLoader(BaseLoader):
if isinstance(loader, zipimport.zipimporter): if isinstance(loader, zipimport.zipimporter):
self._archive = loader.archive self._archive = loader.archive
pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore
template_root = os.path.join(pkgdir, package_path) template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
else: else:
roots: t.List[str] = [] roots: t.List[str] = []
@ -326,7 +330,12 @@ class PackageLoader(BaseLoader):
def get_source( def get_source(
self, environment: "Environment", template: str self, environment: "Environment", template: str
) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]: ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
p = os.path.join(self._template_root, *split_template_path(template)) # Use posixpath even on Windows to avoid "drive:" or UNC
# segments breaking out of the search directory. Use normpath to
# convert Windows altsep to sep.
p = os.path.normpath(
posixpath.join(self._template_root, *split_template_path(template))
)
up_to_date: t.Optional[t.Callable[[], bool]] up_to_date: t.Optional[t.Callable[[], bool]]
if self._archive is None: if self._archive is None:
@ -603,7 +612,7 @@ class ModuleLoader(BaseLoader):
if not isinstance(path, abc.Iterable) or isinstance(path, str): if not isinstance(path, abc.Iterable) or isinstance(path, str):
path = [path] path = [path]
mod.__path__ = [os.fspath(p) for p in path] # type: ignore mod.__path__ = [os.fspath(p) for p in path]
sys.modules[package_name] = weakref.proxy( sys.modules[package_name] = weakref.proxy(
mod, lambda x: sys.modules.pop(package_name, None) mod, lambda x: sys.modules.pop(package_name, None)

View File

@ -3,6 +3,7 @@ from ast import literal_eval
from ast import parse from ast import parse
from itertools import chain from itertools import chain
from itertools import islice from itertools import islice
from types import GeneratorType
from . import nodes from . import nodes
from .compiler import CodeGenerator from .compiler import CodeGenerator
@ -31,7 +32,9 @@ def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
if not isinstance(raw, str): if not isinstance(raw, str):
return raw return raw
else: else:
raw = "".join([str(v) for v in chain(head, values)]) if isinstance(values, GeneratorType):
values = chain(head, values)
raw = "".join([str(v) for v in values])
try: try:
return literal_eval( return literal_eval(
@ -86,6 +89,7 @@ class NativeEnvironment(Environment):
"""An environment that renders templates to native Python types.""" """An environment that renders templates to native Python types."""
code_generator_class = NativeCodeGenerator code_generator_class = NativeCodeGenerator
concat = staticmethod(native_concat) # type: ignore
class NativeTemplate(Template): class NativeTemplate(Template):
@ -101,7 +105,9 @@ class NativeTemplate(Template):
ctx = self.new_context(dict(*args, **kwargs)) ctx = self.new_context(dict(*args, **kwargs))
try: try:
return native_concat(self.root_render_func(ctx)) # type: ignore return self.environment_class.concat( # type: ignore
self.root_render_func(ctx) # type: ignore
)
except Exception: except Exception:
return self.environment.handle_exception() return self.environment.handle_exception()
@ -114,7 +120,7 @@ class NativeTemplate(Template):
ctx = self.new_context(dict(*args, **kwargs)) ctx = self.new_context(dict(*args, **kwargs))
try: try:
return native_concat( return self.environment_class.concat( # type: ignore
[n async for n in self.root_render_func(ctx)] # type: ignore [n async for n in self.root_render_func(ctx)] # type: ignore
) )
except Exception: except Exception:

View File

@ -160,7 +160,7 @@ class Parser:
self._last_identifier += 1 self._last_identifier += 1
rv = object.__new__(nodes.InternalName) rv = object.__new__(nodes.InternalName)
nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno) nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
return rv # type: ignore return rv
def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]: def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]:
"""Parse a single statement.""" """Parse a single statement."""
@ -364,14 +364,10 @@ class Parser:
node.names = [] node.names = []
def parse_context() -> bool: def parse_context() -> bool:
if ( if self.stream.current.value in {
self.stream.current.value
in {
"with", "with",
"without", "without",
} } and self.stream.look().test("name:context"):
and self.stream.look().test("name:context")
):
node.with_context = next(self.stream).value == "with" node.with_context = next(self.stream).value == "with"
self.stream.skip() self.stream.skip()
return True return True
@ -957,9 +953,7 @@ class Parser:
kwargs = [] kwargs = []
if self.stream.current.type == "lparen": if self.stream.current.type == "lparen":
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
elif ( elif self.stream.current.type in {
self.stream.current.type
in {
"name", "name",
"string", "string",
"integer", "integer",
@ -967,9 +961,7 @@ class Parser:
"lparen", "lparen",
"lbracket", "lbracket",
"lbrace", "lbrace",
} } and not self.stream.current.test_any("name:else", "name:or", "name:and"):
and not self.stream.current.test_any("name:else", "name:or", "name:and")
):
if self.stream.current.test("name:is"): if self.stream.current.test("name:is"):
self.fail("You cannot chain multiple tests with is") self.fail("You cannot chain multiple tests with is")
arg_node = self.parse_primary() arg_node = self.parse_primary()

View File

@ -49,7 +49,6 @@ exported = [
"Markup", "Markup",
"TemplateRuntimeError", "TemplateRuntimeError",
"missing", "missing",
"concat",
"escape", "escape",
"markup_join", "markup_join",
"str_join", "str_join",
@ -89,18 +88,6 @@ def str_join(seq: t.Iterable[t.Any]) -> str:
return concat(map(str, seq)) return concat(map(str, seq))
def unicode_join(seq: t.Iterable[t.Any]) -> str:
import warnings
warnings.warn(
"This template must be recompiled with at least Jinja 3.0, or"
" it will fail in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return str_join(seq)
def new_context( def new_context(
environment: "Environment", environment: "Environment",
template_name: t.Optional[str], template_name: t.Optional[str],
@ -173,27 +160,6 @@ class Context:
:class:`Undefined` object for missing variables. :class:`Undefined` object for missing variables.
""" """
_legacy_resolve_mode: t.ClassVar[bool] = False
def __init_subclass__(cls) -> None:
if "resolve_or_missing" in cls.__dict__:
# If the subclass overrides resolve_or_missing it opts in to
# modern mode no matter what.
cls._legacy_resolve_mode = False
elif "resolve" in cls.__dict__ or cls._legacy_resolve_mode:
# If the subclass overrides resolve, or if its base is
# already in legacy mode, warn about legacy behavior.
import warnings
warnings.warn(
"Overriding 'resolve' is deprecated and will not have"
" the expected behavior in Jinja 3.1. Override"
" 'resolve_or_missing' instead ",
DeprecationWarning,
stacklevel=2,
)
cls._legacy_resolve_mode = True
def __init__( def __init__(
self, self,
environment: "Environment", environment: "Environment",
@ -251,15 +217,6 @@ class Context:
:param key: The variable name to look up. :param key: The variable name to look up.
""" """
if self._legacy_resolve_mode:
if key in self.vars:
return self.vars[key]
if key in self.parent:
return self.parent[key]
return self.environment.undefined(name=key)
rv = self.resolve_or_missing(key) rv = self.resolve_or_missing(key)
if rv is missing: if rv is missing:
@ -277,14 +234,6 @@ class Context:
:param key: The variable name to look up. :param key: The variable name to look up.
""" """
if self._legacy_resolve_mode:
rv = self.resolve(key)
if isinstance(rv, Undefined):
return missing
return rv
if key in self.vars: if key in self.vars:
return self.vars[key] return self.vars[key]

View File

@ -409,7 +409,7 @@ class ImmutableSandboxedEnvironment(SandboxedEnvironment):
class SandboxedFormatter(Formatter): class SandboxedFormatter(Formatter):
def __init__(self, env: Environment, **kwargs: t.Any) -> None: def __init__(self, env: Environment, **kwargs: t.Any) -> None:
self._env = env self._env = env
super().__init__(**kwargs) # type: ignore super().__init__(**kwargs)
def get_field( def get_field(
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any] self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]

View File

@ -3,7 +3,6 @@ import json
import os import os
import re import re
import typing as t import typing as t
import warnings
from collections import abc from collections import abc
from collections import deque from collections import deque
from random import choice from random import choice
@ -84,74 +83,9 @@ class _PassArg(enum.Enum):
if hasattr(obj, "jinja_pass_arg"): if hasattr(obj, "jinja_pass_arg"):
return obj.jinja_pass_arg # type: ignore return obj.jinja_pass_arg # type: ignore
for prefix in "context", "eval_context", "environment":
squashed = prefix.replace("_", "")
for name in f"{squashed}function", f"{squashed}filter":
if getattr(obj, name, False) is True:
warnings.warn(
f"{name!r} is deprecated and will stop working"
f" in Jinja 3.1. Use 'pass_{prefix}' instead.",
DeprecationWarning,
stacklevel=2,
)
return cls[prefix]
return None return None
def contextfunction(f: F) -> F:
"""Pass the context as the first argument to the decorated function.
.. deprecated:: 3.0
Will be removed in Jinja 3.1. Use :func:`~jinja2.pass_context`
instead.
"""
warnings.warn(
"'contextfunction' is renamed to 'pass_context', the old name"
" will be removed in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return pass_context(f)
def evalcontextfunction(f: F) -> F:
"""Pass the eval context as the first argument to the decorated
function.
.. deprecated:: 3.0
Will be removed in Jinja 3.1. Use
:func:`~jinja2.pass_eval_context` instead.
.. versionadded:: 2.4
"""
warnings.warn(
"'evalcontextfunction' is renamed to 'pass_eval_context', the"
" old name will be removed in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return pass_eval_context(f)
def environmentfunction(f: F) -> F:
"""Pass the environment as the first argument to the decorated
function.
.. deprecated:: 3.0
Will be removed in Jinja 3.1. Use
:func:`~jinja2.pass_environment` instead.
"""
warnings.warn(
"'environmentfunction' is renamed to 'pass_environment', the"
" old name will be removed in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return pass_environment(f)
def internalcode(f: F) -> F: def internalcode(f: F) -> F:
"""Marks the function as internally used""" """Marks the function as internally used"""
internal_code.add(f.__code__) internal_code.add(f.__code__)
@ -483,18 +417,6 @@ def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
return rv return rv
def unicode_urlencode(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
import warnings
warnings.warn(
"'unicode_urlencode' has been renamed to 'url_quote'. The old"
" name will be removed in Jinja 3.1.",
DeprecationWarning,
stacklevel=2,
)
return url_quote(obj, charset=charset, for_qs=for_qs)
@abc.MutableMapping.register @abc.MutableMapping.register
class LRUCache: class LRUCache:
"""A simple LRU Cache implementation.""" """A simple LRU Cache implementation."""
@ -831,24 +753,3 @@ class Namespace:
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<Namespace {self.__attrs!r}>" return f"<Namespace {self.__attrs!r}>"
class Markup(markupsafe.Markup):
def __new__(cls, base="", encoding=None, errors="strict"): # type: ignore
warnings.warn(
"'jinja2.Markup' is deprecated and will be removed in Jinja"
" 3.1. Import 'markupsafe.Markup' instead.",
DeprecationWarning,
stacklevel=2,
)
return super().__new__(cls, base, encoding, errors)
def escape(s: t.Any) -> str:
warnings.warn(
"'jinja2.escape' is deprecated and will be removed in Jinja"
" 3.1. Import 'markupsafe.escape' instead.",
DeprecationWarning,
stacklevel=2,
)
return markupsafe.escape(s)

View File

@ -30,7 +30,7 @@ class NodeVisitor:
exists for this node. In that case the generic visit function is exists for this node. In that case the generic visit function is
used instead. used instead.
""" """
return getattr(self, f"visit_{type(node).__name__}", None) # type: ignore return getattr(self, f"visit_{type(node).__name__}", None)
def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
"""Visit a node.""" """Visit a node."""
@ -43,8 +43,8 @@ class NodeVisitor:
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
"""Called if no explicit visitor function exists for a node.""" """Called if no explicit visitor function exists for a node."""
for node in node.iter_child_nodes(): for child_node in node.iter_child_nodes():
self.visit(node, *args, **kwargs) self.visit(child_node, *args, **kwargs)
class NodeTransformer(NodeVisitor): class NodeTransformer(NodeVisitor):

View File

@ -1,5 +1,4 @@
import asyncio import asyncio
import sys
import pytest import pytest
@ -14,19 +13,6 @@ from jinja2.exceptions import UndefinedError
from jinja2.nativetypes import NativeEnvironment from jinja2.nativetypes import NativeEnvironment
if sys.version_info < (3, 7):
def run(coro):
loop = asyncio.get_event_loop()
return loop.run_until_complete(coro)
else:
def run(coro):
return asyncio.run(coro)
def test_basic_async(): def test_basic_async():
t = Template( t = Template(
"{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True "{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True
@ -35,7 +21,7 @@ def test_basic_async():
async def func(): async def func():
return await t.render_async() return await t.render_async()
rv = run(func()) rv = asyncio.run(func())
assert rv == "[1][2][3]" assert rv == "[1][2][3]"
@ -51,7 +37,7 @@ def test_await_on_calls():
async def func(): async def func():
return await t.render_async(async_func=async_func, normal_func=normal_func) return await t.render_async(async_func=async_func, normal_func=normal_func)
rv = run(func()) rv = asyncio.run(func())
assert rv == "65" assert rv == "65"
@ -65,7 +51,6 @@ def test_await_on_calls_normal_render():
return 23 return 23
rv = t.render(async_func=async_func, normal_func=normal_func) rv = t.render(async_func=async_func, normal_func=normal_func)
assert rv == "65" assert rv == "65"
@ -81,7 +66,7 @@ def test_await_and_macros():
async def func(): async def func():
return await t.render_async(async_func=async_func) return await t.render_async(async_func=async_func)
rv = run(func()) rv = asyncio.run(func())
assert rv == "[42][42]" assert rv == "[42][42]"
@ -95,7 +80,7 @@ def test_async_blocks():
async def func(): async def func():
return await t.render_async() return await t.render_async()
rv = run(func()) rv = asyncio.run(func())
assert rv == "<Test><Test>" assert rv == "<Test><Test>"
@ -172,8 +157,7 @@ class TestAsyncImports:
test_env_async.from_string('{% from "foo" import bar, with with context %}') test_env_async.from_string('{% from "foo" import bar, with with context %}')
def test_exports(self, test_env_async): def test_exports(self, test_env_async):
m = run( coro = test_env_async.from_string(
test_env_async.from_string(
""" """
{% macro toplevel() %}...{% endmacro %} {% macro toplevel() %}...{% endmacro %}
{% macro __private() %}...{% endmacro %} {% macro __private() %}...{% endmacro %}
@ -183,8 +167,8 @@ class TestAsyncImports:
{% endfor %} {% endfor %}
""" """
)._get_default_module_async() )._get_default_module_async()
) m = asyncio.run(coro)
assert run(m.toplevel()) == "..." assert asyncio.run(m.toplevel()) == "..."
assert not hasattr(m, "__missing") assert not hasattr(m, "__missing")
assert m.variable == 42 assert m.variable == 42
assert not hasattr(m, "notthere") assert not hasattr(m, "notthere")
@ -621,7 +605,7 @@ def test_namespace_awaitable(test_env_async):
actual = await t.render_async() actual = await t.render_async()
assert actual == "Bar" assert actual == "Bar"
run(_test()) asyncio.run(_test())
def test_chainable_undefined_aiter(): def test_chainable_undefined_aiter():
@ -634,7 +618,7 @@ def test_chainable_undefined_aiter():
rv = await t.render_async(a={}) rv = await t.render_async(a={})
assert rv == "" assert rv == ""
run(_test()) asyncio.run(_test())
@pytest.fixture @pytest.fixture
@ -648,7 +632,7 @@ def test_native_async(async_native_env):
rv = await t.render_async(x=23) rv = await t.render_async(x=23)
assert rv == 23 assert rv == 23
run(_test()) asyncio.run(_test())
def test_native_list_async(async_native_env): def test_native_list_async(async_native_env):
@ -657,4 +641,20 @@ def test_native_list_async(async_native_env):
rv = await t.render_async(x=list(range(3))) rv = await t.render_async(x=list(range(3)))
assert rv == [0, 1, 2] assert rv == [0, 1, 2]
run(_test()) asyncio.run(_test())
def test_getitem_after_filter():
env = Environment(enable_async=True)
env.filters["add_each"] = lambda v, x: [i + x for i in v]
t = env.from_string("{{ (a|add_each(2))[1:] }}")
out = t.render(a=range(3))
assert out == "[3, 4]"
def test_getitem_after_call():
env = Environment(enable_async=True)
env.globals["add_each"] = lambda v, x: [i + x for i in v]
t = env.from_string("{{ add_each(a, 2)[1:] }}")
out = t.render(a=range(3))
assert out == "[3, 4]"

View File

@ -57,6 +57,26 @@ def test_groupby(env_async, items):
] ]
@pytest.mark.parametrize(
("case_sensitive", "expect"),
[
(False, "a: 1, 3\nb: 2\n"),
(True, "A: 3\na: 1\nb: 2\n"),
],
)
def test_groupby_case(env_async, case_sensitive, expect):
tmpl = env_async.from_string(
"{% for k, vs in data|groupby('k', case_sensitive=cs) %}"
"{{ k }}: {{ vs|join(', ', attribute='v') }}\n"
"{% endfor %}"
)
out = tmpl.render(
data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}],
cs=case_sensitive,
)
assert out == expect
@mark_dualiter("items", lambda: [("a", 1), ("a", 2), ("b", 1)]) @mark_dualiter("items", lambda: [("a", 1), ("a", 2), ("b", 1)])
def test_groupby_tuple_index(env_async, items): def test_groupby_tuple_index(env_async, items):
tmpl = env_async.from_string( tmpl = env_async.from_string(

View File

@ -25,7 +25,7 @@ class TestDebug:
m = re.search(expected_tb.strip(), "".join(tb)) m = re.search(expected_tb.strip(), "".join(tb))
assert ( assert (
m is not None m is not None
), "Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}" ), f"Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}"
def test_runtime_error(self, fs_env): def test_runtime_error(self, fs_env):
def test(): def test():

View File

@ -43,6 +43,9 @@ newstyle_i18n_templates = {
"pgettext.html": '{{ pgettext("fruit", "Apple") }}', "pgettext.html": '{{ pgettext("fruit", "Apple") }}',
"npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s apples",' "npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s apples",'
" apples) }}", " apples) }}",
"pgettext_block": "{% trans 'fruit' num=apples %}Apple{% endtrans %}",
"npgettext_block": "{% trans 'fruit' num=apples %}{{ num }} apple"
"{% pluralize %}{{ num }} apples{% endtrans %}",
"transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}", "transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}",
"transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}", "transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}",
"transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}", "transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}",
@ -593,11 +596,20 @@ class TestNewstyleInternationalization:
tmpl = newstyle_i18n_env.get_template("pgettext.html") tmpl = newstyle_i18n_env.get_template("pgettext.html")
assert tmpl.render(LANGUAGE="de") == "Apple" assert tmpl.render(LANGUAGE="de") == "Apple"
def test_context_newstyle_plural(self): def test_context_plural(self):
tmpl = newstyle_i18n_env.get_template("npgettext.html") tmpl = newstyle_i18n_env.get_template("npgettext.html")
assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple" assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple"
assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples" assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples"
def test_context_block(self):
tmpl = newstyle_i18n_env.get_template("pgettext_block")
assert tmpl.render(LANGUAGE="de") == "Apple"
def test_context_plural_block(self):
tmpl = newstyle_i18n_env.get_template("npgettext_block")
assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple"
assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples"
class TestAutoEscape: class TestAutoEscape:
def test_scoped_setting(self): def test_scoped_setting(self):

View File

@ -1,14 +0,0 @@
import pytest
from jinja2 import Template
# Python < 3.7
def test_generator_stop():
class X:
def __getattr__(self, name):
raise StopIteration()
t = Template("a{{ bad.bar() }}b")
with pytest.raises(RuntimeError):
t.render(bad=X())

View File

@ -251,6 +251,17 @@ class TestFilter:
out = tmpl.render() out = tmpl.render()
assert out == "foo" assert out == "foo"
def test_items(self, env):
d = {i: c for i, c in enumerate("abc")}
tmpl = env.from_string("""{{ d|items|list }}""")
out = tmpl.render(d=d)
assert out == "[(0, 'a'), (1, 'b'), (2, 'c')]"
def test_items_undefined(self, env):
tmpl = env.from_string("""{{ d|items|list }}""")
out = tmpl.render()
assert out == "[]"
def test_pprint(self, env): def test_pprint(self, env):
from pprint import pformat from pprint import pformat
@ -608,6 +619,25 @@ class TestFilter:
) )
assert out == "NY: emma, john\nWA: smith\n" assert out == "NY: emma, john\nWA: smith\n"
@pytest.mark.parametrize(
("case_sensitive", "expect"),
[
(False, "a: 1, 3\nb: 2\n"),
(True, "A: 3\na: 1\nb: 2\n"),
],
)
def test_groupby_case(self, env, case_sensitive, expect):
tmpl = env.from_string(
"{% for k, vs in data|groupby('k', case_sensitive=cs) %}"
"{{ k }}: {{ vs|join(', ', attribute='v') }}\n"
"{% endfor %}"
)
out = tmpl.render(
data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}],
cs=case_sensitive,
)
assert out == expect
def test_filtertag(self, env): def test_filtertag(self, env):
tmpl = env.from_string( tmpl = env.from_string(
"{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}" "{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}"

View File

@ -171,6 +171,15 @@ class TestFileSystemLoader:
t = e.get_template("mojibake.txt") t = e.get_template("mojibake.txt")
assert t.render() == expect assert t.render() == expect
def test_filename_normpath(self):
"""Nested template names should only contain ``os.sep`` in the
loaded filename.
"""
loader = loaders.FileSystemLoader(self.searchpath)
e = Environment(loader=loader)
t = e.get_template("foo/test.html")
assert t.filename == str(self.searchpath / "foo" / "test.html")
class TestModuleLoader: class TestModuleLoader:
archive = None archive = None

View File

@ -153,3 +153,10 @@ def test_leading_spaces(env):
t = env.from_string(" {{ True }}") t = env.from_string(" {{ True }}")
result = t.render() result = t.render()
assert result == " True" assert result == " True"
def test_macro(env):
t = env.from_string("{%- macro x() -%}{{- [1,2] -}}{%- endmacro -%}{{- x()[1] -}}")
result = t.render()
assert result == 2
assert isinstance(result, int)

6
tests/test_pickle.py Normal file
View File

@ -0,0 +1,6 @@
import pickle
def test_environment(env):
env = pickle.loads(pickle.dumps(env))
assert env.from_string("x={{ x }}").render(x=42) == "x=42"

View File

@ -591,23 +591,6 @@ class TestBug:
env = MyEnvironment(loader=loader) env = MyEnvironment(loader=loader)
assert env.get_template("test").render(foobar="test") == "test" assert env.get_template("test").render(foobar="test") == "test"
def test_legacy_custom_context(self, env):
from jinja2.runtime import Context, missing
with pytest.deprecated_call():
class MyContext(Context):
def resolve(self, name):
if name == "foo":
return 42
return super().resolve(name)
x = MyContext(env, parent={"bar": 23}, name="foo", blocks={})
assert x._legacy_resolve_mode
assert x.resolve_or_missing("foo") == 42
assert x.resolve_or_missing("bar") == 23
assert x.resolve_or_missing("baz") is missing
def test_recursive_loop_bug(self, env): def test_recursive_loop_bug(self, env):
tmpl = env.from_string( tmpl = env.from_string(
"{%- for value in values recursive %}1{% else %}0{% endfor -%}" "{%- for value in values recursive %}1{% else %}0{% endfor -%}"

View File

@ -1,6 +1,6 @@
[tox] [tox]
envlist = envlist =
py{311,310,39,38,37,36,py37} py3{11,10,9,8,7},pypy3{8,7}
style style
typing typing
docs docs