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
|
||||
|
||||
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
|
||||
-------------
|
||||
|
||||
|
|
4
PKG-INFO
4
PKG-INFO
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: Jinja2
|
||||
Version: 3.0.3
|
||||
Version: 3.1.2
|
||||
Summary: A very fast and expressive template engine.
|
||||
Home-page: https://palletsprojects.com/p/jinja/
|
||||
Author: Armin Ronacher
|
||||
|
@ -24,7 +24,7 @@ Classifier: Operating System :: OS Independent
|
|||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||
Requires-Python: >=3.6
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
Provides-Extra: i18n
|
||||
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
|
||||
function into the filter dict and call it `to.str`. The regular
|
||||
expression for filter and test identifiers is
|
||||
``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*```.
|
||||
``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*``.
|
||||
|
||||
|
||||
Undefined Types
|
||||
|
@ -410,16 +410,19 @@ The Context
|
|||
.. 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
|
||||
immutable inside functions. Both Jinja and Python are not using the
|
||||
context / frame locals as data storage for variables but only as primary
|
||||
data source.
|
||||
Instead of modifying the context directly, a function should return
|
||||
a value that can be assigned to a variable within the template itself.
|
||||
|
||||
When a template accesses a variable the template does not define, Jinja
|
||||
looks up the variable in the context, after that the variable is treated
|
||||
as if it was defined in the template.
|
||||
.. code-block:: jinja
|
||||
|
||||
{% set comments = get_latest_comments() %}
|
||||
|
||||
|
||||
.. _loaders:
|
||||
|
@ -597,18 +600,6 @@ functions to a Jinja 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.is_undefined
|
||||
|
|
200
docs/faq.rst
200
docs/faq.rst
|
@ -1,175 +1,75 @@
|
|||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
This page answers some of the often asked questions about Jinja.
|
||||
|
||||
.. highlight:: html+jinja
|
||||
|
||||
Why is it called Jinja?
|
||||
-----------------------
|
||||
|
||||
The name Jinja was chosen because it's the name of a Japanese temple and
|
||||
temple and template share a similar pronunciation. It is not named after
|
||||
the city in Uganda.
|
||||
"Jinja" is a Japanese `Shinto shrine`_, or temple, and temple and
|
||||
template share a similar English pronunciation. It is not named after
|
||||
the `city in Uganda`_.
|
||||
|
||||
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
|
||||
this similarity doesn't mean that you can use a Django template unmodified
|
||||
in Jinja. For example filter arguments use a function call syntax rather
|
||||
than a colon to separate filter name and arguments. Additionally the
|
||||
extension interface in Jinja is fundamentally different from the Django one
|
||||
which means that your custom tags won't work any longer.
|
||||
Jinja also makes extensive use of caching. Templates are cached by name
|
||||
after loading, so future uses of the template avoid loading. The
|
||||
template loading itself uses a bytecode cache to avoid repeated
|
||||
compiling. The caches can be external to persist across restarts.
|
||||
Templates can also be precompiled and loaded as fast Python imports.
|
||||
|
||||
Generally speaking you will use much less custom extensions as the Jinja
|
||||
template system allows you to use a certain subset of Python expressions
|
||||
which can replace most Django extensions. For example instead of using
|
||||
something like this::
|
||||
We dislike benchmarks because they don't reflect real use. Performance
|
||||
depends on many factors. Different engines have different default
|
||||
configurations and tradeoffs that make it unclear how to set up a useful
|
||||
comparison. Often, database access, API calls, and data processing have
|
||||
a much larger effect on performance than the template engine.
|
||||
|
||||
{% 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
|
||||
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?
|
||||
-----------------------------------------------------
|
||||
Isn't it a bad idea to put logic in templates?
|
||||
----------------------------------------------
|
||||
|
||||
Without a doubt you should try to remove as much logic from templates as
|
||||
possible. But templates without any logic mean that you have to do all
|
||||
the processing in the code which is boring and stupid. A template engine
|
||||
that does that is shipped with Python and called `string.Template`. Comes
|
||||
without loops and if conditions and is by far the fastest template engine
|
||||
you can get for Python.
|
||||
possible. With less logic, the template is easier to understand, has
|
||||
fewer potential side effects, and is faster to compile and render. But a
|
||||
template without any logic means processing must be done in code before
|
||||
rendering. A template engine that does that is shipped with Python,
|
||||
called :class:`string.Template`, and while it's definitely fast it's not
|
||||
convenient.
|
||||
|
||||
So some amount of logic is required in templates to keep everyone happy.
|
||||
And Jinja leaves it pretty much to you how much logic you want to put into
|
||||
templates. There are some restrictions in what you can do and what not.
|
||||
Jinja's features such as blocks, statements, filters, and function calls
|
||||
make it much easier to write expressive templates, with very few
|
||||
restrictions. Jinja doesn't allow arbitrary Python code in templates, or
|
||||
every feature available in the Python language. This keeps the engine
|
||||
easier to maintain, and keeps templates more readable.
|
||||
|
||||
Jinja neither allows you to put arbitrary Python code into templates nor
|
||||
does it allow all Python expressions. The operators are limited to the
|
||||
most common ones and more advanced expressions such as list comprehensions
|
||||
and generator expressions are not supported. This keeps the template engine
|
||||
easier to maintain and templates more readable.
|
||||
Some amount of logic is required in templates to keep everyone happy.
|
||||
Too much logic in the template can make it complex to reason about and
|
||||
maintain. It's up to you to decide how your application will work and
|
||||
balance how much logic you want to put in the template.
|
||||
|
||||
Why is Autoescaping not the Default?
|
||||
------------------------------------
|
||||
|
||||
There are multiple reasons why automatic escaping is not the default mode
|
||||
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
|
||||
Why is HTML escaping not the default?
|
||||
-------------------------------------
|
||||
|
||||
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
|
||||
|
||||
{% macro foo() %}LAYOUT{% endmacro %}
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
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/
|
||||
While automatic escaping means that you are less likely have an XSS
|
||||
problem, it also requires significant extra processing during compiling
|
||||
and rendering, which can reduce performance. Jinja uses MarkupSafe for
|
||||
escaping, which provides optimized C code for speed, but it still
|
||||
introduces overhead to track escaping across methods and formatting.
|
||||
|
|
|
@ -1,6 +1,25 @@
|
|||
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
|
||||
|
|
|
@ -30,7 +30,7 @@ Installation
|
|||
------------
|
||||
|
||||
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.
|
||||
|
||||
.. _virtual environment: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments
|
||||
|
|
125
docs/sandbox.rst
125
docs/sandbox.rst
|
@ -1,18 +1,56 @@
|
|||
Sandbox
|
||||
=======
|
||||
|
||||
The Jinja sandbox can be used to evaluate untrusted code. Access to unsafe
|
||||
attributes and methods is prohibited.
|
||||
The Jinja sandbox can be used to render untrusted templates. Access to
|
||||
attributes, method calls, operators, mutating data structures, and
|
||||
string formatting can be intercepted and prohibited.
|
||||
|
||||
Assuming `env` is a :class:`SandboxedEnvironment` in the default configuration
|
||||
the following piece of code shows how it works:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from jinja2.sandbox import SandboxedEnvironment
|
||||
>>> env = SandboxedEnvironment()
|
||||
>>> func = lambda: "Hello, Sandbox!"
|
||||
>>> env.from_string("{{ func() }}").render(func=func)
|
||||
'Hello, Sandbox!'
|
||||
>>> env.from_string("{{ func.__code__.co_code }}").render(func=func)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SecurityError: access to attribute '__code__' of 'function' object is unsafe.
|
||||
|
||||
A sandboxed environment can be useful, for example, to allow users of an
|
||||
internal reporting system to create custom emails. You would document
|
||||
what data is available in the templates, then the user would write a
|
||||
template using that information. Your code would generate the report
|
||||
data and pass it to the user's sandboxed template to render.
|
||||
|
||||
|
||||
Security Considerations
|
||||
-----------------------
|
||||
|
||||
The sandbox alone is not a solution for perfect security. Keep these
|
||||
things in mind when using the sandbox.
|
||||
|
||||
Templates can still raise errors when compiled or rendered. Your code
|
||||
should attempt to catch errors instead of crashing.
|
||||
|
||||
It is possible to construct a relatively small template that renders to
|
||||
a very large amount of output, which could correspond to a high use of
|
||||
CPU or memory. You should run your application with limits on resources
|
||||
such as CPU and memory to mitigate this.
|
||||
|
||||
Jinja only renders text, it does not understand, for example, JavaScript
|
||||
code. Depending on how the rendered template will be used, you may need
|
||||
to do other postprocessing to restrict the output.
|
||||
|
||||
Pass only the data that is relevant to the template. Avoid passing
|
||||
global data, or objects with methods that have side effects. By default
|
||||
the sandbox prevents private and internal attribute access. You can
|
||||
override :meth:`~SandboxedEnvironment.is_safe_attribute` to further
|
||||
restrict attributes access. Decorate methods with :func:`unsafe` to
|
||||
prevent calling them from templates when passing objects as data. Use
|
||||
:class:`ImmutableSandboxedEnvironment` to prevent modifying lists and
|
||||
dictionaries.
|
||||
|
||||
>>> env.from_string("{{ func.func_code }}").render(func=lambda:None)
|
||||
u''
|
||||
>>> env.from_string("{{ func.func_code.do_something }}").render(func=lambda:None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SecurityError: access to attribute 'func_code' of 'function' object is unsafe.
|
||||
|
||||
API
|
||||
---
|
||||
|
@ -34,61 +72,40 @@ API
|
|||
|
||||
.. 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
|
||||
---------------------
|
||||
|
||||
.. 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
|
||||
specific callback methods. This means that it's not possible to have this
|
||||
intercepted by overriding :meth:`Environment.call`. Furthermore a
|
||||
conversion from operator to special method is not always directly possible
|
||||
due to how operators work. For instance for divisions more than one
|
||||
special method exist.
|
||||
The sandbox can instruct the compiler to output a function to intercept
|
||||
certain operators instead. Override
|
||||
:attr:`SandboxedEnvironment.intercepted_binops` and
|
||||
:attr:`SandboxedEnvironment.intercepted_unops` with the operator symbols
|
||||
you want to intercept. The compiler will replace the symbols with calls
|
||||
to :meth:`SandboxedEnvironment.call_binop` and
|
||||
:meth:`SandboxedEnvironment.call_unop` instead. The default
|
||||
implementation of those methods will use
|
||||
:attr:`SandboxedEnvironment.binop_table` and
|
||||
:attr:`SandboxedEnvironment.unop_table` to translate operator symbols
|
||||
into :mod:`operator` functions.
|
||||
|
||||
With Jinja 2.6 there is now support for explicit operator intercepting.
|
||||
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.
|
||||
For example, the power (``**``) operator can be disabled:
|
||||
|
||||
The default implementation of :attr:`SandboxedEnvironment.call_binop`
|
||||
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::
|
||||
.. code-block:: python
|
||||
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
|
||||
class MyEnvironment(SandboxedEnvironment):
|
||||
intercepted_binops = frozenset(['**'])
|
||||
intercepted_binops = frozenset(["**"])
|
||||
|
||||
def call_binop(self, context, operator, left, right):
|
||||
if operator == '**':
|
||||
return self.undefined('the power operator is unavailable')
|
||||
return SandboxedEnvironment.call_binop(self, context,
|
||||
operator, left, right)
|
||||
if operator == "**":
|
||||
return self.undefined("The power (**) operator is unavailable.")
|
||||
|
||||
Make sure to always call into the super method, even if you are not
|
||||
intercepting the call. Jinja might internally call the method to
|
||||
evaluate expressions.
|
||||
return super().call_binop(self, context, operator, left, right)
|
||||
|
|
|
@ -1,141 +1,73 @@
|
|||
Switching from other Template Engines
|
||||
Switching From Other Template Engines
|
||||
=====================================
|
||||
|
||||
.. highlight:: html+jinja
|
||||
|
||||
If you have used a different template engine in the past and want to switch
|
||||
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.
|
||||
This is a brief guide on some of the differences between Jinja syntax
|
||||
and other template languages. See :doc:`/templates` for a comprehensive
|
||||
guide to Jinja syntax and features.
|
||||
|
||||
|
||||
Django
|
||||
------
|
||||
|
||||
If you have previously worked with Django templates, you should find
|
||||
Jinja very familiar. In fact, most of the syntax elements look and
|
||||
work the same.
|
||||
Jinja very familiar. Many of the syntax elements look and work the same.
|
||||
However, Jinja provides some more syntax elements, and some work a bit
|
||||
differently.
|
||||
|
||||
However, Jinja provides some more syntax elements covered in the
|
||||
documentation and some work a bit different.
|
||||
This section covers the template changes. The API, including extension
|
||||
support, is fundamentally different so it won't be covered here.
|
||||
|
||||
Django supports using Jinja as its template engine, see
|
||||
https://docs.djangoproject.com/en/stable/topics/templates/#support-for-template-engines.
|
||||
|
||||
This section covers the template changes. As the API is fundamentally
|
||||
different we won't cover it here.
|
||||
|
||||
Method Calls
|
||||
~~~~~~~~~~~~
|
||||
|
||||
In Django method calls work implicitly, while Jinja requires the explicit
|
||||
Python syntax. Thus this Django code::
|
||||
In Django, methods are called implicitly, without parentheses.
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% for page in user.get_created_pages %}
|
||||
...
|
||||
{% 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() %}
|
||||
...
|
||||
{% 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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Jinja provides more than one argument for filters. Also the syntax for
|
||||
argument passing is different. A template that looks like this in Django::
|
||||
In Django, one literal value can be passed to a filter after a colon.
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{{ 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
|
||||
~~~~~
|
||||
|
||||
In addition to filters there also are tests you can perform using the is
|
||||
operator. Here are some examples::
|
||||
In addition to filters, Jinja also has "tests" used with the ``is``
|
||||
operator. This operator is not the same as the Python operator.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% if user.user_id is odd %}
|
||||
{{ user.username|e }} is odd
|
||||
|
@ -146,11 +78,10 @@ operator. Here are some examples::
|
|||
Loops
|
||||
~~~~~
|
||||
|
||||
For loops work very similarly to Django, but notably the Jinja special
|
||||
variable for the loop context is called `loop`, not `forloop` as in Django.
|
||||
In Django, the special variable for the loop context is called
|
||||
``forloop``, and the ``empty`` is used for no loop items.
|
||||
|
||||
In addition, the Django `empty` argument is called `else` in Jinja. For
|
||||
example, the Django template::
|
||||
.. code-block:: django
|
||||
|
||||
{% for item in items %}
|
||||
{{ item }}
|
||||
|
@ -158,52 +89,74 @@ example, the Django template::
|
|||
No items!
|
||||
{% 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 %}
|
||||
{{ item }}
|
||||
{{ loop.index}}. {{ item }}
|
||||
{% else %}
|
||||
No items!
|
||||
{% endfor %}
|
||||
|
||||
|
||||
Cycle
|
||||
~~~~~
|
||||
|
||||
The ``{% cycle %}`` tag does not exist in Jinja; however, you can achieve the
|
||||
same output by using the `cycle` method on the loop context special variable.
|
||||
In Django, the ``{% cycle %}`` can be used in a for loop to alternate
|
||||
between values per loop.
|
||||
|
||||
The following Django template::
|
||||
.. code-block:: django
|
||||
|
||||
{% for user in users %}
|
||||
<li class="{% cycle 'odd' 'even' %}">{{ user }}</li>
|
||||
{% endfor %}
|
||||
|
||||
...looks like this in Jinja::
|
||||
In Jinja, the ``loop`` context has a ``cycle`` method.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% for user in users %}
|
||||
<li class="{{ loop.cycle('odd', 'even') }}">{{ user }}</li>
|
||||
{% 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
|
||||
----
|
||||
|
||||
.. 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
|
||||
Jinja to look more like Mako:
|
||||
.. code-block:: python
|
||||
|
||||
.. 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
|
||||
a small subset of Mako templates. Jinja does not support embedded Python
|
||||
code, so you would have to move that out of the template. The syntax for defs
|
||||
(which are called macros in Jinja) and template inheritance is different too.
|
||||
The following Mako template::
|
||||
Jinja does not support embedded Python code, so you would have to move
|
||||
that out of the template. You could either process the data with the
|
||||
same code before rendering, or add a global function or filter to the
|
||||
Jinja environment.
|
||||
|
||||
The syntax for defs (which are called macros in Jinja) and template
|
||||
inheritance is different too.
|
||||
|
||||
The following Mako template:
|
||||
|
||||
.. code-block:: mako
|
||||
|
||||
<%inherit file="layout.html" />
|
||||
<%def name="title()">Page Title</%def>
|
||||
|
@ -213,7 +166,9 @@ The following Mako template::
|
|||
% endfor
|
||||
</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" %>
|
||||
<% 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
|
||||
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:
|
||||
|
||||
|
@ -1113,42 +1130,45 @@ at the same time. They are documented in detail in the
|
|||
Include
|
||||
~~~~~~~
|
||||
|
||||
The `include` tag is useful to include a template and return the
|
||||
rendered contents of that file into the current namespace::
|
||||
The ``include`` tag renders another template and outputs the result into
|
||||
the current template.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% include 'header.html' %}
|
||||
Body
|
||||
Body goes here.
|
||||
{% include 'footer.html' %}
|
||||
|
||||
Included templates have access to the variables of the active context by
|
||||
default. For more details about context behavior of imports and includes,
|
||||
see :ref:`import-visibility`.
|
||||
The included template has access to context of the current template by
|
||||
default. Use ``without context`` to use a separate context instead.
|
||||
``with context`` is also valid, but is the default behavior. See
|
||||
:ref:`import-visibility`.
|
||||
|
||||
From Jinja 2.2 onwards, you can mark an include with ``ignore missing``; in
|
||||
which case Jinja will ignore the statement if the template to be included
|
||||
does not exist. When combined with ``with`` or ``without context``, it must
|
||||
be placed *before* the context visibility statement. Here are some valid
|
||||
examples::
|
||||
The included template can ``extend`` another template and override
|
||||
blocks in that template. However, the current template cannot override
|
||||
any blocks that the included template outputs.
|
||||
|
||||
Use ``ignore missing`` to ignore the statement if the template does not
|
||||
exist. It must be placed *before* a context visibility statement.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% include "sidebar.html" without context %}
|
||||
{% include "sidebar.html" ignore missing %}
|
||||
{% include "sidebar.html" ignore missing with context %}
|
||||
{% include "sidebar.html" ignore missing without context %}
|
||||
|
||||
.. 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
|
||||
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::
|
||||
.. code-block:: jinja
|
||||
|
||||
{% include ['page_detailed.html', 'page.html'] %}
|
||||
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
If a template object was passed to the template context, you can
|
||||
include that object using `include`.
|
||||
A variable, with either a template name or template object, can also be
|
||||
passed to the statment.
|
||||
|
||||
.. _import:
|
||||
|
||||
|
@ -1732,11 +1752,35 @@ to disable it for a block.
|
|||
.. versionadded:: 2.10
|
||||
The ``trimmed`` and ``notrimmed`` modifiers have been added.
|
||||
|
||||
If the translation depends on the context that the message appears in,
|
||||
the ``pgettext`` and ``npgettext`` functions take a ``context`` string
|
||||
as the first argument, which is used to select the appropriate
|
||||
translation. To specify a context with the ``{% trans %}`` tag, provide
|
||||
a string as the first token after ``trans``.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% trans "fruit" %}apple{% endtrans %}
|
||||
{% trans "fruit" trimmed count -%}
|
||||
1 apple
|
||||
{%- pluralize -%}
|
||||
{{ count }} apples
|
||||
{%- endtrans %}
|
||||
|
||||
.. versionadded:: 3.1
|
||||
A context can be passed to the ``trans`` tag to use ``pgettext`` and
|
||||
``npgettext``.
|
||||
|
||||
It's possible to translate strings in expressions with these functions:
|
||||
|
||||
- ``gettext``: translate a single string
|
||||
- ``ngettext``: translate a pluralizable string
|
||||
- ``_``: alias for ``gettext``
|
||||
- ``_(message)``: Alias for ``gettext``.
|
||||
- ``gettext(message)``: Translate a message.
|
||||
- ``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:
|
||||
|
||||
|
|
|
@ -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::
|
||||
|
||||
{% 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>
|
||||
<link rel="stylesheet" href="style.css" type="text/css">
|
||||
{% block body %}
|
||||
|
@ -74,8 +74,8 @@ sense to define a default for that variable::
|
|||
...
|
||||
<ul id="navigation">
|
||||
{% for href, id, caption in navigation_bar %}
|
||||
<li{% if id == active_page %} class="active"{% endif
|
||||
%}><a href="{{ href|e }}">{{ caption|e }}</a></li>
|
||||
<li{% if id == active_page %} class="active"{% endif %}>
|
||||
<a href="{{ href|e }}">{{ caption|e }}</a></li>
|
||||
{% endfor %}
|
||||
</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:
|
||||
#
|
||||
# pip-compile requirements/dev.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
alabaster==0.7.12
|
||||
# via sphinx
|
||||
attrs==21.2.0
|
||||
# via pytest
|
||||
babel==2.9.1
|
||||
# via sphinx
|
||||
backports.entry-points-selectable==1.1.0
|
||||
# via virtualenv
|
||||
certifi==2021.10.8
|
||||
# via requests
|
||||
-r docs.txt
|
||||
-r tests.txt
|
||||
-r typing.txt
|
||||
cfgv==3.3.1
|
||||
# via pre-commit
|
||||
charset-normalizer==2.0.7
|
||||
# via requests
|
||||
click==8.0.3
|
||||
# via pip-tools
|
||||
distlib==0.3.3
|
||||
click==8.1.2
|
||||
# via
|
||||
# pip-compile-multi
|
||||
# pip-tools
|
||||
distlib==0.3.4
|
||||
# via virtualenv
|
||||
docutils==0.17.1
|
||||
# via sphinx
|
||||
filelock==3.3.2
|
||||
filelock==3.6.0
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
identify==2.3.3
|
||||
identify==2.5.0
|
||||
# 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
|
||||
# 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
|
||||
# via pip-tools
|
||||
pip-tools==6.4.0
|
||||
pip-compile-multi==2.4.5
|
||||
# via -r requirements/dev.in
|
||||
platformdirs==2.4.0
|
||||
pip-tools==6.6.0
|
||||
# via pip-compile-multi
|
||||
platformdirs==2.5.2
|
||||
# via virtualenv
|
||||
pluggy==1.0.0
|
||||
# via
|
||||
# pytest
|
||||
# tox
|
||||
pre-commit==2.15.0
|
||||
pre-commit==2.18.1
|
||||
# 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
|
||||
# via pre-commit
|
||||
requests==2.26.0
|
||||
# via sphinx
|
||||
six==1.16.0
|
||||
# via
|
||||
# tox
|
||||
# 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
|
||||
# via
|
||||
# mypy
|
||||
# pre-commit
|
||||
# pytest
|
||||
# tox
|
||||
tomli==1.2.2
|
||||
# via pep517
|
||||
tox==3.24.4
|
||||
toposort==1.7
|
||||
# via pip-compile-multi
|
||||
tox==3.25.0
|
||||
# via -r requirements/dev.in
|
||||
typing-extensions==3.10.0.2
|
||||
# via mypy
|
||||
urllib3==1.26.7
|
||||
# via requests
|
||||
virtualenv==20.10.0
|
||||
virtualenv==20.14.1
|
||||
# via
|
||||
# pre-commit
|
||||
# tox
|
||||
wheel==0.37.0
|
||||
wheel==0.37.1
|
||||
# via pip-tools
|
||||
|
||||
# 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:
|
||||
#
|
||||
# pip-compile requirements/docs.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
alabaster==0.7.12
|
||||
# via sphinx
|
||||
babel==2.9.1
|
||||
babel==2.10.1
|
||||
# via sphinx
|
||||
certifi==2021.10.8
|
||||
# via requests
|
||||
charset-normalizer==2.0.7
|
||||
charset-normalizer==2.0.12
|
||||
# via requests
|
||||
docutils==0.17.1
|
||||
# via sphinx
|
||||
idna==3.3
|
||||
# via requests
|
||||
imagesize==1.2.0
|
||||
imagesize==1.3.0
|
||||
# via sphinx
|
||||
jinja2==3.0.2
|
||||
jinja2==3.1.1
|
||||
# via sphinx
|
||||
markupsafe==2.0.1
|
||||
markupsafe==2.1.1
|
||||
# via jinja2
|
||||
packaging==21.2
|
||||
packaging==21.3
|
||||
# via
|
||||
# pallets-sphinx-themes
|
||||
# sphinx
|
||||
pallets-sphinx-themes==2.0.1
|
||||
pallets-sphinx-themes==2.0.2
|
||||
# via -r requirements/docs.in
|
||||
pygments==2.10.0
|
||||
pygments==2.12.0
|
||||
# via sphinx
|
||||
pyparsing==2.4.7
|
||||
pyparsing==3.0.8
|
||||
# via packaging
|
||||
pytz==2021.3
|
||||
pytz==2022.1
|
||||
# via babel
|
||||
requests==2.26.0
|
||||
requests==2.27.1
|
||||
# via sphinx
|
||||
snowballstemmer==2.1.0
|
||||
snowballstemmer==2.2.0
|
||||
# via sphinx
|
||||
sphinx==4.2.0
|
||||
sphinx==4.5.0
|
||||
# via
|
||||
# -r requirements/docs.in
|
||||
# pallets-sphinx-themes
|
||||
# sphinx-issues
|
||||
# sphinxcontrib-log-cabinet
|
||||
sphinx-issues==1.2.0
|
||||
sphinx-issues==3.0.1
|
||||
# via -r requirements/docs.in
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
# via sphinx
|
||||
|
@ -60,8 +61,5 @@ sphinxcontrib-qthelp==1.0.3
|
|||
# via sphinx
|
||||
sphinxcontrib-serializinghtml==1.1.5
|
||||
# via sphinx
|
||||
urllib3==1.26.7
|
||||
urllib3==1.26.9
|
||||
# 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:
|
||||
#
|
||||
# pip-compile requirements/tests.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
attrs==21.2.0
|
||||
attrs==21.4.0
|
||||
# via pytest
|
||||
iniconfig==1.1.1
|
||||
# via pytest
|
||||
packaging==21.2
|
||||
packaging==21.3
|
||||
# via pytest
|
||||
pluggy==1.0.0
|
||||
# via pytest
|
||||
py==1.11.0
|
||||
# via pytest
|
||||
pyparsing==2.4.7
|
||||
pyparsing==3.0.8
|
||||
# via packaging
|
||||
pytest==6.2.5
|
||||
pytest==7.1.2
|
||||
# via -r requirements/tests.in
|
||||
toml==0.10.2
|
||||
tomli==2.0.1
|
||||
# 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:
|
||||
#
|
||||
# pip-compile requirements/typing.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
mypy==0.910
|
||||
mypy==0.950
|
||||
# via -r requirements/typing.in
|
||||
mypy-extensions==0.4.3
|
||||
# via mypy
|
||||
toml==0.10.2
|
||||
tomli==2.0.1
|
||||
# via mypy
|
||||
typing-extensions==3.10.0.2
|
||||
typing-extensions==4.2.0
|
||||
# via mypy
|
||||
|
|
|
@ -32,8 +32,8 @@ classifiers =
|
|||
[options]
|
||||
packages = find:
|
||||
package_dir = = src
|
||||
include_package_data = true
|
||||
python_requires = >= 3.6
|
||||
include_package_data = True
|
||||
python_requires = >= 3.7
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
@ -72,7 +72,8 @@ per-file-ignores =
|
|||
|
||||
[mypy]
|
||||
files = src/jinja2
|
||||
python_version = 3.6
|
||||
python_version = 3.7
|
||||
show_error_codes = True
|
||||
disallow_subclassing_any = True
|
||||
disallow_untyped_calls = True
|
||||
disallow_untyped_defs = True
|
||||
|
|
1
setup.py
1
setup.py
|
@ -1,4 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
from setuptools import setup
|
||||
|
||||
# Metadata goes in setup.cfg. These are here for GitHub's dependency graph.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: Jinja2
|
||||
Version: 3.0.3
|
||||
Version: 3.1.2
|
||||
Summary: A very fast and expressive template engine.
|
||||
Home-page: https://palletsprojects.com/p/jinja/
|
||||
Author: Armin Ronacher
|
||||
|
@ -24,7 +24,7 @@ Classifier: Operating System :: OS Independent
|
|||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||
Requires-Python: >=3.6
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
Provides-Extra: i18n
|
||||
License-File: LICENSE.rst
|
||||
|
|
|
@ -80,7 +80,6 @@ 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
|
||||
|
@ -89,6 +88,7 @@ tests/test_lexnparse.py
|
|||
tests/test_loader.py
|
||||
tests/test_nativetypes.py
|
||||
tests/test_nodes.py
|
||||
tests/test_pickle.py
|
||||
tests/test_regression.py
|
||||
tests/test_runtime.py
|
||||
tests/test_security.py
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
[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 TemplateSyntaxError as TemplateSyntaxError
|
||||
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 ChoiceLoader as ChoiceLoader
|
||||
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 Undefined as Undefined
|
||||
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 Markup
|
||||
from .utils import pass_context as pass_context
|
||||
from .utils import pass_environment as pass_environment
|
||||
from .utils import pass_eval_context as pass_eval_context
|
||||
from .utils import select_autoescape as select_autoescape
|
||||
|
||||
__version__ = "3.0.3"
|
||||
__version__ = "3.1.2"
|
||||
|
|
|
@ -2,5 +2,5 @@ import re
|
|||
|
||||
# generated by scripts/generate_identifier_pattern.py
|
||||
pattern = re.compile(
|
||||
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
|
||||
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
|
||||
)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import inspect
|
||||
import typing as t
|
||||
from functools import WRAPPER_ASSIGNMENTS
|
||||
from functools import wraps
|
||||
|
||||
from .utils import _PassArg
|
||||
|
@ -23,7 +24,15 @@ def async_variant(normal_func): # type: ignore
|
|||
def is_async(args: t.Any) -> bool:
|
||||
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
|
||||
b = is_async(args)
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ class Bucket:
|
|||
self.reset()
|
||||
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."""
|
||||
if self.code is None:
|
||||
raise TypeError("can't write empty bucket")
|
||||
|
@ -262,13 +262,55 @@ class FileSystemBytecodeCache(BytecodeCache):
|
|||
def load_bytecode(self, bucket: Bucket) -> None:
|
||||
filename = self._get_cache_filename(bucket)
|
||||
|
||||
if os.path.exists(filename):
|
||||
with open(filename, "rb") as f:
|
||||
bucket.load_bytecode(f)
|
||||
# Don't test for existence before opening the file, since the
|
||||
# file could disappear after the test before the open.
|
||||
try:
|
||||
f = open(filename, "rb")
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
# PermissionError can occur on Windows when an operation is
|
||||
# in progress, such as calling clear().
|
||||
return
|
||||
|
||||
with f:
|
||||
bucket.load_bytecode(f)
|
||||
|
||||
def dump_bytecode(self, bucket: Bucket) -> None:
|
||||
with open(self._get_cache_filename(bucket), "wb") as f:
|
||||
bucket.write_bytecode(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)
|
||||
except BaseException:
|
||||
remove_silent()
|
||||
raise
|
||||
|
||||
try:
|
||||
os.replace(f.name, name)
|
||||
except OSError:
|
||||
# Another process may have called clear(). On Windows,
|
||||
# another program may be holding the file open.
|
||||
remove_silent()
|
||||
except BaseException:
|
||||
remove_silent()
|
||||
raise
|
||||
|
||||
def clear(self) -> None:
|
||||
# imported lazily here because google app-engine doesn't support
|
||||
|
|
|
@ -218,7 +218,7 @@ class Frame:
|
|||
|
||||
def copy(self) -> "Frame":
|
||||
"""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.symbols = self.symbols.copy()
|
||||
return rv
|
||||
|
@ -724,6 +724,7 @@ class CodeGenerator(NodeVisitor):
|
|||
"""
|
||||
self.writeline("resolve = context.resolve_or_missing")
|
||||
self.writeline("undefined = environment.undefined")
|
||||
self.writeline("concat = environment.concat")
|
||||
# always use the standard Undefined class for the implicit else of
|
||||
# conditional expressions
|
||||
self.writeline("cond_expr_undefined = Undefined")
|
||||
|
@ -835,7 +836,6 @@ class CodeGenerator(NodeVisitor):
|
|||
else:
|
||||
exported_names = sorted(exported)
|
||||
|
||||
self.writeline("from __future__ import generator_stop") # Python < 3.7
|
||||
self.writeline("from jinja2.runtime import " + ", ".join(exported_names))
|
||||
|
||||
# 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
|
||||
) -> t.Iterator[None]:
|
||||
if self.environment.is_async:
|
||||
self.write("await auto_await(")
|
||||
self.write("(await auto_await(")
|
||||
|
||||
if is_filter:
|
||||
self.write(f"{self.filters[node.name]}(")
|
||||
|
@ -1790,7 +1790,7 @@ class CodeGenerator(NodeVisitor):
|
|||
self.write(")")
|
||||
|
||||
if self.environment.is_async:
|
||||
self.write(")")
|
||||
self.write("))")
|
||||
|
||||
@optimizeconst
|
||||
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
|
||||
) -> None:
|
||||
if self.environment.is_async:
|
||||
self.write("await auto_await(")
|
||||
self.write("(await auto_await(")
|
||||
if self.environment.sandboxed:
|
||||
self.write("environment.call(context, ")
|
||||
else:
|
||||
|
@ -1858,7 +1858,7 @@ class CodeGenerator(NodeVisitor):
|
|||
self.signature(node, frame, extra_kwargs)
|
||||
self.write(")")
|
||||
if self.environment.is_async:
|
||||
self.write(")")
|
||||
self.write("))")
|
||||
|
||||
def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None:
|
||||
self.write(node.key + "=")
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import platform
|
||||
import sys
|
||||
import typing as t
|
||||
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.
|
||||
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)
|
||||
|
||||
|
@ -189,71 +189,3 @@ def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any
|
|||
data[name] = value
|
||||
|
||||
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.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import typing
|
||||
import typing as t
|
||||
import weakref
|
||||
|
@ -282,6 +281,8 @@ class Environment:
|
|||
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
|
||||
code_generator_class: t.Type["CodeGenerator"] = CodeGenerator
|
||||
|
||||
concat = "".join
|
||||
|
||||
#: the context class that is used for templates. See
|
||||
#: :class:`~jinja2.runtime.Context` for more information.
|
||||
context_class: t.Type[Context] = Context
|
||||
|
@ -392,6 +393,8 @@ class Environment:
|
|||
line_comment_prefix: t.Optional[str] = missing,
|
||||
trim_blocks: bool = missing,
|
||||
lstrip_blocks: bool = missing,
|
||||
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
|
||||
keep_trailing_newline: bool = missing,
|
||||
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
|
||||
optimized: bool = missing,
|
||||
undefined: t.Type[Undefined] = missing,
|
||||
|
@ -401,6 +404,7 @@ class Environment:
|
|||
cache_size: int = missing,
|
||||
auto_reload: bool = missing,
|
||||
bytecode_cache: t.Optional["BytecodeCache"] = missing,
|
||||
enable_async: bool = False,
|
||||
) -> "Environment":
|
||||
"""Create a new overlay environment that shares all the data with the
|
||||
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
|
||||
copied over so modifications on the original environment may not shine
|
||||
through.
|
||||
|
||||
.. versionchanged:: 3.1.2
|
||||
Added the ``newline_sequence``,, ``keep_trailing_newline``,
|
||||
and ``enable_async`` parameters to match ``__init__``.
|
||||
"""
|
||||
args = dict(locals())
|
||||
del args["self"], args["cache_size"], args["extensions"]
|
||||
del args["self"], args["cache_size"], args["extensions"], args["enable_async"]
|
||||
|
||||
rv = object.__new__(self.__class__)
|
||||
rv.__dict__.update(self.__dict__)
|
||||
|
@ -436,6 +444,9 @@ class Environment:
|
|||
if extensions is not missing:
|
||||
rv.extensions.update(load_extensions(rv, extensions))
|
||||
|
||||
if enable_async is not missing:
|
||||
rv.is_async = enable_async
|
||||
|
||||
return _environment_config_check(rv)
|
||||
|
||||
@property
|
||||
|
@ -938,7 +949,7 @@ class Environment:
|
|||
|
||||
@internalcode
|
||||
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":
|
||||
if self.loader is None:
|
||||
raise TypeError("no loader for this environment specified")
|
||||
|
@ -966,13 +977,15 @@ class Environment:
|
|||
self,
|
||||
name: t.Union[str, "Template"],
|
||||
parent: t.Optional[str] = None,
|
||||
globals: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
"""Load a template by name with :attr:`loader` and return a
|
||||
:class:`Template`. If the template does not exist a
|
||||
:exc:`TemplateNotFound` exception is raised.
|
||||
|
||||
:param name: Name of the template to load.
|
||||
:param name: Name of the template to load. When loading
|
||||
templates from the filesystem, "/" is used as the path
|
||||
separator, even on Windows.
|
||||
:param parent: The name of the parent template importing this
|
||||
template. :meth:`join_path` can be used to implement name
|
||||
transformations with this.
|
||||
|
@ -1001,7 +1014,7 @@ class Environment:
|
|||
self,
|
||||
names: t.Iterable[t.Union[str, "Template"]],
|
||||
parent: t.Optional[str] = None,
|
||||
globals: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
"""Like :meth:`get_template`, but tries loading multiple names.
|
||||
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"]]
|
||||
],
|
||||
parent: t.Optional[str] = None,
|
||||
globals: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
"""Use :meth:`select_template` if an iterable of template names
|
||||
is given, or :meth:`get_template` if one name is given.
|
||||
|
@ -1073,7 +1086,7 @@ class Environment:
|
|||
def from_string(
|
||||
self,
|
||||
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":
|
||||
"""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)
|
||||
|
||||
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]:
|
||||
"""Make the globals map for a template. Any given template
|
||||
globals overlay the environment :attr:`globals`.
|
||||
|
@ -1268,14 +1281,11 @@ class Template:
|
|||
|
||||
close = False
|
||||
|
||||
if sys.version_info < (3, 7):
|
||||
loop = asyncio.get_event_loop()
|
||||
else:
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
close = True
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
close = True
|
||||
|
||||
try:
|
||||
return loop.run_until_complete(self.render_async(*args, **kwargs))
|
||||
|
@ -1286,7 +1296,7 @@ class Template:
|
|||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
||||
try:
|
||||
return concat(self.root_render_func(ctx)) # type: ignore
|
||||
return self.environment.concat(self.root_render_func(ctx)) # type: ignore
|
||||
except Exception:
|
||||
self.environment.handle_exception()
|
||||
|
||||
|
@ -1307,7 +1317,9 @@ class Template:
|
|||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
||||
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:
|
||||
return self.environment.handle_exception()
|
||||
|
||||
|
@ -1331,13 +1343,7 @@ class Template:
|
|||
async def to_list() -> t.List[str]:
|
||||
return [x async for x in self.generate_async(*args, **kwargs)]
|
||||
|
||||
if sys.version_info < (3, 7):
|
||||
loop = asyncio.get_event_loop()
|
||||
out = loop.run_until_complete(to_list())
|
||||
else:
|
||||
out = asyncio.run(to_list())
|
||||
|
||||
yield from out
|
||||
yield from asyncio.run(to_list())
|
||||
return
|
||||
|
||||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import pprint
|
||||
import re
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
|
@ -91,7 +90,7 @@ class Extension:
|
|||
|
||||
def bind(self, environment: Environment) -> "Extension":
|
||||
"""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.environment = environment
|
||||
return rv
|
||||
|
@ -355,13 +354,19 @@ class InternationalizationExtension(Extension):
|
|||
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||
"""Parse a translatable tag."""
|
||||
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
|
||||
# defined in the body of the trans block too, but this is checked at
|
||||
# a later state.
|
||||
plural_expr: t.Optional[nodes.Expr] = None
|
||||
plural_expr_assignment: t.Optional[nodes.Assign] = None
|
||||
num_called_num = False
|
||||
variables: t.Dict[str, nodes.Expr] = {}
|
||||
trimmed = None
|
||||
while parser.stream.current.type != "block_end":
|
||||
|
@ -456,6 +461,7 @@ class InternationalizationExtension(Extension):
|
|||
node = self._make_node(
|
||||
singular,
|
||||
plural,
|
||||
context,
|
||||
variables,
|
||||
plural_expr,
|
||||
bool(referenced),
|
||||
|
@ -511,6 +517,7 @@ class InternationalizationExtension(Extension):
|
|||
self,
|
||||
singular: str,
|
||||
plural: t.Optional[str],
|
||||
context: t.Optional[str],
|
||||
variables: t.Dict[str, nodes.Expr],
|
||||
plural_expr: t.Optional[nodes.Expr],
|
||||
vars_referenced: bool,
|
||||
|
@ -527,21 +534,18 @@ class InternationalizationExtension(Extension):
|
|||
if plural:
|
||||
plural = plural.replace("%%", "%")
|
||||
|
||||
# singular only:
|
||||
if plural_expr is None:
|
||||
gettext = nodes.Name("gettext", "load")
|
||||
node = nodes.Call(gettext, [nodes.Const(singular)], [], None, None)
|
||||
func_name = "gettext"
|
||||
func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
|
||||
|
||||
# singular and plural
|
||||
else:
|
||||
ngettext = nodes.Name("ngettext", "load")
|
||||
node = nodes.Call(
|
||||
ngettext,
|
||||
[nodes.Const(singular), nodes.Const(plural), plural_expr],
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
if context is not None:
|
||||
func_args.insert(0, nodes.Const(context))
|
||||
func_name = f"p{func_name}"
|
||||
|
||||
if plural_expr is not None:
|
||||
func_name = f"n{func_name}"
|
||||
func_args.extend((nodes.Const(plural), plural_expr))
|
||||
|
||||
node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)
|
||||
|
||||
# in case newstyle gettext is used, the method is powerful
|
||||
# enough to handle the variable expansion and autoescape
|
||||
|
@ -597,28 +601,6 @@ class LoopControlExtension(Extension):
|
|||
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):
|
||||
"""A ``{% debug %}`` tag that dumps the available variables,
|
||||
filters, and tests.
|
||||
|
@ -874,6 +856,4 @@ def babel_extract(
|
|||
i18n = InternationalizationExtension
|
||||
do = ExprStmtExtension
|
||||
loopcontrols = LoopControlExtension
|
||||
with_ = WithExtension
|
||||
autoescape = AutoEscapeExtension
|
||||
debug = DebugExtension
|
||||
|
|
|
@ -4,7 +4,6 @@ import random
|
|||
import re
|
||||
import typing
|
||||
import typing as t
|
||||
import warnings
|
||||
from collections import abc
|
||||
from itertools import chain
|
||||
from itertools import groupby
|
||||
|
@ -44,58 +43,6 @@ K = t.TypeVar("K")
|
|||
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:
|
||||
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
|
||||
to lowercase and returns other types as-is."""
|
||||
|
@ -271,6 +218,36 @@ def do_lower(s: str) -> str:
|
|||
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
|
||||
def do_xmlattr(
|
||||
eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
|
||||
|
@ -415,7 +392,7 @@ def do_sort(
|
|||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% for user users|sort(attribute="age,name") %}
|
||||
{% for user in users|sort(attribute="age,name") %}
|
||||
...
|
||||
{% endfor %}
|
||||
|
||||
|
@ -1164,7 +1141,7 @@ def do_round(
|
|||
return round(value, precision)
|
||||
|
||||
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):
|
||||
|
@ -1186,7 +1163,8 @@ def sync_do_groupby(
|
|||
value: "t.Iterable[V]",
|
||||
attribute: t.Union[str, int],
|
||||
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
|
||||
:func:`itertools.groupby`. The attribute can use dot notation for
|
||||
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>
|
||||
{% 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
|
||||
Added the ``default`` parameter.
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
The attribute supports dot notation for nested access.
|
||||
"""
|
||||
expr = make_attrgetter(environment, attribute, default=default)
|
||||
return [
|
||||
expr = make_attrgetter(
|
||||
environment,
|
||||
attribute,
|
||||
postprocess=ignore_case if not case_sensitive else None,
|
||||
default=default,
|
||||
)
|
||||
out = [
|
||||
_GroupTuple(key, list(values))
|
||||
for key, values in groupby(sorted(value, key=expr), expr)
|
||||
]
|
||||
|
||||
if not case_sensitive:
|
||||
# Return the real key from the first value instead of the lowercase key.
|
||||
output_expr = make_attrgetter(environment, attribute, default=default)
|
||||
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out]
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@async_variant(sync_do_groupby) # type: ignore
|
||||
async def do_groupby(
|
||||
|
@ -1245,13 +1247,26 @@ async def do_groupby(
|
|||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
attribute: t.Union[str, int],
|
||||
default: t.Optional[t.Any] = None,
|
||||
) -> "t.List[t.Tuple[t.Any, t.List[V]]]":
|
||||
expr = make_attrgetter(environment, attribute, default=default)
|
||||
return [
|
||||
case_sensitive: bool = False,
|
||||
) -> "t.List[_GroupTuple]":
|
||||
expr = make_attrgetter(
|
||||
environment,
|
||||
attribute,
|
||||
postprocess=ignore_case if not case_sensitive else None,
|
||||
default=default,
|
||||
)
|
||||
out = [
|
||||
_GroupTuple(key, await auto_to_list(values))
|
||||
for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr)
|
||||
]
|
||||
|
||||
if not case_sensitive:
|
||||
# Return the real key from the first value instead of the lowercase key.
|
||||
output_expr = make_attrgetter(environment, attribute, default=default)
|
||||
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out]
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@pass_environment
|
||||
def sync_do_sum(
|
||||
|
@ -1271,13 +1286,13 @@ def sync_do_sum(
|
|||
Total: {{ items|sum(attribute='price') }}
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
The `attribute` parameter was added to allow suming up over
|
||||
attributes. Also the `start` parameter was moved on to the right.
|
||||
The ``attribute`` parameter was added to allow summing up over
|
||||
attributes. Also the ``start`` parameter was moved on to the right.
|
||||
"""
|
||||
if attribute is not None:
|
||||
iterable = map(make_attrgetter(environment, attribute), iterable)
|
||||
|
||||
return sum(iterable, start)
|
||||
return sum(iterable, start) # type: ignore[no-any-return, call-overload]
|
||||
|
||||
|
||||
@async_variant(sync_do_sum) # type: ignore
|
||||
|
@ -1792,6 +1807,7 @@ FILTERS = {
|
|||
"length": len,
|
||||
"list": do_list,
|
||||
"lower": do_lower,
|
||||
"items": do_items,
|
||||
"map": do_map,
|
||||
"min": do_min,
|
||||
"max": do_max,
|
||||
|
|
|
@ -84,7 +84,7 @@ class Symbols:
|
|||
return rv
|
||||
|
||||
def copy(self) -> "Symbols":
|
||||
rv = t.cast(Symbols, object.__new__(self.__class__))
|
||||
rv = object.__new__(self.__class__)
|
||||
rv.__dict__.update(self.__dict__)
|
||||
rv.refs = self.refs.copy()
|
||||
rv.loads = self.loads.copy()
|
||||
|
|
|
@ -507,19 +507,17 @@ class Lexer:
|
|||
# block suffix if trimming is enabled
|
||||
block_suffix_re = "\\n?" if environment.trim_blocks else ""
|
||||
|
||||
# If lstrip is enabled, it should not be applied if there is any
|
||||
# non-whitespace between the newline and block.
|
||||
self.lstrip_unless_re = c(r"[^ \t]") if environment.lstrip_blocks else None
|
||||
self.lstrip_blocks = environment.lstrip_blocks
|
||||
|
||||
self.newline_sequence = environment.newline_sequence
|
||||
self.keep_trailing_newline = environment.keep_trailing_newline
|
||||
|
||||
root_raw_re = (
|
||||
fr"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
|
||||
fr"(?:\-{block_end_re}\s*|{block_end_re}))"
|
||||
rf"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
|
||||
rf"(?:\-{block_end_re}\s*|{block_end_re}))"
|
||||
)
|
||||
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
|
||||
|
@ -527,7 +525,7 @@ class Lexer:
|
|||
"root": [
|
||||
# directives
|
||||
_Rule(
|
||||
c(fr"(.*?)(?:{root_parts_re})"),
|
||||
c(rf"(.*?)(?:{root_parts_re})"),
|
||||
OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore
|
||||
"#bygroup",
|
||||
),
|
||||
|
@ -538,8 +536,8 @@ class Lexer:
|
|||
TOKEN_COMMENT_BEGIN: [
|
||||
_Rule(
|
||||
c(
|
||||
fr"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
|
||||
fr"|{comment_end_re}{block_suffix_re}))"
|
||||
rf"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
|
||||
rf"|{comment_end_re}{block_suffix_re}))"
|
||||
),
|
||||
(TOKEN_COMMENT, TOKEN_COMMENT_END),
|
||||
"#pop",
|
||||
|
@ -550,8 +548,8 @@ class Lexer:
|
|||
TOKEN_BLOCK_BEGIN: [
|
||||
_Rule(
|
||||
c(
|
||||
fr"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||
fr"|{block_end_re}{block_suffix_re})"
|
||||
rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||
rf"|{block_end_re}{block_suffix_re})"
|
||||
),
|
||||
TOKEN_BLOCK_END,
|
||||
"#pop",
|
||||
|
@ -561,7 +559,7 @@ class Lexer:
|
|||
# variables
|
||||
TOKEN_VARIABLE_BEGIN: [
|
||||
_Rule(
|
||||
c(fr"\-{variable_end_re}\s*|{variable_end_re}"),
|
||||
c(rf"\-{variable_end_re}\s*|{variable_end_re}"),
|
||||
TOKEN_VARIABLE_END,
|
||||
"#pop",
|
||||
)
|
||||
|
@ -571,9 +569,9 @@ class Lexer:
|
|||
TOKEN_RAW_BEGIN: [
|
||||
_Rule(
|
||||
c(
|
||||
fr"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
|
||||
fr"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||
fr"|{block_end_re}{block_suffix_re}))"
|
||||
rf"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
|
||||
rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||
rf"|{block_end_re}{block_suffix_re}))"
|
||||
),
|
||||
OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore
|
||||
"#pop",
|
||||
|
@ -697,7 +695,6 @@ class Lexer:
|
|||
statetokens = self.rules[stack[-1]]
|
||||
source_length = len(source)
|
||||
balancing_stack: t.List[str] = []
|
||||
lstrip_unless_re = self.lstrip_unless_re
|
||||
newlines_stripped = 0
|
||||
line_starting = True
|
||||
|
||||
|
@ -723,7 +720,7 @@ class Lexer:
|
|||
|
||||
# tuples support more options
|
||||
if isinstance(tokens, tuple):
|
||||
groups = m.groups()
|
||||
groups: t.Sequence[str] = m.groups()
|
||||
|
||||
if isinstance(tokens, OptionalLStrip):
|
||||
# Rule supports lstrip. Match will look like
|
||||
|
@ -743,7 +740,7 @@ class Lexer:
|
|||
# Not marked for preserving whitespace.
|
||||
strip_sign != "+"
|
||||
# lstrip is enabled.
|
||||
and lstrip_unless_re is not None
|
||||
and self.lstrip_blocks
|
||||
# Not a variable expression.
|
||||
and not m.groupdict().get(TOKEN_VARIABLE_BEGIN)
|
||||
):
|
||||
|
@ -753,7 +750,7 @@ class Lexer:
|
|||
if l_pos > 0 or line_starting:
|
||||
# If there's only whitespace between the newline and the
|
||||
# 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:]]
|
||||
|
||||
for idx, token in enumerate(tokens):
|
||||
|
|
|
@ -3,6 +3,7 @@ sources.
|
|||
"""
|
||||
import importlib.util
|
||||
import os
|
||||
import posixpath
|
||||
import sys
|
||||
import typing as t
|
||||
import weakref
|
||||
|
@ -193,7 +194,9 @@ class FileSystemLoader(BaseLoader):
|
|||
) -> t.Tuple[str, str, t.Callable[[], bool]]:
|
||||
pieces = split_template_path(template)
|
||||
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)
|
||||
if f is None:
|
||||
continue
|
||||
|
@ -210,7 +213,8 @@ class FileSystemLoader(BaseLoader):
|
|||
except OSError:
|
||||
return False
|
||||
|
||||
return contents, filename, uptodate
|
||||
# Use normpath to convert Windows altsep to sep.
|
||||
return contents, os.path.normpath(filename), uptodate
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self) -> t.List[str]:
|
||||
|
@ -296,7 +300,7 @@ class PackageLoader(BaseLoader):
|
|||
if isinstance(loader, zipimport.zipimporter):
|
||||
self._archive = loader.archive
|
||||
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:
|
||||
roots: t.List[str] = []
|
||||
|
||||
|
@ -326,7 +330,12 @@ class PackageLoader(BaseLoader):
|
|||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> 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]]
|
||||
|
||||
if self._archive is None:
|
||||
|
@ -603,7 +612,7 @@ class ModuleLoader(BaseLoader):
|
|||
if not isinstance(path, abc.Iterable) or isinstance(path, str):
|
||||
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(
|
||||
mod, lambda x: sys.modules.pop(package_name, None)
|
||||
|
|
|
@ -3,6 +3,7 @@ from ast import literal_eval
|
|||
from ast import parse
|
||||
from itertools import chain
|
||||
from itertools import islice
|
||||
from types import GeneratorType
|
||||
|
||||
from . import nodes
|
||||
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):
|
||||
return raw
|
||||
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:
|
||||
return literal_eval(
|
||||
|
@ -86,6 +89,7 @@ class NativeEnvironment(Environment):
|
|||
"""An environment that renders templates to native Python types."""
|
||||
|
||||
code_generator_class = NativeCodeGenerator
|
||||
concat = staticmethod(native_concat) # type: ignore
|
||||
|
||||
|
||||
class NativeTemplate(Template):
|
||||
|
@ -101,7 +105,9 @@ class NativeTemplate(Template):
|
|||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
||||
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:
|
||||
return self.environment.handle_exception()
|
||||
|
||||
|
@ -114,7 +120,7 @@ class NativeTemplate(Template):
|
|||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
||||
try:
|
||||
return native_concat(
|
||||
return self.environment_class.concat( # type: ignore
|
||||
[n async for n in self.root_render_func(ctx)] # type: ignore
|
||||
)
|
||||
except Exception:
|
||||
|
|
|
@ -160,7 +160,7 @@ class Parser:
|
|||
self._last_identifier += 1
|
||||
rv = object.__new__(nodes.InternalName)
|
||||
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]]:
|
||||
"""Parse a single statement."""
|
||||
|
@ -364,14 +364,10 @@ class Parser:
|
|||
node.names = []
|
||||
|
||||
def parse_context() -> bool:
|
||||
if (
|
||||
self.stream.current.value
|
||||
in {
|
||||
"with",
|
||||
"without",
|
||||
}
|
||||
and self.stream.look().test("name:context")
|
||||
):
|
||||
if self.stream.current.value in {
|
||||
"with",
|
||||
"without",
|
||||
} and self.stream.look().test("name:context"):
|
||||
node.with_context = next(self.stream).value == "with"
|
||||
self.stream.skip()
|
||||
return True
|
||||
|
@ -957,19 +953,15 @@ class Parser:
|
|||
kwargs = []
|
||||
if self.stream.current.type == "lparen":
|
||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
||||
elif (
|
||||
self.stream.current.type
|
||||
in {
|
||||
"name",
|
||||
"string",
|
||||
"integer",
|
||||
"float",
|
||||
"lparen",
|
||||
"lbracket",
|
||||
"lbrace",
|
||||
}
|
||||
and not self.stream.current.test_any("name:else", "name:or", "name:and")
|
||||
):
|
||||
elif self.stream.current.type in {
|
||||
"name",
|
||||
"string",
|
||||
"integer",
|
||||
"float",
|
||||
"lparen",
|
||||
"lbracket",
|
||||
"lbrace",
|
||||
} and not self.stream.current.test_any("name:else", "name:or", "name:and"):
|
||||
if self.stream.current.test("name:is"):
|
||||
self.fail("You cannot chain multiple tests with is")
|
||||
arg_node = self.parse_primary()
|
||||
|
|
|
@ -49,7 +49,6 @@ exported = [
|
|||
"Markup",
|
||||
"TemplateRuntimeError",
|
||||
"missing",
|
||||
"concat",
|
||||
"escape",
|
||||
"markup_join",
|
||||
"str_join",
|
||||
|
@ -89,18 +88,6 @@ def str_join(seq: t.Iterable[t.Any]) -> str:
|
|||
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(
|
||||
environment: "Environment",
|
||||
template_name: t.Optional[str],
|
||||
|
@ -173,27 +160,6 @@ class Context:
|
|||
: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__(
|
||||
self,
|
||||
environment: "Environment",
|
||||
|
@ -251,15 +217,6 @@ class Context:
|
|||
|
||||
: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)
|
||||
|
||||
if rv is missing:
|
||||
|
@ -277,14 +234,6 @@ class Context:
|
|||
|
||||
: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:
|
||||
return self.vars[key]
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
|||
class SandboxedFormatter(Formatter):
|
||||
def __init__(self, env: Environment, **kwargs: t.Any) -> None:
|
||||
self._env = env
|
||||
super().__init__(**kwargs) # type: ignore
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def get_field(
|
||||
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
|
||||
|
|
|
@ -3,7 +3,6 @@ import json
|
|||
import os
|
||||
import re
|
||||
import typing as t
|
||||
import warnings
|
||||
from collections import abc
|
||||
from collections import deque
|
||||
from random import choice
|
||||
|
@ -84,74 +83,9 @@ class _PassArg(enum.Enum):
|
|||
if hasattr(obj, "jinja_pass_arg"):
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
"""Marks the function as internally used"""
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
class LRUCache:
|
||||
"""A simple LRU Cache implementation."""
|
||||
|
@ -831,24 +753,3 @@ class Namespace:
|
|||
|
||||
def __repr__(self) -> str:
|
||||
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
|
||||
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:
|
||||
"""Visit a node."""
|
||||
|
@ -43,8 +43,8 @@ class NodeVisitor:
|
|||
|
||||
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for node in node.iter_child_nodes():
|
||||
self.visit(node, *args, **kwargs)
|
||||
for child_node in node.iter_child_nodes():
|
||||
self.visit(child_node, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeTransformer(NodeVisitor):
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import asyncio
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -14,19 +13,6 @@ from jinja2.exceptions import UndefinedError
|
|||
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():
|
||||
t = Template(
|
||||
"{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True
|
||||
|
@ -35,7 +21,7 @@ def test_basic_async():
|
|||
async def func():
|
||||
return await t.render_async()
|
||||
|
||||
rv = run(func())
|
||||
rv = asyncio.run(func())
|
||||
assert rv == "[1][2][3]"
|
||||
|
||||
|
||||
|
@ -51,7 +37,7 @@ def test_await_on_calls():
|
|||
async def func():
|
||||
return await t.render_async(async_func=async_func, normal_func=normal_func)
|
||||
|
||||
rv = run(func())
|
||||
rv = asyncio.run(func())
|
||||
assert rv == "65"
|
||||
|
||||
|
||||
|
@ -65,7 +51,6 @@ def test_await_on_calls_normal_render():
|
|||
return 23
|
||||
|
||||
rv = t.render(async_func=async_func, normal_func=normal_func)
|
||||
|
||||
assert rv == "65"
|
||||
|
||||
|
||||
|
@ -81,7 +66,7 @@ def test_await_and_macros():
|
|||
async def func():
|
||||
return await t.render_async(async_func=async_func)
|
||||
|
||||
rv = run(func())
|
||||
rv = asyncio.run(func())
|
||||
assert rv == "[42][42]"
|
||||
|
||||
|
||||
|
@ -95,7 +80,7 @@ def test_async_blocks():
|
|||
async def func():
|
||||
return await t.render_async()
|
||||
|
||||
rv = run(func())
|
||||
rv = asyncio.run(func())
|
||||
assert rv == "<Test><Test>"
|
||||
|
||||
|
||||
|
@ -172,19 +157,18 @@ class TestAsyncImports:
|
|||
test_env_async.from_string('{% from "foo" import bar, with with context %}')
|
||||
|
||||
def test_exports(self, test_env_async):
|
||||
m = run(
|
||||
test_env_async.from_string(
|
||||
"""
|
||||
coro = test_env_async.from_string(
|
||||
"""
|
||||
{% macro toplevel() %}...{% endmacro %}
|
||||
{% macro __private() %}...{% endmacro %}
|
||||
{% set variable = 42 %}
|
||||
{% for item in [1] %}
|
||||
{% macro notthere() %}{% endmacro %}
|
||||
{% endfor %}
|
||||
"""
|
||||
)._get_default_module_async()
|
||||
)
|
||||
assert run(m.toplevel()) == "..."
|
||||
"""
|
||||
)._get_default_module_async()
|
||||
m = asyncio.run(coro)
|
||||
assert asyncio.run(m.toplevel()) == "..."
|
||||
assert not hasattr(m, "__missing")
|
||||
assert m.variable == 42
|
||||
assert not hasattr(m, "notthere")
|
||||
|
@ -621,7 +605,7 @@ def test_namespace_awaitable(test_env_async):
|
|||
actual = await t.render_async()
|
||||
assert actual == "Bar"
|
||||
|
||||
run(_test())
|
||||
asyncio.run(_test())
|
||||
|
||||
|
||||
def test_chainable_undefined_aiter():
|
||||
|
@ -634,7 +618,7 @@ def test_chainable_undefined_aiter():
|
|||
rv = await t.render_async(a={})
|
||||
assert rv == ""
|
||||
|
||||
run(_test())
|
||||
asyncio.run(_test())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -648,7 +632,7 @@ def test_native_async(async_native_env):
|
|||
rv = await t.render_async(x=23)
|
||||
assert rv == 23
|
||||
|
||||
run(_test())
|
||||
asyncio.run(_test())
|
||||
|
||||
|
||||
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)))
|
||||
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)])
|
||||
def test_groupby_tuple_index(env_async, items):
|
||||
tmpl = env_async.from_string(
|
||||
|
|
|
@ -25,7 +25,7 @@ class TestDebug:
|
|||
m = re.search(expected_tb.strip(), "".join(tb))
|
||||
assert (
|
||||
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():
|
||||
|
|
|
@ -43,6 +43,9 @@ newstyle_i18n_templates = {
|
|||
"pgettext.html": '{{ pgettext("fruit", "Apple") }}',
|
||||
"npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s 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 %}",
|
||||
"transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}",
|
||||
"transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}",
|
||||
|
@ -593,11 +596,20 @@ class TestNewstyleInternationalization:
|
|||
tmpl = newstyle_i18n_env.get_template("pgettext.html")
|
||||
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")
|
||||
assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple"
|
||||
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:
|
||||
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()
|
||||
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):
|
||||
from pprint import pformat
|
||||
|
||||
|
@ -608,6 +619,25 @@ class TestFilter:
|
|||
)
|
||||
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):
|
||||
tmpl = env.from_string(
|
||||
"{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}"
|
||||
|
|
|
@ -171,6 +171,15 @@ class TestFileSystemLoader:
|
|||
t = e.get_template("mojibake.txt")
|
||||
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:
|
||||
archive = None
|
||||
|
|
|
@ -153,3 +153,10 @@ def test_leading_spaces(env):
|
|||
t = env.from_string(" {{ True }}")
|
||||
result = t.render()
|
||||
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)
|
||||
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):
|
||||
tmpl = env.from_string(
|
||||
"{%- for value in values recursive %}1{% else %}0{% endfor -%}"
|
||||
|
|
Loading…
Reference in New Issue