mirror of https://gitee.com/openkylin/jinja2.git
Import Upstream version 3.1.2
This commit is contained in:
parent
648aeedbb8
commit
65adae8035
60
CHANGES.rst
60
CHANGES.rst
|
@ -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
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
4
PKG-INFO
4
PKG-INFO
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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 it’s
|
|
||||||
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. It’s 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.
|
|
|
@ -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.
|
|
|
@ -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
|
|
|
@ -1,4 +0,0 @@
|
||||||
addon: jinja
|
|
||||||
description: "allow syntax highlighting for Jinja templates"
|
|
||||||
files:
|
|
||||||
- syntax/jinja.vim
|
|
|
@ -1 +0,0 @@
|
||||||
# You must remove unused comment lines for the released package.
|
|
|
@ -1 +0,0 @@
|
||||||
docs/_build/html
|
|
|
@ -1 +0,0 @@
|
||||||
examples/*
|
|
|
@ -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
|
|
|
@ -1,2 +0,0 @@
|
||||||
jinja2/asyncfilters.py
|
|
||||||
jinja2/asyncsupport.py
|
|
|
@ -1 +0,0 @@
|
||||||
re|-3.6|/usr/lib/python3/dist-packages/jinja2|.*/async(support|filters).py
|
|
|
@ -1,2 +0,0 @@
|
||||||
/usr/share/vim/addons/syntax/
|
|
||||||
/usr/share/vim/registry
|
|
|
@ -1,2 +0,0 @@
|
||||||
debian/jinja.yaml /usr/share/vim/registry
|
|
||||||
debian/jinja.vim /usr/share/vim/addons/syntax/
|
|
|
@ -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
|
|
|
@ -1 +0,0 @@
|
||||||
3.0 (native)
|
|
|
@ -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)))
|
|
33
docs/api.rst
33
docs/api.rst
|
@ -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
|
||||||
|
|
200
docs/faq.rst
200
docs/faq.rst
|
@ -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/
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
123
docs/sandbox.rst
123
docs/sandbox.rst
|
@ -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.
|
|
||||||
|
|
|
@ -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 %>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
...
|
...
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
[babel.extractors]
|
[babel.extractors]
|
||||||
jinja2 = jinja2.ext:babel_extract [i18n]
|
jinja2 = jinja2.ext:babel_extract[i18n]
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 + "=")
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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]"
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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())
|
|
|
@ -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 %}"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
|
@ -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 -%}"
|
||||||
|
|
Loading…
Reference in New Issue