mirror of
https://gitee.com/openharmony/third_party_jinja2
synced 2024-11-23 15:19:46 +00:00
Merge branch 'master' into 557-map-default
This commit is contained in:
commit
81783615ea
61
.azure-pipelines.yml
Normal file
61
.azure-pipelines.yml
Normal file
@ -0,0 +1,61 @@
|
||||
trigger:
|
||||
- master
|
||||
- '*.x'
|
||||
|
||||
variables:
|
||||
vmImage: ubuntu-latest
|
||||
python.version: 3.7
|
||||
TOXENV: py,coverage-ci
|
||||
hasTestResults: true
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
Python 3.7 Linux:
|
||||
vmImage: ubuntu-latest
|
||||
Python 3.7 Windows:
|
||||
vmImage: windows-latest
|
||||
Python 3.7 Mac:
|
||||
vmImage: macos-latest
|
||||
PyPy 3 Linux:
|
||||
python.version: pypy3
|
||||
Python 3.6 Linux:
|
||||
python.version: 3.6
|
||||
Python 3.5 Linux:
|
||||
python.version: 3.5
|
||||
Python 2.7 Linux:
|
||||
python.version: 2.7
|
||||
Python 2.7 Windows:
|
||||
python.version: 2.7
|
||||
vmImage: windows-latest
|
||||
Docs:
|
||||
TOXENV: docs-html
|
||||
hasTestResults: false
|
||||
|
||||
pool:
|
||||
vmImage: $[ variables.vmImage ]
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: $(python.version)
|
||||
displayName: Use Python $(python.version)
|
||||
|
||||
- script: pip --disable-pip-version-check install -U tox
|
||||
displayName: Install tox
|
||||
|
||||
- script: tox -s false -- --junit-xml=test-results.xml
|
||||
displayName: Run tox
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFiles: test-results.xml
|
||||
testRunTitle: $(Agent.JobName)
|
||||
condition: eq(variables['hasTestResults'], 'true')
|
||||
displayName: Publish test results
|
||||
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
summaryFileLocation: coverage.xml
|
||||
condition: eq(variables['hasTestResults'], 'true')
|
||||
displayName: Publish coverage results
|
11
.coveragerc
11
.coveragerc
@ -1,11 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source =
|
||||
jinja2
|
||||
tests
|
||||
|
||||
[paths]
|
||||
source =
|
||||
jinja2
|
||||
.tox/*/lib/python*/site-packages/jinja2
|
||||
.tox/pypy/site-packages/jinja2
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ dist/
|
||||
.tox/
|
||||
.cache/
|
||||
.idea/
|
||||
env/
|
||||
venv/
|
||||
venv-*/
|
||||
.coverage
|
||||
|
46
.travis.yml
46
.travis.yml
@ -1,46 +0,0 @@
|
||||
dist: xenial
|
||||
sudo: false
|
||||
language: python
|
||||
|
||||
python:
|
||||
- 3.7
|
||||
- 3.6
|
||||
- 3.5
|
||||
- 3.4
|
||||
- 2.7
|
||||
- pypy3.5-6.0
|
||||
- pypy2.7-6.0
|
||||
|
||||
env:
|
||||
- TOXENV=py,codecov
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- python: nightly
|
||||
env: TOXENV=py
|
||||
- python: 3.6
|
||||
env: TOXENV=docs-html
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
|
||||
script:
|
||||
- tox
|
||||
|
||||
cache:
|
||||
- pip
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^.*-maintenance$/
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#pocoo"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
use_notice: true
|
||||
skip_join: true
|
20
CHANGES.rst
20
CHANGES.rst
@ -13,11 +13,25 @@ unreleased
|
||||
:class:`~environment.Environment` enables it, in order to avoid a
|
||||
slow initial import. (`#765`_)
|
||||
- Python 2.6 and 3.3 are not supported anymore.
|
||||
- The `map` filter in async mode now automatically awaits
|
||||
- Added `default` parameter for the `map` filter. (`#985`_)
|
||||
- The ``map`` filter in async mode now automatically awaits
|
||||
- Added a new ``ChainableUndefined`` class to support getitem
|
||||
and getattr on an undefined object. (`#977`_)
|
||||
- Allow ``{%+`` syntax (with NOP behavior) when
|
||||
``lstrip_blocks == False`` (`#748`_)
|
||||
- Added a ``default`` parameter for the ``map`` filter. (`#557`_)
|
||||
|
||||
.. _#557: https://github.com/pallets/jinja/issues/557
|
||||
.. _#765: https://github.com/pallets/jinja/issues/765
|
||||
.. _#985: https://github.com/pallets/jinja/pull/985
|
||||
.. _#748: https://github.com/pallets/jinja/issues/748
|
||||
.. _#977: https://github.com/pallets/jinja/issues/977
|
||||
|
||||
|
||||
Version 2.10.2
|
||||
--------------
|
||||
|
||||
_Unreleased_
|
||||
|
||||
- Fix Python 3.7 deprecation warnings.
|
||||
|
||||
|
||||
Version 2.10.1
|
||||
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at report@palletsprojects.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
31
LICENSE
31
LICENSE
@ -1,31 +0,0 @@
|
||||
Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* 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.
|
||||
|
||||
* The names of the contributors may not 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
|
||||
OWNER 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.
|
28
LICENSE.rst
Normal file
28
LICENSE.rst
Normal file
@ -0,0 +1,28 @@
|
||||
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.
|
21
Makefile
21
Makefile
@ -1,21 +0,0 @@
|
||||
test:
|
||||
py.test tests --tb=short
|
||||
|
||||
develop:
|
||||
pip install --editable .
|
||||
|
||||
tox-test:
|
||||
@tox
|
||||
|
||||
release:
|
||||
python scripts/make-release.py
|
||||
|
||||
upload-docs:
|
||||
$(MAKE) -C docs html dirhtml latex
|
||||
$(MAKE) -C docs/_build/latex all-pdf
|
||||
cd docs/_build/; mv html jinja-docs; zip -r jinja-docs.zip jinja-docs; mv jinja-docs html
|
||||
scp -r docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/
|
||||
scp -r docs/_build/latex/Jinja2.pdf flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/jinja-docs.pdf
|
||||
scp -r docs/_build/jinja-docs.zip flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/
|
||||
|
||||
.PHONY: test
|
@ -2,6 +2,7 @@ API
|
||||
===
|
||||
|
||||
.. module:: jinja2
|
||||
:noindex:
|
||||
:synopsis: public Jinja2 API
|
||||
|
||||
This document describes the API to Jinja2 and not the template language. It
|
||||
@ -46,7 +47,7 @@ To load a template from this environment you just have to call the
|
||||
|
||||
To render it with some variables, just call the :meth:`render` method::
|
||||
|
||||
print template.render(the='variables', go='here')
|
||||
print(template.render(the='variables', go='here'))
|
||||
|
||||
Using a template loader rather than passing strings to :class:`Template`
|
||||
or :meth:`Environment.from_string` has multiple advantages. Besides being
|
||||
@ -322,7 +323,7 @@ unable to look up a name or access an attribute one of those objects is
|
||||
created and returned. Some operations on undefined values are then allowed,
|
||||
others fail.
|
||||
|
||||
The closest to regular Python behavior is the `StrictUndefined` which
|
||||
The closest to regular Python behavior is the :class:`StrictUndefined` which
|
||||
disallows all operations beside testing if it's an undefined object.
|
||||
|
||||
.. autoclass:: jinja2.Undefined()
|
||||
@ -353,6 +354,8 @@ disallows all operations beside testing if it's an undefined object.
|
||||
:attr:`_undefined_exception` with an error message generated
|
||||
from the undefined hints stored on the undefined object.
|
||||
|
||||
.. autoclass:: jinja2.ChainableUndefined()
|
||||
|
||||
.. autoclass:: jinja2.DebugUndefined()
|
||||
|
||||
.. autoclass:: jinja2.StrictUndefined()
|
||||
@ -834,7 +837,7 @@ Here a simple test that checks if a variable is a prime number::
|
||||
def is_prime(n):
|
||||
if n == 2:
|
||||
return True
|
||||
for i in xrange(2, int(math.ceil(math.sqrt(n))) + 1):
|
||||
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
|
||||
if n % i == 0:
|
||||
return False
|
||||
return True
|
||||
|
@ -153,7 +153,7 @@ Why is there no Python 2.3/2.4/2.5/2.6/3.1/3.2/3.3 support?
|
||||
Python 2.3 is missing a lot of features that are used heavily in Jinja2. This
|
||||
decision was made as with the upcoming Python 2.6 and 3.0 versions it becomes
|
||||
harder to maintain the code for older Python versions. If you really need
|
||||
Python 2.3 support you either have to use `Jinja 1`_ or other templating
|
||||
Python 2.3 support you either have to use Jinja 1 or other templating
|
||||
engines that still support 2.3.
|
||||
|
||||
Python 2.4/2.5/3.1/3.2 support was removed when we switched to supporting
|
||||
@ -192,4 +192,3 @@ 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: http://jinja.pocoo.org/1/
|
||||
|
@ -395,7 +395,9 @@ this template "extends" another template. When the template system evaluates
|
||||
this template, it first locates the parent. The extends tag should be the
|
||||
first tag in the template. Everything before it is printed out normally and
|
||||
may cause confusion. For details about this behavior and how to take
|
||||
advantage of it, see :ref:`null-master-fallback`.
|
||||
advantage of it, see :ref:`null-master-fallback`. Also a block will always be
|
||||
filled in regardless of whether the surrounding condition is evaluated to be true
|
||||
or false.
|
||||
|
||||
The filename of the template depends on the template loader. For example, the
|
||||
:class:`FileSystemLoader` allows you to access other templates by giving the
|
||||
@ -425,7 +427,7 @@ If you want to print a block multiple times, you can, however, use the special
|
||||
Super Blocks
|
||||
~~~~~~~~~~~~
|
||||
|
||||
It's possible to render the contents of the parent block by calling `super`.
|
||||
It's possible to render the contents of the parent block by calling ``super()``.
|
||||
This gives back the results of the parent block::
|
||||
|
||||
{% block sidebar %}
|
||||
@ -435,6 +437,41 @@ This gives back the results of the parent block::
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Nesting extends
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
In the case of multiple levels of ``{% extends %}``,
|
||||
``super`` references may be chained (as in ``super.super()``)
|
||||
to skip levels in the inheritance tree.
|
||||
|
||||
For example::
|
||||
|
||||
# parent.tmpl
|
||||
body: {% block body %}Hi from parent.{% endblock %}
|
||||
|
||||
# child.tmpl
|
||||
{% extends "parent.tmpl" %}
|
||||
{% block body %}Hi from child. {{ super() }}{% endblock %}
|
||||
|
||||
# grandchild1.tmpl
|
||||
{% extends "child.tmpl" %}
|
||||
{% block body %}Hi from grandchild1.{% endblock %}
|
||||
|
||||
# grandchild2.tmpl
|
||||
{% extends "child.tmpl" %}
|
||||
{% block body %}Hi from grandchild2. {{ super.super() }} {% endblock %}
|
||||
|
||||
|
||||
Rendering ``child.tmpl`` will give
|
||||
``body: Hi from child. Hi from parent.``
|
||||
|
||||
Rendering ``grandchild1.tmpl`` will give
|
||||
``body: Hi from grandchild1.``
|
||||
|
||||
Rendering ``grandchild2.tmpl`` will give
|
||||
``body: Hi from grandchild2. Hi from parent.``
|
||||
|
||||
|
||||
Named Block End-Tags
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -577,7 +614,7 @@ As variables in templates retain their object properties, it is possible to
|
||||
iterate over containers like `dict`::
|
||||
|
||||
<dl>
|
||||
{% for key, value in my_dict.iteritems() %}
|
||||
{% for key, value in my_dict.items() %}
|
||||
<dt>{{ key|e }}</dt>
|
||||
<dd>{{ value|e }}</dd>
|
||||
{% endfor %}
|
||||
@ -1201,7 +1238,6 @@ but exists for completeness' sake. The following operators are supported:
|
||||
/
|
||||
Divide two numbers. The return value will be a floating point number.
|
||||
``{{ 1 / 2 }}`` is ``{{ 0.5 }}``.
|
||||
(Just like ``from __future__ import division``.)
|
||||
|
||||
//
|
||||
Divide two numbers and return the truncated integer result.
|
||||
@ -1314,11 +1350,31 @@ The general syntax is ``<do something> if <something is true> else <do
|
||||
something else>``.
|
||||
|
||||
The `else` part is optional. If not provided, the else block implicitly
|
||||
evaluates into an undefined object:
|
||||
evaluates into an undefined object::
|
||||
|
||||
{{ ('[%s]' % page.title) if page.title }}
|
||||
|
||||
|
||||
.. _python-methods:
|
||||
|
||||
Python Methods
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You can also use any of the methods of defined on a variable's type.
|
||||
The value returned from the method invocation is used as the value of the expression.
|
||||
Here is an example that uses methods defined on strings (where ``page.title`` is a string):
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{{ ('[%s]' % page.title) if page.title }}
|
||||
{{ page.title.capitalize() }}
|
||||
|
||||
This also works for methods on user-defined types.
|
||||
For example, if variable ``f`` of type ``Foo`` has a method ``bar`` defined on it,
|
||||
you can do the following:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{{ f.bar() }}
|
||||
|
||||
|
||||
.. _builtin-filters:
|
||||
|
@ -82,6 +82,9 @@ syn region jinjaRaw matchgroup=jinjaRawDelim start="{%\s*raw\s*%}" end="{%\s*end
|
||||
|
||||
" Jinja comments
|
||||
syn region jinjaComment matchgroup=jinjaCommentDelim start="{#" end="#}" containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaString,jinjaComment
|
||||
" help support folding for some setups
|
||||
setlocal commentstring={#%s#}
|
||||
setlocal comments=s:{#,e:#}
|
||||
|
||||
" 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
|
||||
|
@ -32,7 +32,7 @@ class InlineGettext(Extension):
|
||||
paren_stack = 0
|
||||
|
||||
for token in stream:
|
||||
if token.type is not 'data':
|
||||
if token.type != 'data':
|
||||
yield token
|
||||
continue
|
||||
|
||||
|
@ -42,8 +42,8 @@ from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
|
||||
MemcachedBytecodeCache
|
||||
|
||||
# undefined types
|
||||
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
|
||||
make_logging_undefined
|
||||
from jinja2.runtime import Undefined, ChainableUndefined, DebugUndefined, \
|
||||
StrictUndefined, make_logging_undefined
|
||||
|
||||
# exceptions
|
||||
from jinja2.exceptions import TemplateError, UndefinedError, \
|
||||
|
@ -3,9 +3,9 @@
|
||||
jinja2.bccache
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements the bytecode cache system Jinja is optionally
|
||||
using. This is useful if you have very complex template situations and
|
||||
the compiliation of all those templates slow down your application too
|
||||
This module implements the bytecode cache system that Jinja optionally uses.
|
||||
This is useful if you have very complex template situations and
|
||||
the compilation of all those templates slows down your application too
|
||||
much.
|
||||
|
||||
Situations where this is useful are often forking web applications that
|
||||
|
@ -65,12 +65,7 @@ def make_attrgetter(environment, attribute, postprocess=None, default=None):
|
||||
to access attributes of attributes. Integer parts in paths are
|
||||
looked up as integers.
|
||||
"""
|
||||
if attribute is None:
|
||||
attribute = []
|
||||
elif isinstance(attribute, string_types):
|
||||
attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')]
|
||||
else:
|
||||
attribute = [attribute]
|
||||
attribute = _prepare_attribute_parts(attribute)
|
||||
|
||||
def attrgetter(item):
|
||||
for part in attribute:
|
||||
@ -78,7 +73,6 @@ def make_attrgetter(environment, attribute, postprocess=None, default=None):
|
||||
|
||||
if default and isinstance(item, Undefined):
|
||||
item = default
|
||||
break
|
||||
|
||||
if postprocess is not None:
|
||||
item = postprocess(item)
|
||||
@ -88,6 +82,45 @@ def make_attrgetter(environment, attribute, postprocess=None, default=None):
|
||||
return attrgetter
|
||||
|
||||
|
||||
def make_multi_attrgetter(environment, attribute, postprocess=None):
|
||||
"""Returns a callable that looks up the given comma separated
|
||||
attributes from a passed object with the rules of the environment.
|
||||
Dots are allowed to access attributes of each attribute. Integer
|
||||
parts in paths are looked up as integers.
|
||||
|
||||
The value returned by the returned callable is a list of extracted
|
||||
attribute values.
|
||||
|
||||
Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
|
||||
"""
|
||||
attribute_parts = attribute.split(',') if isinstance(attribute, string_types) else [attribute]
|
||||
attribute = [_prepare_attribute_parts(attribute_part) for attribute_part in attribute_parts]
|
||||
|
||||
def attrgetter(item):
|
||||
items = [None] * len(attribute)
|
||||
for i, attribute_part in enumerate(attribute):
|
||||
item_i = item
|
||||
for part in attribute_part:
|
||||
item_i = environment.getitem(item_i, part)
|
||||
|
||||
if postprocess is not None:
|
||||
item_i = postprocess(item_i)
|
||||
|
||||
items[i] = item_i
|
||||
return items
|
||||
|
||||
return attrgetter
|
||||
|
||||
|
||||
def _prepare_attribute_parts(attr):
|
||||
if attr is None:
|
||||
return []
|
||||
elif isinstance(attr, string_types):
|
||||
return [int(x) if x.isdigit() else x for x in attr.split('.')]
|
||||
else:
|
||||
return [attr]
|
||||
|
||||
|
||||
def do_forceescape(value):
|
||||
"""Enforce HTML escaping. This will probably double escape variables."""
|
||||
if hasattr(value, '__html__'):
|
||||
@ -274,8 +307,10 @@ def do_sort(
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
The `attribute` parameter was added.
|
||||
The attribute parameter can contain multiple comma separated
|
||||
attributes, e.g. attr1,attr2.
|
||||
"""
|
||||
key_func = make_attrgetter(
|
||||
key_func = make_multi_attrgetter(
|
||||
environment, attribute,
|
||||
postprocess=ignore_case if not case_sensitive else None
|
||||
)
|
||||
@ -284,11 +319,11 @@ def do_sort(
|
||||
|
||||
@environmentfilter
|
||||
def do_unique(environment, value, case_sensitive=False, attribute=None):
|
||||
"""Returns a list of unique items from the the given iterable.
|
||||
"""Returns a list of unique items from the given iterable.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique }}
|
||||
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }}
|
||||
-> ['foo', 'bar', 'foobar']
|
||||
|
||||
The unique items are yielded in the same order as their first occurrence in
|
||||
@ -320,8 +355,9 @@ def _min_or_max(environment, value, func, case_sensitive, attribute):
|
||||
return environment.undefined('No aggregated item, sequence was empty.')
|
||||
|
||||
key_func = make_attrgetter(
|
||||
environment, attribute,
|
||||
ignore_case if not case_sensitive else None
|
||||
environment,
|
||||
attribute,
|
||||
postprocess=ignore_case if not case_sensitive else None
|
||||
)
|
||||
return func(chain([first], it), key=key_func)
|
||||
|
||||
@ -372,6 +408,12 @@ def do_default(value, default_value=u'', boolean=False):
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ ''|default('the string was empty', true) }}
|
||||
|
||||
.. versionchanged:: 2.11
|
||||
It's now possible to configure the :class:`~jinja2.Environment` with
|
||||
:class:`~jinja2.ChainableUndefined` to make the `default` filter work
|
||||
on nested elements and attributes that may contain undefined values
|
||||
in the chain without getting an :exc:`~jinja2.UndefinedError`.
|
||||
"""
|
||||
if isinstance(value, Undefined) or (boolean and not value):
|
||||
return default_value
|
||||
@ -723,7 +765,7 @@ def do_slice(value, slices, fill_with=None):
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<div class="columwrapper">
|
||||
<div class="columnwrapper">
|
||||
{%- for column in items|slice(3) %}
|
||||
<ul class="column-{{ loop.index }}">
|
||||
{%- for item in column %}
|
||||
@ -965,12 +1007,12 @@ def do_map(*args, **kwargs):
|
||||
|
||||
Users on this page: {{ users|map(attribute='username')|join(', ') }}
|
||||
|
||||
If the list of objects may not contain the given attribute, a default
|
||||
value may be provided.
|
||||
You can specify a ``default`` value to use if an object in the list
|
||||
does not have the given attribute.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ users|map(attribute='username', default='Anonymous')|join(', ') }}
|
||||
{{ users|map(attribute="username", default="Anonymous")|join(", ") }}
|
||||
|
||||
Alternatively you can let it invoke a filter by passing the name of the
|
||||
filter and the arguments afterwards. A good example would be applying a
|
||||
@ -980,6 +1022,9 @@ def do_map(*args, **kwargs):
|
||||
|
||||
Users on this page: {{ titles|map('lower')|join(', ') }}
|
||||
|
||||
.. versionchanged:: 2.11.0
|
||||
Added the ``default`` parameter.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
"""
|
||||
seq, func = prepare_map(args, kwargs)
|
||||
@ -1106,6 +1151,7 @@ def do_tojson(eval_ctx, value, indent=None):
|
||||
def prepare_map(args, kwargs):
|
||||
context = args[0]
|
||||
seq = args[1]
|
||||
default = None
|
||||
|
||||
if len(args) == 2 and 'attribute' in kwargs:
|
||||
attribute = kwargs.pop('attribute')
|
||||
|
@ -445,22 +445,21 @@ class Lexer(object):
|
||||
|
||||
# strip leading spaces if lstrip_blocks is enabled
|
||||
prefix_re = {}
|
||||
no_lstrip_re = e('+')
|
||||
# detect overlap between block and variable or comment strings
|
||||
block_diff = c(r'^%s(.*)' % e(environment.block_start_string))
|
||||
# make sure we don't mistake a block for a variable or a comment
|
||||
m = block_diff.match(environment.comment_start_string)
|
||||
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||
m = block_diff.match(environment.variable_start_string)
|
||||
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||
# detect overlap between comment and variable strings
|
||||
comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string))
|
||||
m = comment_diff.match(environment.variable_start_string)
|
||||
no_variable_re = m and r'(?!%s)' % e(m.group(1)) or ''
|
||||
|
||||
if environment.lstrip_blocks:
|
||||
# use '{%+' to manually disable lstrip_blocks behavior
|
||||
no_lstrip_re = e('+')
|
||||
# detect overlap between block and variable or comment strings
|
||||
block_diff = c(r'^%s(.*)' % e(environment.block_start_string))
|
||||
# make sure we don't mistake a block for a variable or a comment
|
||||
m = block_diff.match(environment.comment_start_string)
|
||||
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||
m = block_diff.match(environment.variable_start_string)
|
||||
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||
|
||||
# detect overlap between comment and variable strings
|
||||
comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string))
|
||||
m = comment_diff.match(environment.variable_start_string)
|
||||
no_variable_re = m and r'(?!%s)' % e(m.group(1)) or ''
|
||||
|
||||
lstrip_re = r'^[ \t]*'
|
||||
block_prefix_re = r'%s%s(?!%s)|%s\+?' % (
|
||||
lstrip_re,
|
||||
@ -474,10 +473,21 @@ class Lexer(object):
|
||||
no_variable_re,
|
||||
e(environment.comment_start_string),
|
||||
)
|
||||
prefix_re['block'] = block_prefix_re
|
||||
prefix_re['comment'] = comment_prefix_re
|
||||
else:
|
||||
block_prefix_re = '%s' % e(environment.block_start_string)
|
||||
# If lstrip_blocks is False, then '{%+' is allowed but a NOP
|
||||
block_prefix_re = r'%s(?!%s)|%s\+?' % (
|
||||
e(environment.block_start_string),
|
||||
no_lstrip_re,
|
||||
e(environment.block_start_string),
|
||||
)
|
||||
comment_prefix_re = r'%s%s|%s\+?' % (
|
||||
e(environment.comment_start_string),
|
||||
no_variable_re,
|
||||
e(environment.comment_start_string),
|
||||
)
|
||||
|
||||
prefix_re['block'] = block_prefix_re
|
||||
prefix_re['comment'] = comment_prefix_re
|
||||
|
||||
self.newline_sequence = environment.newline_sequence
|
||||
self.keep_trailing_newline = environment.keep_trailing_newline
|
||||
|
@ -841,7 +841,9 @@ class Parser(object):
|
||||
'name:and')):
|
||||
if self.stream.current.test('name:is'):
|
||||
self.fail('You cannot chain multiple tests with is')
|
||||
args = [self.parse_primary()]
|
||||
arg_node = self.parse_primary()
|
||||
arg_node = self.parse_postfix(arg_node)
|
||||
args = [arg_node]
|
||||
else:
|
||||
args = []
|
||||
node = nodes.Test(node, name, args, kwargs, dyn_args,
|
||||
|
@ -401,7 +401,7 @@ class LoopContextBase(object):
|
||||
return self._recurse(iterable, self._recurse, self.depth0 + 1)
|
||||
|
||||
# a nifty trick to enhance the error message if someone tried to call
|
||||
# the the loop without or with too many arguments.
|
||||
# the loop without or with too many arguments.
|
||||
__call__ = loop
|
||||
del loop
|
||||
|
||||
@ -510,7 +510,7 @@ class Macro(object):
|
||||
# check here.
|
||||
#
|
||||
# This is considered safe because an eval context is not a valid
|
||||
# argument to callables otherwise anwyays. Worst case here is
|
||||
# argument to callables otherwise anyway. Worst case here is
|
||||
# that if no eval context is passed we fall back to the compile
|
||||
# time autoescape flag.
|
||||
if args and isinstance(args[0], EvalContext):
|
||||
@ -586,7 +586,7 @@ class Macro(object):
|
||||
@implements_to_string
|
||||
class Undefined(object):
|
||||
"""The default undefined type. This undefined type can be printed and
|
||||
iterated over, but every other access will raise an :exc:`jinja2.exceptions.UndefinedError`:
|
||||
iterated over, but every other access will raise an :exc:`UndefinedError`:
|
||||
|
||||
>>> foo = Undefined(name='foo')
|
||||
>>> str(foo)
|
||||
@ -610,7 +610,7 @@ class Undefined(object):
|
||||
@internalcode
|
||||
def _fail_with_undefined_error(self, *args, **kwargs):
|
||||
"""Regular callback function for undefined objects that raises an
|
||||
`jinja2.exceptions.UndefinedError` on call.
|
||||
`UndefinedError` on call.
|
||||
"""
|
||||
if self._undefined_hint is None:
|
||||
if self._undefined_obj is missing:
|
||||
@ -750,6 +750,32 @@ def make_logging_undefined(logger=None, base=None):
|
||||
return LoggingUndefined
|
||||
|
||||
|
||||
# No @implements_to_string decorator here because __str__
|
||||
# is not overwritten from Undefined in this class.
|
||||
# This would cause a recursion error in Python 2.
|
||||
class ChainableUndefined(Undefined):
|
||||
"""An undefined that is chainable, where both
|
||||
__getattr__ and __getitem__ return itself rather than
|
||||
raising an :exc:`UndefinedError`:
|
||||
|
||||
>>> foo = ChainableUndefined(name='foo')
|
||||
>>> str(foo.bar['baz'])
|
||||
''
|
||||
>>> foo.bar['baz'] + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __getattr__(self, _):
|
||||
return self
|
||||
|
||||
__getitem__ = __getattr__
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class DebugUndefined(Undefined):
|
||||
"""An undefined that returns the debug info when printed.
|
||||
@ -805,4 +831,5 @@ class StrictUndefined(Undefined):
|
||||
|
||||
# remove remaining slots attributes, after the metaclass did the magic they
|
||||
# are unneeded and irritating as they contain wrong data for the subclasses.
|
||||
del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__
|
||||
del Undefined.__slots__, ChainableUndefined.__slots__, \
|
||||
DebugUndefined.__slots__, StrictUndefined.__slots__
|
||||
|
@ -11,6 +11,7 @@
|
||||
import re
|
||||
import json
|
||||
import errno
|
||||
import warnings
|
||||
from collections import deque
|
||||
from threading import Lock
|
||||
from jinja2._compat import text_type, string_types, implements_iterator, \
|
||||
@ -456,6 +457,14 @@ class LRUCache(object):
|
||||
return [x[1] for x in self.items()]
|
||||
|
||||
def itervalue(self):
|
||||
"""Iterate over all values."""
|
||||
warnings.warn(DeprecationWarning(
|
||||
'"itervalue()" is deprecated and will be removed in version 2.12.'
|
||||
+ ' Use "itervalues()" instead.'
|
||||
), stacklevel=2)
|
||||
return self.itervalues()
|
||||
|
||||
def itervalues(self):
|
||||
"""Iterate over all values."""
|
||||
return iter(self.values())
|
||||
|
||||
|
19
setup.cfg
19
setup.cfg
@ -1,9 +1,20 @@
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE
|
||||
|
||||
[bdist_wheel]
|
||||
universal = true
|
||||
|
||||
[tool:pytest]
|
||||
minversion = 3.0
|
||||
testpaths = tests
|
||||
|
||||
[coverage:run]
|
||||
branch = True
|
||||
source =
|
||||
jinja2
|
||||
tests
|
||||
|
||||
[coverage:paths]
|
||||
source =
|
||||
src/jinja2
|
||||
.tox/*/lib/python*/site-packages/jinja2
|
||||
.tox/*/site-packages/jinja2
|
||||
|
2
setup.py
2
setup.py
@ -42,7 +42,7 @@ setup(
|
||||
name='Jinja2',
|
||||
version='2.11.dev',
|
||||
url='http://jinja.pocoo.org/',
|
||||
license='BSD',
|
||||
license='BSD-3-Clause',
|
||||
author='Armin Ronacher',
|
||||
author_email='armin.ronacher@active-4.com',
|
||||
description='A small but fast and easy to use stand-alone template '
|
||||
|
@ -16,12 +16,54 @@ from jinja2.utils import have_async_gen
|
||||
from jinja2 import Environment
|
||||
|
||||
|
||||
def pytest_ignore_collect(path, config):
|
||||
def pytest_ignore_collect(path):
|
||||
if 'async' in path.basename and not have_async_gen:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
'''Register custom marks for test categories.'''
|
||||
custom_markers = [
|
||||
'api',
|
||||
'byte_code_cache',
|
||||
'core_tags',
|
||||
'debug',
|
||||
'escapeUrlizeTarget',
|
||||
'ext',
|
||||
'extended',
|
||||
'filter',
|
||||
'for_loop',
|
||||
'helpers',
|
||||
'if_condition',
|
||||
'imports',
|
||||
'includes',
|
||||
'inheritance',
|
||||
'lexer',
|
||||
'lexnparse',
|
||||
'loaders',
|
||||
'lowlevel',
|
||||
'lrucache',
|
||||
'lstripblocks',
|
||||
'macros',
|
||||
'meta',
|
||||
'moduleloader',
|
||||
'parser',
|
||||
'regression',
|
||||
'sandbox',
|
||||
'set',
|
||||
'streaming',
|
||||
'syntax',
|
||||
'test_tests',
|
||||
'tokenstream',
|
||||
'undefined',
|
||||
'utils',
|
||||
'with_',
|
||||
]
|
||||
for mark in custom_markers:
|
||||
config.addinivalue_line('markers', mark + ': test category')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def env():
|
||||
'''returns a new environment.
|
||||
|
@ -13,8 +13,8 @@ import tempfile
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
from jinja2 import Environment, Undefined, DebugUndefined, \
|
||||
StrictUndefined, UndefinedError, meta, \
|
||||
from jinja2 import Environment, Undefined, ChainableUndefined, \
|
||||
DebugUndefined, StrictUndefined, UndefinedError, meta, \
|
||||
is_undefined, Template, DictLoader, make_logging_undefined
|
||||
from jinja2.compiler import CodeGenerator
|
||||
from jinja2.runtime import Context
|
||||
@ -258,6 +258,27 @@ class TestUndefined(object):
|
||||
pytest.raises(UndefinedError,
|
||||
env.from_string('{{ missing - 1}}').render)
|
||||
|
||||
def test_chainable_undefined(self):
|
||||
env = Environment(undefined=ChainableUndefined)
|
||||
# The following tests are copied from test_default_undefined
|
||||
assert env.from_string('{{ missing }}').render() == u''
|
||||
assert env.from_string('{{ missing|list }}').render() == '[]'
|
||||
assert env.from_string('{{ missing is not defined }}').render() \
|
||||
== 'True'
|
||||
assert env.from_string('{{ foo.missing }}').render(foo=42) == ''
|
||||
assert env.from_string('{{ not missing }}').render() == 'True'
|
||||
pytest.raises(UndefinedError,
|
||||
env.from_string('{{ missing - 1}}').render)
|
||||
|
||||
# The following tests ensure subclass functionality works as expected
|
||||
assert env.from_string('{{ missing.bar["baz"] }}').render() == u''
|
||||
assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() \
|
||||
== u'foo'
|
||||
assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(
|
||||
foo=42) == u'bar'
|
||||
assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(
|
||||
foo={'bar': 42}) == u'baz'
|
||||
|
||||
def test_debug_undefined(self):
|
||||
env = Environment(undefined=DebugUndefined)
|
||||
assert env.from_string('{{ missing }}').render() == '{{ missing }}'
|
||||
|
@ -23,6 +23,16 @@ class Magic(object):
|
||||
return text_type(self.value)
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class Magic2(object):
|
||||
def __init__(self, value1, value2):
|
||||
self.value1 = value1
|
||||
self.value2 = value2
|
||||
|
||||
def __str__(self):
|
||||
return u'(%s,%s)' % (text_type(self.value1), text_type(self.value2))
|
||||
|
||||
|
||||
@pytest.mark.filter
|
||||
class TestFilter(object):
|
||||
|
||||
@ -417,6 +427,29 @@ class TestFilter(object):
|
||||
tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
|
||||
assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
|
||||
|
||||
def test_sort5(self, env):
|
||||
tmpl = env.from_string('''{{ items|sort(attribute='value.0')|join }}''')
|
||||
assert tmpl.render(items=map(Magic, [[3], [2], [4], [1]])) == '[1][2][3][4]'
|
||||
|
||||
def test_sort6(self, env):
|
||||
tmpl = env.from_string('''{{ items|sort(attribute='value1,value2')|join }}''')
|
||||
assert (tmpl.render(items=map(
|
||||
lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]))
|
||||
== '(2,1)(2,2)(2,5)(3,1)')
|
||||
|
||||
def test_sort7(self, env):
|
||||
tmpl = env.from_string('''{{ items|sort(attribute='value2,value1')|join }}''')
|
||||
assert (tmpl.render(items=map(lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)])) ==
|
||||
'(2,1)(3,1)(2,2)(2,5)')
|
||||
|
||||
def test_sort8(self, env):
|
||||
tmpl = env.from_string(
|
||||
'''{{ items|sort(attribute='value1.0,value2.0')|join }}''')
|
||||
assert (tmpl.render(items=map(
|
||||
lambda x: Magic2(x[0], x[1]),
|
||||
[([3], [1]), ([2], [2]), ([2], [1]), ([2], [5])]))
|
||||
== '([2],[1])([2],[2])([2],[5])([3],[1])')
|
||||
|
||||
def test_unique(self, env):
|
||||
t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}')
|
||||
assert t.render() == "bA"
|
||||
@ -561,27 +594,33 @@ class TestFilter(object):
|
||||
tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
|
||||
assert tmpl.render(users=users) == 'john|jane|mike'
|
||||
|
||||
def test_attribute_map_default(self, env):
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
class NotUser(object):
|
||||
def __init__(self, not_name):
|
||||
self.not_name = not_name
|
||||
env = Environment()
|
||||
users = [
|
||||
User('john'),
|
||||
User('jane'),
|
||||
NotUser('plant'),
|
||||
]
|
||||
tmpl = env.from_string('{{ users|map(attribute="name", default="anonymous")|join("|") }}')
|
||||
assert tmpl.render(users=users) == 'john|jane|anonymous'
|
||||
|
||||
def test_empty_map(self, env):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ none|map("upper")|list }}')
|
||||
assert tmpl.render() == '[]'
|
||||
|
||||
def test_map_default(self, env):
|
||||
class Fullname(object):
|
||||
def __init__(self, firstname, lastname):
|
||||
self.firstname = firstname
|
||||
self.lastname = lastname
|
||||
|
||||
class Firstname(object):
|
||||
def __init__(self, firstname):
|
||||
self.firstname = firstname
|
||||
|
||||
env = Environment()
|
||||
tmpl = env.from_string(
|
||||
'{{ users|map(attribute="lastname", default="smith")|join(", ") }}'
|
||||
)
|
||||
users = [
|
||||
Fullname("john", "lennon"),
|
||||
Fullname("jane", "edwards"),
|
||||
Fullname("jon", None),
|
||||
Firstname("mike")
|
||||
]
|
||||
assert tmpl.render(users=users) == "lennon, edwards, None, smith"
|
||||
|
||||
def test_simple_select(self, env):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')
|
||||
|
@ -139,8 +139,6 @@ class TestIncludes(object):
|
||||
test_includes(t, x='missing')
|
||||
t = test_env.from_string('{% include x %}')
|
||||
test_includes(t, x='header')
|
||||
t = test_env.from_string('{% include x %}')
|
||||
test_includes(t, x='header')
|
||||
t = test_env.from_string('{% include [x] %}')
|
||||
test_includes(t, x='header')
|
||||
|
||||
|
@ -503,6 +503,14 @@ class TestLstripBlocks(object):
|
||||
tmpl = env.from_string(''' {%+ if True %}\n {%+ endif %}''')
|
||||
assert tmpl.render() == " \n "
|
||||
|
||||
def test_lstrip_blocks_false_with_no_lstrip(self, env):
|
||||
# Test that + is a NOP (but does not cause an error) if lstrip_blocks=False
|
||||
env = Environment(lstrip_blocks=False, trim_blocks=False)
|
||||
tmpl = env.from_string(''' {% if True %}\n {% endif %}''')
|
||||
assert tmpl.render() == " \n "
|
||||
tmpl = env.from_string(''' {%+ if True %}\n {%+ endif %}''')
|
||||
assert tmpl.render() == " \n "
|
||||
|
||||
def test_lstrip_endline(self, env):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(
|
||||
|
@ -483,6 +483,10 @@ class TestBug(object):
|
||||
t = env.from_string('{% if foo %}{% else %}42{% endif %}')
|
||||
assert t.render(foo=False) == '42'
|
||||
|
||||
def test_subproperty_if(self, env):
|
||||
t = env.from_string('{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}')
|
||||
assert t.render(object1={'subproperty1': 'value'}, object2={'subproperty2': 'value'}) == '42'
|
||||
|
||||
def test_set_and_include(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'inc': 'bar',
|
||||
|
@ -32,6 +32,27 @@ class TestLRUCache(object):
|
||||
assert len(d) == 3
|
||||
assert 'a' in d and 'c' in d and 'd' in d and 'b' not in d
|
||||
|
||||
def test_itervalue_deprecated(self):
|
||||
cache = LRUCache(3)
|
||||
cache["a"] = 1
|
||||
cache["b"] = 2
|
||||
with pytest.deprecated_call():
|
||||
cache.itervalue()
|
||||
|
||||
def test_itervalues(self):
|
||||
cache = LRUCache(3)
|
||||
cache["b"] = 1
|
||||
cache["a"] = 2
|
||||
values = [v for v in cache.itervalues()]
|
||||
assert len(values) == 2
|
||||
assert 1 in values
|
||||
assert 2 in values
|
||||
|
||||
def test_itervalues_empty(self):
|
||||
cache = LRUCache(2)
|
||||
values = [v for v in cache.itervalues()]
|
||||
assert len(values) == 0
|
||||
|
||||
def test_pickleable(self):
|
||||
cache = LRUCache(2)
|
||||
cache["foo"] = 42
|
||||
|
36
tox.ini
36
tox.ini
@ -1,39 +1,35 @@
|
||||
[tox]
|
||||
envlist =
|
||||
py{37,36,35,34,27,py3,py}
|
||||
py{37,36,35,27,py3,py}
|
||||
docs-html
|
||||
coverage-report
|
||||
coverage
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
passenv = LANG
|
||||
usedevelop = true
|
||||
deps =
|
||||
pytest>=3,<3.7
|
||||
coverage
|
||||
|
||||
commands = coverage run -p -m pytest {posargs}
|
||||
pytest
|
||||
commands = coverage run -p -m pytest --tb=short -Werror --basetemp={envtmpdir} {posargs}
|
||||
|
||||
[testenv:docs-html]
|
||||
deps = sphinx
|
||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
|
||||
deps =
|
||||
Sphinx
|
||||
Pallets-Sphinx-Themes
|
||||
sphinx-issues
|
||||
commands = sphinx-build -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
|
||||
|
||||
[testenv:docs-linkcheck]
|
||||
deps = sphinx
|
||||
commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/linkcheck
|
||||
|
||||
[testenv:coverage-report]
|
||||
[testenv:coverage]
|
||||
deps = coverage
|
||||
skip_install = true
|
||||
commands =
|
||||
coverage combine
|
||||
coverage report
|
||||
coverage html
|
||||
coverage report
|
||||
|
||||
[testenv:codecov]
|
||||
passenv = CI TRAVIS TRAVIS_*
|
||||
deps = codecov
|
||||
[testenv:coverage-ci]
|
||||
deps = coverage
|
||||
skip_install = true
|
||||
commands =
|
||||
coverage combine
|
||||
coverage report
|
||||
codecov
|
||||
# Ignoring errors because 2.7.15 and 3.5.5 on Azure can't parse async files.
|
||||
coverage xml --ignore-errors
|
||||
|
Loading…
Reference in New Issue
Block a user