diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /contrib/python/Jinja2/py3 | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/Jinja2/py3')
60 files changed, 13658 insertions, 13658 deletions
diff --git a/contrib/python/Jinja2/py3/.dist-info/METADATA b/contrib/python/Jinja2/py3/.dist-info/METADATA index 3b9355ac05..ed74e23b6d 100644 --- a/contrib/python/Jinja2/py3/.dist-info/METADATA +++ b/contrib/python/Jinja2/py3/.dist-info/METADATA @@ -1,113 +1,113 @@ -Metadata-Version: 2.1 -Name: Jinja2 +Metadata-Version: 2.1 +Name: Jinja2 Version: 3.0.3 -Summary: A very fast and expressive template engine. -Home-page: https://palletsprojects.com/p/jinja/ -Author: Armin Ronacher -Author-email: armin.ronacher@active-4.com -Maintainer: Pallets -Maintainer-email: contact@palletsprojects.com -License: BSD-3-Clause -Project-URL: Donate, https://palletsprojects.com/donate -Project-URL: Documentation, https://jinja.palletsprojects.com/ -Project-URL: Changes, https://jinja.palletsprojects.com/changes/ -Project-URL: Source Code, https://github.com/pallets/jinja/ -Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/ -Project-URL: Twitter, https://twitter.com/PalletsTeam -Project-URL: Chat, https://discord.gg/pallets -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -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 -Description-Content-Type: text/x-rst -License-File: LICENSE.rst -Requires-Dist: MarkupSafe (>=2.0) -Provides-Extra: i18n -Requires-Dist: Babel (>=2.7) ; extra == 'i18n' - -Jinja -===== - -Jinja is a fast, expressive, extensible templating engine. Special -placeholders in the template allow writing code similar to Python -syntax. Then the template is passed data to render the final document. - -It includes: - -- Template inheritance and inclusion. -- Define and import macros within templates. -- HTML templates can use autoescaping to prevent XSS from untrusted - user input. -- A sandboxed environment can safely render untrusted templates. -- AsyncIO support for generating templates and calling async - functions. -- I18N support with Babel. -- Templates are compiled to optimized Python code just-in-time and - cached, or can be compiled ahead-of-time. -- Exceptions point to the correct line in templates to make debugging - easier. -- Extensible filters, tests, functions, and even syntax. - -Jinja's philosophy is that while application logic belongs in Python if -possible, it shouldn't make the template designer's job difficult by -restricting functionality too much. - - -Installing ----------- - -Install and update using `pip`_: - -.. code-block:: text - - $ pip install -U Jinja2 - -.. _pip: https://pip.pypa.io/en/stable/getting-started/ - - -In A Nutshell -------------- - -.. code-block:: jinja - - {% extends "base.html" %} - {% block title %}Members{% endblock %} - {% block content %} - <ul> - {% for user in users %} - <li><a href="{{ user.url }}">{{ user.username }}</a></li> - {% endfor %} - </ul> - {% endblock %} - - -Donate ------- - -The Pallets organization develops and supports Jinja and other popular -packages. In order to grow the community of contributors and users, and -allow the maintainers to devote more time to the projects, `please -donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - - -Links ------ - -- Documentation: https://jinja.palletsprojects.com/ -- Changes: https://jinja.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/Jinja2/ -- Source Code: https://github.com/pallets/jinja/ -- Issue Tracker: https://github.com/pallets/jinja/issues/ -- Website: https://palletsprojects.com/p/jinja/ -- Twitter: https://twitter.com/PalletsTeam -- Chat: https://discord.gg/pallets - - +Summary: A very fast and expressive template engine. +Home-page: https://palletsprojects.com/p/jinja/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://jinja.palletsprojects.com/ +Project-URL: Changes, https://jinja.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/jinja/ +Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +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 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: MarkupSafe (>=2.0) +Provides-Extra: i18n +Requires-Dist: Babel (>=2.7) ; extra == 'i18n' + +Jinja +===== + +Jinja is a fast, expressive, extensible templating engine. Special +placeholders in the template allow writing code similar to Python +syntax. Then the template is passed data to render the final document. + +It includes: + +- Template inheritance and inclusion. +- Define and import macros within templates. +- HTML templates can use autoescaping to prevent XSS from untrusted + user input. +- A sandboxed environment can safely render untrusted templates. +- AsyncIO support for generating templates and calling async + functions. +- I18N support with Babel. +- Templates are compiled to optimized Python code just-in-time and + cached, or can be compiled ahead-of-time. +- Exceptions point to the correct line in templates to make debugging + easier. +- Extensible filters, tests, functions, and even syntax. + +Jinja's philosophy is that while application logic belongs in Python if +possible, it shouldn't make the template designer's job difficult by +restricting functionality too much. + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U Jinja2 + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +In A Nutshell +------------- + +.. code-block:: jinja + + {% extends "base.html" %} + {% block title %}Members{% endblock %} + {% block content %} + <ul> + {% for user in users %} + <li><a href="{{ user.url }}">{{ user.username }}</a></li> + {% endfor %} + </ul> + {% endblock %} + + +Donate +------ + +The Pallets organization develops and supports Jinja and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://jinja.palletsprojects.com/ +- Changes: https://jinja.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Jinja2/ +- Source Code: https://github.com/pallets/jinja/ +- Issue Tracker: https://github.com/pallets/jinja/issues/ +- Website: https://palletsprojects.com/p/jinja/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/contrib/python/Jinja2/py3/.dist-info/entry_points.txt b/contrib/python/Jinja2/py3/.dist-info/entry_points.txt index 3619483fd4..b2df602f20 100644 --- a/contrib/python/Jinja2/py3/.dist-info/entry_points.txt +++ b/contrib/python/Jinja2/py3/.dist-info/entry_points.txt @@ -1,3 +1,3 @@ -[babel.extractors] -jinja2 = jinja2.ext:babel_extract [i18n] - +[babel.extractors] +jinja2 = jinja2.ext:babel_extract [i18n] + diff --git a/contrib/python/Jinja2/py3/.dist-info/top_level.txt b/contrib/python/Jinja2/py3/.dist-info/top_level.txt index 7f7afbf3bf..a73547a38b 100644 --- a/contrib/python/Jinja2/py3/.dist-info/top_level.txt +++ b/contrib/python/Jinja2/py3/.dist-info/top_level.txt @@ -1 +1 @@ -jinja2 +jinja2 diff --git a/contrib/python/Jinja2/py3/.yandex_meta/yamaker.yaml b/contrib/python/Jinja2/py3/.yandex_meta/yamaker.yaml index 8ba9e31be6..c41f8044f2 100644 --- a/contrib/python/Jinja2/py3/.yandex_meta/yamaker.yaml +++ b/contrib/python/Jinja2/py3/.yandex_meta/yamaker.yaml @@ -1,4 +1,4 @@ -exclude: -- examples/basic/test.py -mark_as_sources: -- jinja2/tests.py +exclude: +- examples/basic/test.py +mark_as_sources: +- jinja2/tests.py diff --git a/contrib/python/Jinja2/py3/LICENSE.rst b/contrib/python/Jinja2/py3/LICENSE.rst index c37cae49ec..32f5805772 100644 --- a/contrib/python/Jinja2/py3/LICENSE.rst +++ b/contrib/python/Jinja2/py3/LICENSE.rst @@ -1,28 +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. +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. diff --git a/contrib/python/Jinja2/py3/README.rst b/contrib/python/Jinja2/py3/README.rst index a197aea647..60a786f1b7 100644 --- a/contrib/python/Jinja2/py3/README.rst +++ b/contrib/python/Jinja2/py3/README.rst @@ -1,50 +1,50 @@ -Jinja -===== - -Jinja is a fast, expressive, extensible templating engine. Special -placeholders in the template allow writing code similar to Python -syntax. Then the template is passed data to render the final document. - -It includes: - -- Template inheritance and inclusion. -- Define and import macros within templates. -- HTML templates can use autoescaping to prevent XSS from untrusted - user input. -- A sandboxed environment can safely render untrusted templates. -- AsyncIO support for generating templates and calling async - functions. -- I18N support with Babel. -- Templates are compiled to optimized Python code just-in-time and - cached, or can be compiled ahead-of-time. -- Exceptions point to the correct line in templates to make debugging - easier. -- Extensible filters, tests, functions, and even syntax. - -Jinja's philosophy is that while application logic belongs in Python if -possible, it shouldn't make the template designer's job difficult by -restricting functionality too much. - - -Installing ----------- - -Install and update using `pip`_: - -.. code-block:: text - - $ pip install -U Jinja2 - -.. _pip: https://pip.pypa.io/en/stable/getting-started/ - - -In A Nutshell -------------- - +Jinja +===== + +Jinja is a fast, expressive, extensible templating engine. Special +placeholders in the template allow writing code similar to Python +syntax. Then the template is passed data to render the final document. + +It includes: + +- Template inheritance and inclusion. +- Define and import macros within templates. +- HTML templates can use autoescaping to prevent XSS from untrusted + user input. +- A sandboxed environment can safely render untrusted templates. +- AsyncIO support for generating templates and calling async + functions. +- I18N support with Babel. +- Templates are compiled to optimized Python code just-in-time and + cached, or can be compiled ahead-of-time. +- Exceptions point to the correct line in templates to make debugging + easier. +- Extensible filters, tests, functions, and even syntax. + +Jinja's philosophy is that while application logic belongs in Python if +possible, it shouldn't make the template designer's job difficult by +restricting functionality too much. + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U Jinja2 + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +In A Nutshell +------------- + .. code-block:: jinja - {% extends "base.html" %} - {% block title %}Members{% endblock %} + {% extends "base.html" %} + {% block title %}Members{% endblock %} {% block content %} <ul> {% for user in users %} @@ -54,25 +54,25 @@ In A Nutshell {% endblock %} -Donate ------- - -The Pallets organization develops and supports Jinja and other popular -packages. In order to grow the community of contributors and users, and -allow the maintainers to devote more time to the projects, `please -donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - - -Links ------ - -- Documentation: https://jinja.palletsprojects.com/ -- Changes: https://jinja.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/Jinja2/ -- Source Code: https://github.com/pallets/jinja/ -- Issue Tracker: https://github.com/pallets/jinja/issues/ -- Website: https://palletsprojects.com/p/jinja/ -- Twitter: https://twitter.com/PalletsTeam -- Chat: https://discord.gg/pallets +Donate +------ + +The Pallets organization develops and supports Jinja and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://jinja.palletsprojects.com/ +- Changes: https://jinja.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Jinja2/ +- Source Code: https://github.com/pallets/jinja/ +- Issue Tracker: https://github.com/pallets/jinja/issues/ +- Website: https://palletsprojects.com/p/jinja/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets diff --git a/contrib/python/Jinja2/py3/jinja2/__init__.py b/contrib/python/Jinja2/py3/jinja2/__init__.py index 3d5a8600da..0a4803e111 100644 --- a/contrib/python/Jinja2/py3/jinja2/__init__.py +++ b/contrib/python/Jinja2/py3/jinja2/__init__.py @@ -1,46 +1,46 @@ -"""Jinja is a template engine written in pure Python. It provides a -non-XML syntax that supports inline expressions and an optional -sandboxed environment. +"""Jinja is a template engine written in pure Python. It provides a +non-XML syntax that supports inline expressions and an optional +sandboxed environment. """ -from .bccache import BytecodeCache as BytecodeCache -from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache -from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache -from .environment import Environment as Environment -from .environment import Template as Template -from .exceptions import TemplateAssertionError as TemplateAssertionError -from .exceptions import TemplateError as TemplateError -from .exceptions import TemplateNotFound as TemplateNotFound -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 -from .loaders import FileSystemLoader as FileSystemLoader -from .loaders import FunctionLoader as FunctionLoader -from .loaders import ModuleLoader as ModuleLoader -from .loaders import PackageLoader as PackageLoader -from .loaders import PrefixLoader as PrefixLoader -from .loaders import ResourceLoader as ResourceLoader -from .runtime import ChainableUndefined as ChainableUndefined -from .runtime import DebugUndefined as DebugUndefined -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 +from .bccache import BytecodeCache as BytecodeCache +from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache +from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache +from .environment import Environment as Environment +from .environment import Template as Template +from .exceptions import TemplateAssertionError as TemplateAssertionError +from .exceptions import TemplateError as TemplateError +from .exceptions import TemplateNotFound as TemplateNotFound +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 +from .loaders import FileSystemLoader as FileSystemLoader +from .loaders import FunctionLoader as FunctionLoader +from .loaders import ModuleLoader as ModuleLoader +from .loaders import PackageLoader as PackageLoader +from .loaders import PrefixLoader as PrefixLoader +from .loaders import ResourceLoader as ResourceLoader +from .runtime import ChainableUndefined as ChainableUndefined +from .runtime import DebugUndefined as DebugUndefined +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" diff --git a/contrib/python/Jinja2/py3/jinja2/_identifier.py b/contrib/python/Jinja2/py3/jinja2/_identifier.py index 224d5449d1..265c49df58 100644 --- a/contrib/python/Jinja2/py3/jinja2/_identifier.py +++ b/contrib/python/Jinja2/py3/jinja2/_identifier.py @@ -1,6 +1,6 @@ -import re - +import re + # generated by scripts/generate_identifier_pattern.py -pattern = re.compile( - r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 -) +pattern = re.compile( + r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 +) diff --git a/contrib/python/Jinja2/py3/jinja2/async_utils.py b/contrib/python/Jinja2/py3/jinja2/async_utils.py index 35e6cb1090..2d826cfb23 100644 --- a/contrib/python/Jinja2/py3/jinja2/async_utils.py +++ b/contrib/python/Jinja2/py3/jinja2/async_utils.py @@ -1,75 +1,75 @@ -import inspect -import typing as t -from functools import wraps - -from .utils import _PassArg -from .utils import pass_eval_context - -V = t.TypeVar("V") - - -def async_variant(normal_func): # type: ignore - def decorator(async_func): # type: ignore - pass_arg = _PassArg.from_obj(normal_func) - need_eval_context = pass_arg is None - - if pass_arg is _PassArg.environment: - - def is_async(args: t.Any) -> bool: - return t.cast(bool, args[0].is_async) - - else: - - def is_async(args: t.Any) -> bool: - return t.cast(bool, args[0].environment.is_async) - - @wraps(normal_func) - def wrapper(*args, **kwargs): # type: ignore - b = is_async(args) - - if need_eval_context: - args = args[1:] - - if b: - return async_func(*args, **kwargs) - - return normal_func(*args, **kwargs) - - if need_eval_context: - wrapper = pass_eval_context(wrapper) - - wrapper.jinja_async_variant = True - return wrapper - - return decorator - - +import inspect +import typing as t +from functools import wraps + +from .utils import _PassArg +from .utils import pass_eval_context + +V = t.TypeVar("V") + + +def async_variant(normal_func): # type: ignore + def decorator(async_func): # type: ignore + pass_arg = _PassArg.from_obj(normal_func) + need_eval_context = pass_arg is None + + if pass_arg is _PassArg.environment: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].is_async) + + else: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].environment.is_async) + + @wraps(normal_func) + def wrapper(*args, **kwargs): # type: ignore + b = is_async(args) + + if need_eval_context: + args = args[1:] + + if b: + return async_func(*args, **kwargs) + + return normal_func(*args, **kwargs) + + if need_eval_context: + wrapper = pass_eval_context(wrapper) + + wrapper.jinja_async_variant = True + return wrapper + + return decorator + + _common_primitives = {int, float, bool, str, list, dict, tuple, type(None)} -async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V": +async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V": # Avoid a costly call to isawaitable if type(value) in _common_primitives: return t.cast("V", value) - if inspect.isawaitable(value): - return await t.cast("t.Awaitable[V]", value) - - return t.cast("V", value) - - -async def auto_aiter( - iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", -) -> "t.AsyncIterator[V]": - if hasattr(iterable, "__aiter__"): - async for item in t.cast("t.AsyncIterable[V]", iterable): - yield item - else: - for item in t.cast("t.Iterable[V]", iterable): - yield item - - -async def auto_to_list( - value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", -) -> t.List["V"]: - return [x async for x in auto_aiter(value)] + if inspect.isawaitable(value): + return await t.cast("t.Awaitable[V]", value) + + return t.cast("V", value) + + +async def auto_aiter( + iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> "t.AsyncIterator[V]": + if hasattr(iterable, "__aiter__"): + async for item in t.cast("t.AsyncIterable[V]", iterable): + yield item + else: + for item in t.cast("t.Iterable[V]", iterable): + yield item + + +async def auto_to_list( + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> t.List["V"]: + return [x async for x in auto_aiter(value)] diff --git a/contrib/python/Jinja2/py3/jinja2/bccache.py b/contrib/python/Jinja2/py3/jinja2/bccache.py index 3bb61b7c34..943c1a0f40 100644 --- a/contrib/python/Jinja2/py3/jinja2/bccache.py +++ b/contrib/python/Jinja2/py3/jinja2/bccache.py @@ -1,47 +1,47 @@ -"""The optional bytecode cache system. This is useful if you have very -complex template situations and the compilation of all those templates -slows down your application too much. +"""The optional bytecode cache system. 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 -are initialized on the first request. +Situations where this is useful are often forking web applications that +are initialized on the first request. """ -import errno -import fnmatch -import marshal +import errno +import fnmatch +import marshal import os -import pickle -import stat +import pickle +import stat import sys import tempfile -import typing as t +import typing as t from hashlib import sha1 -from io import BytesIO -from types import CodeType - -if t.TYPE_CHECKING: - import typing_extensions as te - from .environment import Environment - - class _MemcachedClient(te.Protocol): - def get(self, key: str) -> bytes: - ... - - def set(self, key: str, value: bytes, timeout: t.Optional[int] = None) -> None: - ... - - -bc_version = 5 -# Magic bytes to identify Jinja bytecode cache files. Contains the -# Python major and minor version to avoid loading incompatible bytecode -# if a project upgrades its Python version. -bc_magic = ( - b"j2" - + pickle.dumps(bc_version, 2) - + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2) -) - - -class Bucket: +from io import BytesIO +from types import CodeType + +if t.TYPE_CHECKING: + import typing_extensions as te + from .environment import Environment + + class _MemcachedClient(te.Protocol): + def get(self, key: str) -> bytes: + ... + + def set(self, key: str, value: bytes, timeout: t.Optional[int] = None) -> None: + ... + + +bc_version = 5 +# Magic bytes to identify Jinja bytecode cache files. Contains the +# Python major and minor version to avoid loading incompatible bytecode +# if a project upgrades its Python version. +bc_magic = ( + b"j2" + + pickle.dumps(bc_version, 2) + + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2) +) + + +class Bucket: """Buckets are used to store the bytecode for one template. It's created and initialized by the bytecode cache and passed to the loading functions. @@ -50,17 +50,17 @@ class Bucket: cache subclasses don't have to care about cache invalidation. """ - def __init__(self, environment: "Environment", key: str, checksum: str) -> None: + def __init__(self, environment: "Environment", key: str, checksum: str) -> None: self.environment = environment self.key = key self.checksum = checksum self.reset() - def reset(self) -> None: + def reset(self) -> None: """Resets the bucket (unloads the bytecode).""" - self.code: t.Optional[CodeType] = None + self.code: t.Optional[CodeType] = None - def load_bytecode(self, f: t.BinaryIO) -> None: + def load_bytecode(self, f: t.BinaryIO) -> None: """Loads bytecode from a file or file like object.""" # make sure the magic header is correct magic = f.read(len(bc_magic)) @@ -74,31 +74,31 @@ class Bucket: return # if marshal_load fails then we need to reload try: - self.code = marshal.load(f) + self.code = marshal.load(f) except (EOFError, ValueError, TypeError): self.reset() return - def write_bytecode(self, f: t.BinaryIO) -> None: + def write_bytecode(self, f: t.BinaryIO) -> None: """Dump the bytecode into the file or file like object passed.""" if self.code is None: - raise TypeError("can't write empty bucket") + raise TypeError("can't write empty bucket") f.write(bc_magic) pickle.dump(self.checksum, f, 2) - marshal.dump(self.code, f) + marshal.dump(self.code, f) - def bytecode_from_string(self, string: bytes) -> None: - """Load bytecode from bytes.""" + def bytecode_from_string(self, string: bytes) -> None: + """Load bytecode from bytes.""" self.load_bytecode(BytesIO(string)) - def bytecode_to_string(self) -> bytes: - """Return the bytecode as bytes.""" + def bytecode_to_string(self) -> bytes: + """Return the bytecode as bytes.""" out = BytesIO() self.write_bytecode(out) return out.getvalue() -class BytecodeCache: +class BytecodeCache: """To implement your own bytecode cache you have to subclass this class and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of these methods are passed a :class:`~jinja2.bccache.Bucket`. @@ -124,51 +124,51 @@ class BytecodeCache: bucket.write_bytecode(f) A more advanced version of a filesystem based bytecode cache is part of - Jinja. + Jinja. """ - def load_bytecode(self, bucket: Bucket) -> None: + def load_bytecode(self, bucket: Bucket) -> None: """Subclasses have to override this method to load bytecode into a bucket. If they are not able to find code in the cache for the bucket, it must not do anything. """ raise NotImplementedError() - def dump_bytecode(self, bucket: Bucket) -> None: + def dump_bytecode(self, bucket: Bucket) -> None: """Subclasses have to override this method to write the bytecode from a bucket back to the cache. If it unable to do so it must not fail silently but raise an exception. """ raise NotImplementedError() - def clear(self) -> None: - """Clears the cache. This method is not used by Jinja but should be + def clear(self) -> None: + """Clears the cache. This method is not used by Jinja but should be implemented to allow applications to clear the bytecode cache used by a particular environment. """ - def get_cache_key( - self, name: str, filename: t.Optional[t.Union[str]] = None - ) -> str: + def get_cache_key( + self, name: str, filename: t.Optional[t.Union[str]] = None + ) -> str: """Returns the unique hash key for this template name.""" - hash = sha1(name.encode("utf-8")) - + hash = sha1(name.encode("utf-8")) + if filename is not None: - hash.update(f"|{filename}".encode()) - + hash.update(f"|{filename}".encode()) + return hash.hexdigest() - def get_source_checksum(self, source: str) -> str: + def get_source_checksum(self, source: str) -> str: """Returns a checksum for the source.""" - return sha1(source.encode("utf-8")).hexdigest() - - def get_bucket( - self, - environment: "Environment", - name: str, - filename: t.Optional[str], - source: str, - ) -> Bucket: + return sha1(source.encode("utf-8")).hexdigest() + + def get_bucket( + self, + environment: "Environment", + name: str, + filename: t.Optional[str], + source: str, + ) -> Bucket: """Return a cache bucket for the given template. All arguments are mandatory but filename may be `None`. """ @@ -178,7 +178,7 @@ class BytecodeCache: self.load_bytecode(bucket) return bucket - def set_bucket(self, bucket: Bucket) -> None: + def set_bucket(self, bucket: Bucket) -> None: """Put the bucket into the cache.""" self.dump_bytecode(bucket) @@ -201,31 +201,31 @@ class FileSystemBytecodeCache(BytecodeCache): This bytecode cache supports clearing of the cache using the clear method. """ - def __init__( - self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache" - ) -> None: + def __init__( + self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache" + ) -> None: if directory is None: directory = self._get_default_cache_dir() self.directory = directory self.pattern = pattern - def _get_default_cache_dir(self) -> str: - def _unsafe_dir() -> "te.NoReturn": - raise RuntimeError( - "Cannot determine safe temp directory. You " - "need to explicitly provide one." - ) + def _get_default_cache_dir(self) -> str: + def _unsafe_dir() -> "te.NoReturn": + raise RuntimeError( + "Cannot determine safe temp directory. You " + "need to explicitly provide one." + ) tmpdir = tempfile.gettempdir() # On windows the temporary directory is used specific unless # explicitly forced otherwise. We can just use that. - if os.name == "nt": + if os.name == "nt": return tmpdir - if not hasattr(os, "getuid"): + if not hasattr(os, "getuid"): _unsafe_dir() - dirname = f"_jinja2-cache-{os.getuid()}" + dirname = f"_jinja2-cache-{os.getuid()}" actual_dir = os.path.join(tmpdir, dirname) try: @@ -236,50 +236,50 @@ class FileSystemBytecodeCache(BytecodeCache): try: os.chmod(actual_dir, stat.S_IRWXU) actual_dir_stat = os.lstat(actual_dir) - if ( - actual_dir_stat.st_uid != os.getuid() - or not stat.S_ISDIR(actual_dir_stat.st_mode) - or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU - ): + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): _unsafe_dir() except OSError as e: if e.errno != errno.EEXIST: raise actual_dir_stat = os.lstat(actual_dir) - if ( - actual_dir_stat.st_uid != os.getuid() - or not stat.S_ISDIR(actual_dir_stat.st_mode) - or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU - ): + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): _unsafe_dir() return actual_dir - def _get_cache_filename(self, bucket: Bucket) -> str: - return os.path.join(self.directory, self.pattern % (bucket.key,)) + def _get_cache_filename(self, bucket: Bucket) -> str: + return os.path.join(self.directory, self.pattern % (bucket.key,)) - def load_bytecode(self, bucket: Bucket) -> None: - filename = self._get_cache_filename(bucket) - - if os.path.exists(filename): - with open(filename, "rb") as f: + 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) - def dump_bytecode(self, bucket: Bucket) -> None: - with open(self._get_cache_filename(bucket), "wb") as f: + def dump_bytecode(self, bucket: Bucket) -> None: + with open(self._get_cache_filename(bucket), "wb") as f: bucket.write_bytecode(f) - def clear(self) -> None: + def clear(self) -> None: # imported lazily here because google app-engine doesn't support # write access on the file system and the function does not exist # normally. from os import remove - - files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",)) + + files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",)) for filename in files: try: - remove(os.path.join(self.directory, filename)) + remove(os.path.join(self.directory, filename)) except OSError: pass @@ -292,11 +292,11 @@ class MemcachedBytecodeCache(BytecodeCache): Libraries compatible with this class: - - `cachelib <https://github.com/pallets/cachelib>`_ - - `python-memcached <https://pypi.org/project/python-memcached/>`_ + - `cachelib <https://github.com/pallets/cachelib>`_ + - `python-memcached <https://pypi.org/project/python-memcached/>`_ (Unfortunately the django cache interface is not compatible because it - does not support storing binary data, only text. You can however pass + does not support storing binary data, only text. You can however pass the underlying cache client to the bytecode cache which is available as `django.core.cache.cache._client`.) @@ -329,36 +329,36 @@ class MemcachedBytecodeCache(BytecodeCache): `ignore_memcache_errors` parameter. """ - def __init__( - self, - client: "_MemcachedClient", - prefix: str = "jinja2/bytecode/", - timeout: t.Optional[int] = None, - ignore_memcache_errors: bool = True, - ): + def __init__( + self, + client: "_MemcachedClient", + prefix: str = "jinja2/bytecode/", + timeout: t.Optional[int] = None, + ignore_memcache_errors: bool = True, + ): self.client = client self.prefix = prefix self.timeout = timeout self.ignore_memcache_errors = ignore_memcache_errors - def load_bytecode(self, bucket: Bucket) -> None: + def load_bytecode(self, bucket: Bucket) -> None: try: code = self.client.get(self.prefix + bucket.key) except Exception: if not self.ignore_memcache_errors: raise - else: + else: bucket.bytecode_from_string(code) - def dump_bytecode(self, bucket: Bucket) -> None: - key = self.prefix + bucket.key - value = bucket.bytecode_to_string() - + def dump_bytecode(self, bucket: Bucket) -> None: + key = self.prefix + bucket.key + value = bucket.bytecode_to_string() + try: - if self.timeout is not None: - self.client.set(key, value, self.timeout) - else: - self.client.set(key, value) + if self.timeout is not None: + self.client.set(key, value, self.timeout) + else: + self.client.set(key, value) except Exception: if not self.ignore_memcache_errors: raise diff --git a/contrib/python/Jinja2/py3/jinja2/compiler.py b/contrib/python/Jinja2/py3/jinja2/compiler.py index 52fd5b83e2..a9e86395d8 100644 --- a/contrib/python/Jinja2/py3/jinja2/compiler.py +++ b/contrib/python/Jinja2/py3/jinja2/compiler.py @@ -1,147 +1,147 @@ -"""Compiles nodes from the parser into Python code.""" -import typing as t -from contextlib import contextmanager -from functools import update_wrapper -from io import StringIO +"""Compiles nodes from the parser into Python code.""" +import typing as t +from contextlib import contextmanager +from functools import update_wrapper +from io import StringIO from itertools import chain from keyword import iskeyword as is_python_keyword -from markupsafe import escape -from markupsafe import Markup - -from . import nodes -from .exceptions import TemplateAssertionError -from .idtracking import Symbols -from .idtracking import VAR_LOAD_ALIAS -from .idtracking import VAR_LOAD_PARAMETER -from .idtracking import VAR_LOAD_RESOLVE -from .idtracking import VAR_LOAD_UNDEFINED -from .nodes import EvalContext -from .optimizer import Optimizer -from .utils import _PassArg -from .utils import concat -from .visitor import NodeVisitor - -if t.TYPE_CHECKING: - import typing_extensions as te - from .environment import Environment - -F = t.TypeVar("F", bound=t.Callable[..., t.Any]) - +from markupsafe import escape +from markupsafe import Markup + +from . import nodes +from .exceptions import TemplateAssertionError +from .idtracking import Symbols +from .idtracking import VAR_LOAD_ALIAS +from .idtracking import VAR_LOAD_PARAMETER +from .idtracking import VAR_LOAD_RESOLVE +from .idtracking import VAR_LOAD_UNDEFINED +from .nodes import EvalContext +from .optimizer import Optimizer +from .utils import _PassArg +from .utils import concat +from .visitor import NodeVisitor + +if t.TYPE_CHECKING: + import typing_extensions as te + from .environment import Environment + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + operators = { - "eq": "==", - "ne": "!=", - "gt": ">", - "gteq": ">=", - "lt": "<", - "lteq": "<=", - "in": "in", - "notin": "not in", + "eq": "==", + "ne": "!=", + "gt": ">", + "gteq": ">=", + "lt": "<", + "lteq": "<=", + "in": "in", + "notin": "not in", } -def optimizeconst(f: F) -> F: - def new_func( - self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any - ) -> t.Any: +def optimizeconst(f: F) -> F: + def new_func( + self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any + ) -> t.Any: # Only optimize if the frame is not volatile - if self.optimizer is not None and not frame.eval_ctx.volatile: + if self.optimizer is not None and not frame.eval_ctx.volatile: new_node = self.optimizer.visit(node, frame.eval_ctx) - + if new_node != node: return self.visit(new_node, frame) - + return f(self, node, frame, **kwargs) - - return update_wrapper(t.cast(F, new_func), f) - - -def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]: - @optimizeconst - def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None: - if ( - self.environment.sandboxed - and op in self.environment.intercepted_binops # type: ignore - ): - self.write(f"environment.call_binop(context, {op!r}, ") - self.visit(node.left, frame) - self.write(", ") - self.visit(node.right, frame) - else: - self.write("(") - self.visit(node.left, frame) - self.write(f" {op} ") - self.visit(node.right, frame) - - self.write(")") - - return visitor - - -def _make_unop( - op: str, -) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]: - @optimizeconst - def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None: - if ( - self.environment.sandboxed - and op in self.environment.intercepted_unops # type: ignore - ): - self.write(f"environment.call_unop(context, {op!r}, ") - self.visit(node.node, frame) - else: - self.write("(" + op) - self.visit(node.node, frame) - - self.write(")") - - return visitor - - -def generate( - node: nodes.Template, - environment: "Environment", - name: t.Optional[str], - filename: t.Optional[str], - stream: t.Optional[t.TextIO] = None, - defer_init: bool = False, - optimized: bool = True, -) -> t.Optional[str]: + + return update_wrapper(t.cast(F, new_func), f) + + +def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed + and op in self.environment.intercepted_binops # type: ignore + ): + self.write(f"environment.call_binop(context, {op!r}, ") + self.visit(node.left, frame) + self.write(", ") + self.visit(node.right, frame) + else: + self.write("(") + self.visit(node.left, frame) + self.write(f" {op} ") + self.visit(node.right, frame) + + self.write(")") + + return visitor + + +def _make_unop( + op: str, +) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed + and op in self.environment.intercepted_unops # type: ignore + ): + self.write(f"environment.call_unop(context, {op!r}, ") + self.visit(node.node, frame) + else: + self.write("(" + op) + self.visit(node.node, frame) + + self.write(")") + + return visitor + + +def generate( + node: nodes.Template, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, +) -> t.Optional[str]: """Generate the python source for a node tree.""" if not isinstance(node, nodes.Template): - raise TypeError("Can't compile non template nodes") - - generator = environment.code_generator_class( - environment, name, filename, stream, defer_init, optimized - ) + raise TypeError("Can't compile non template nodes") + + generator = environment.code_generator_class( + environment, name, filename, stream, defer_init, optimized + ) generator.visit(node) - + if stream is None: - return generator.stream.getvalue() # type: ignore + return generator.stream.getvalue() # type: ignore - return None + return None - -def has_safe_repr(value: t.Any) -> bool: + +def has_safe_repr(value: t.Any) -> bool: """Does the node have a safe representation?""" if value is None or value is NotImplemented or value is Ellipsis: return True - - if type(value) in {bool, int, float, complex, range, str, Markup}: + + if type(value) in {bool, int, float, complex, range, str, Markup}: return True - - if type(value) in {tuple, list, set, frozenset}: - return all(has_safe_repr(v) for v in value) - - if type(value) is dict: - return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items()) - + + if type(value) in {tuple, list, set, frozenset}: + return all(has_safe_repr(v) for v in value) + + if type(value) is dict: + return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items()) + return False -def find_undeclared( - nodes: t.Iterable[nodes.Node], names: t.Iterable[str] -) -> t.Set[str]: +def find_undeclared( + nodes: t.Iterable[nodes.Node], names: t.Iterable[str] +) -> t.Set[str]: """Check if the names passed are accessed undeclared. The return value is a set of all the undeclared names from the sequence of names found. """ @@ -154,50 +154,50 @@ def find_undeclared( return visitor.undeclared -class MacroRef: - def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None: +class MacroRef: + def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None: self.node = node self.accesses_caller = False self.accesses_kwargs = False self.accesses_varargs = False -class Frame: +class Frame: """Holds compile time information for us.""" - def __init__( - self, - eval_ctx: EvalContext, - parent: t.Optional["Frame"] = None, - level: t.Optional[int] = None, - ) -> None: + def __init__( + self, + eval_ctx: EvalContext, + parent: t.Optional["Frame"] = None, + level: t.Optional[int] = None, + ) -> None: self.eval_ctx = eval_ctx - # the parent of this frame - self.parent = parent - - if parent is None: - self.symbols = Symbols(level=level) - - # in some dynamic inheritance situations the compiler needs to add - # write tests around output statements. - self.require_output_check = False - - # inside some tags we are using a buffer rather than yield statements. - # this for example affects {% filter %} or {% macro %}. If a frame - # is buffered this variable points to the name of the list used as - # buffer. - self.buffer: t.Optional[str] = None - - # the name of the block we're in, otherwise None. - self.block: t.Optional[str] = None - - else: - self.symbols = Symbols(parent.symbols, level=level) - self.require_output_check = parent.require_output_check - self.buffer = parent.buffer - self.block = parent.block - + # the parent of this frame + self.parent = parent + + if parent is None: + self.symbols = Symbols(level=level) + + # in some dynamic inheritance situations the compiler needs to add + # write tests around output statements. + self.require_output_check = False + + # inside some tags we are using a buffer rather than yield statements. + # this for example affects {% filter %} or {% macro %}. If a frame + # is buffered this variable points to the name of the list used as + # buffer. + self.buffer: t.Optional[str] = None + + # the name of the block we're in, otherwise None. + self.block: t.Optional[str] = None + + else: + self.symbols = Symbols(parent.symbols, level=level) + self.require_output_check = parent.require_output_check + self.buffer = parent.buffer + self.block = parent.block + # a toplevel frame is the root + soft frames such as if conditions. self.toplevel = False @@ -206,40 +206,40 @@ class Frame: # situations. self.rootlevel = False - # variables set inside of loops and blocks should not affect outer frames, - # but they still needs to be kept track of as part of the active context. - self.loop_frame = False - self.block_frame = False + # variables set inside of loops and blocks should not affect outer frames, + # but they still needs to be kept track of as part of the active context. + self.loop_frame = False + self.block_frame = False - # track whether the frame is being used in an if-statement or conditional - # expression as it determines which errors should be raised during runtime - # or compile time. - self.soft_frame = False + # track whether the frame is being used in an if-statement or conditional + # expression as it determines which errors should be raised during runtime + # or compile time. + self.soft_frame = False - def copy(self) -> "Frame": + def copy(self) -> "Frame": """Create a copy of the current one.""" - rv = t.cast(Frame, object.__new__(self.__class__)) + rv = t.cast(Frame, object.__new__(self.__class__)) rv.__dict__.update(self.__dict__) rv.symbols = self.symbols.copy() return rv - def inner(self, isolated: bool = False) -> "Frame": + def inner(self, isolated: bool = False) -> "Frame": """Return an inner frame.""" if isolated: return Frame(self.eval_ctx, level=self.symbols.level + 1) return Frame(self.eval_ctx, self) - def soft(self) -> "Frame": + def soft(self) -> "Frame": """Return a soft frame. A soft frame may not be modified as standalone thing as it shares the resources with the frame it was created of, but it's not a rootlevel frame any longer. - This is only used to implement if-statements and conditional - expressions. + This is only used to implement if-statements and conditional + expressions. """ rv = self.copy() rv.rootlevel = False - rv.soft_frame = True + rv.soft_frame = True return rv __copy__ = copy @@ -252,19 +252,19 @@ class VisitorExit(RuntimeError): class DependencyFinderVisitor(NodeVisitor): """A visitor that collects filter and test calls.""" - def __init__(self) -> None: - self.filters: t.Set[str] = set() - self.tests: t.Set[str] = set() + def __init__(self) -> None: + self.filters: t.Set[str] = set() + self.tests: t.Set[str] = set() - def visit_Filter(self, node: nodes.Filter) -> None: + def visit_Filter(self, node: nodes.Filter) -> None: self.generic_visit(node) self.filters.add(node.name) - def visit_Test(self, node: nodes.Test) -> None: + def visit_Test(self, node: nodes.Test) -> None: self.generic_visit(node) self.tests.add(node.name) - def visit_Block(self, node: nodes.Block) -> None: + def visit_Block(self, node: nodes.Block) -> None: """Stop visiting at blocks.""" @@ -274,19 +274,19 @@ class UndeclaredNameVisitor(NodeVisitor): not stop at closure frames. """ - def __init__(self, names: t.Iterable[str]) -> None: + def __init__(self, names: t.Iterable[str]) -> None: self.names = set(names) - self.undeclared: t.Set[str] = set() + self.undeclared: t.Set[str] = set() - def visit_Name(self, node: nodes.Name) -> None: - if node.ctx == "load" and node.name in self.names: + def visit_Name(self, node: nodes.Name) -> None: + if node.ctx == "load" and node.name in self.names: self.undeclared.add(node.name) if self.undeclared == self.names: raise VisitorExit() else: self.names.discard(node.name) - def visit_Block(self, node: nodes.Block) -> None: + def visit_Block(self, node: nodes.Block) -> None: """Stop visiting a blocks.""" @@ -298,34 +298,34 @@ class CompilerExit(Exception): class CodeGenerator(NodeVisitor): - def __init__( - self, - environment: "Environment", - name: t.Optional[str], - filename: t.Optional[str], - stream: t.Optional[t.TextIO] = None, - defer_init: bool = False, - optimized: bool = True, - ) -> None: + def __init__( + self, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, + ) -> None: if stream is None: - stream = StringIO() + stream = StringIO() self.environment = environment self.name = name self.filename = filename self.stream = stream self.created_block_context = False self.defer_init = defer_init - self.optimizer: t.Optional[Optimizer] = None - + self.optimizer: t.Optional[Optimizer] = None + if optimized: self.optimizer = Optimizer(environment) # aliases for imports - self.import_aliases: t.Dict[str, str] = {} + self.import_aliases: t.Dict[str, str] = {} # a registry for all blocks. Because blocks are moved out # into the global python scope they are registered here - self.blocks: t.Dict[str, nodes.Block] = {} + self.blocks: t.Dict[str, nodes.Block] = {} # the number of extends statements so far self.extends_so_far = 0 @@ -339,12 +339,12 @@ class CodeGenerator(NodeVisitor): self.code_lineno = 1 # registry of all filters and tests (global, not block local) - self.tests: t.Dict[str, str] = {} - self.filters: t.Dict[str, str] = {} + self.tests: t.Dict[str, str] = {} + self.filters: t.Dict[str, str] = {} # the debug information - self.debug_info: t.List[t.Tuple[int, int]] = [] - self._write_debug_info: t.Optional[int] = None + self.debug_info: t.List[t.Tuple[int, int]] = [] + self._write_debug_info: t.Optional[int] = None # the number of new lines before the next write() self._new_lines = 0 @@ -363,254 +363,254 @@ class CodeGenerator(NodeVisitor): self._indentation = 0 # Tracks toplevel assignments - self._assign_stack: t.List[t.Set[str]] = [] + self._assign_stack: t.List[t.Set[str]] = [] # Tracks parameter definition blocks - self._param_def_block: t.List[t.Set[str]] = [] + self._param_def_block: t.List[t.Set[str]] = [] # Tracks the current context. - self._context_reference_stack = ["context"] - - @property - def optimized(self) -> bool: - return self.optimizer is not None + self._context_reference_stack = ["context"] + @property + def optimized(self) -> bool: + return self.optimizer is not None + # -- Various compilation helpers - def fail(self, msg: str, lineno: int) -> "te.NoReturn": + def fail(self, msg: str, lineno: int) -> "te.NoReturn": """Fail with a :exc:`TemplateAssertionError`.""" raise TemplateAssertionError(msg, lineno, self.name, self.filename) - def temporary_identifier(self) -> str: + def temporary_identifier(self) -> str: """Get a new unique identifier.""" self._last_identifier += 1 - return f"t_{self._last_identifier}" + return f"t_{self._last_identifier}" - def buffer(self, frame: Frame) -> None: + def buffer(self, frame: Frame) -> None: """Enable buffering for the frame from that point onwards.""" frame.buffer = self.temporary_identifier() - self.writeline(f"{frame.buffer} = []") + self.writeline(f"{frame.buffer} = []") - def return_buffer_contents( - self, frame: Frame, force_unescaped: bool = False - ) -> None: + def return_buffer_contents( + self, frame: Frame, force_unescaped: bool = False + ) -> None: """Return the buffer contents of the frame.""" if not force_unescaped: if frame.eval_ctx.volatile: - self.writeline("if context.eval_ctx.autoescape:") + self.writeline("if context.eval_ctx.autoescape:") self.indent() - self.writeline(f"return Markup(concat({frame.buffer}))") + self.writeline(f"return Markup(concat({frame.buffer}))") self.outdent() - self.writeline("else:") + self.writeline("else:") self.indent() - self.writeline(f"return concat({frame.buffer})") + self.writeline(f"return concat({frame.buffer})") self.outdent() return elif frame.eval_ctx.autoescape: - self.writeline(f"return Markup(concat({frame.buffer}))") + self.writeline(f"return Markup(concat({frame.buffer}))") return - self.writeline(f"return concat({frame.buffer})") + self.writeline(f"return concat({frame.buffer})") - def indent(self) -> None: + def indent(self) -> None: """Indent by one.""" self._indentation += 1 - def outdent(self, step: int = 1) -> None: + def outdent(self, step: int = 1) -> None: """Outdent by step.""" self._indentation -= step - def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None: + def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None: """Yield or write into the frame buffer.""" if frame.buffer is None: - self.writeline("yield ", node) + self.writeline("yield ", node) else: - self.writeline(f"{frame.buffer}.append(", node) + self.writeline(f"{frame.buffer}.append(", node) - def end_write(self, frame: Frame) -> None: + def end_write(self, frame: Frame) -> None: """End the writing process started by `start_write`.""" if frame.buffer is not None: - self.write(")") + self.write(")") - def simple_write( - self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None - ) -> None: + def simple_write( + self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None + ) -> None: """Simple shortcut for start_write + write + end_write.""" self.start_write(frame, node) self.write(s) self.end_write(frame) - def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None: + def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None: """Visit a list of nodes as block in a frame. If the current frame is no buffer a dummy ``if 0: yield None`` is written automatically. """ try: - self.writeline("pass") + self.writeline("pass") for node in nodes: self.visit(node, frame) except CompilerExit: pass - def write(self, x: str) -> None: + def write(self, x: str) -> None: """Write a string into the output stream.""" if self._new_lines: if not self._first_write: - self.stream.write("\n" * self._new_lines) + self.stream.write("\n" * self._new_lines) self.code_lineno += self._new_lines if self._write_debug_info is not None: - self.debug_info.append((self._write_debug_info, self.code_lineno)) + self.debug_info.append((self._write_debug_info, self.code_lineno)) self._write_debug_info = None self._first_write = False - self.stream.write(" " * self._indentation) + self.stream.write(" " * self._indentation) self._new_lines = 0 self.stream.write(x) - def writeline( - self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0 - ) -> None: + def writeline( + self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0 + ) -> None: """Combination of newline and write.""" self.newline(node, extra) self.write(x) - def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None: + def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None: """Add one or more newlines before the next write.""" self._new_lines = max(self._new_lines, 1 + extra) if node is not None and node.lineno != self._last_line: self._write_debug_info = node.lineno self._last_line = node.lineno - def signature( - self, - node: t.Union[nodes.Call, nodes.Filter, nodes.Test], - frame: Frame, - extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None, - ) -> None: + def signature( + self, + node: t.Union[nodes.Call, nodes.Filter, nodes.Test], + frame: Frame, + extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + ) -> None: """Writes a function call to the stream for the current node. A leading comma is added automatically. The extra keyword arguments may not include python keywords otherwise a syntax - error could occur. The extra keyword arguments should be given + error could occur. The extra keyword arguments should be given as python dict. """ # if any of the given keyword arguments is a python keyword # we have to make sure that no invalid call is created. - kwarg_workaround = any( - is_python_keyword(t.cast(str, k)) - for k in chain((x.key for x in node.kwargs), extra_kwargs or ()) - ) + kwarg_workaround = any( + is_python_keyword(t.cast(str, k)) + for k in chain((x.key for x in node.kwargs), extra_kwargs or ()) + ) for arg in node.args: - self.write(", ") + self.write(", ") self.visit(arg, frame) if not kwarg_workaround: for kwarg in node.kwargs: - self.write(", ") + self.write(", ") self.visit(kwarg, frame) if extra_kwargs is not None: - for key, value in extra_kwargs.items(): - self.write(f", {key}={value}") + for key, value in extra_kwargs.items(): + self.write(f", {key}={value}") if node.dyn_args: - self.write(", *") + self.write(", *") self.visit(node.dyn_args, frame) if kwarg_workaround: if node.dyn_kwargs is not None: - self.write(", **dict({") + self.write(", **dict({") else: - self.write(", **{") + self.write(", **{") for kwarg in node.kwargs: - self.write(f"{kwarg.key!r}: ") + self.write(f"{kwarg.key!r}: ") self.visit(kwarg.value, frame) - self.write(", ") + self.write(", ") if extra_kwargs is not None: - for key, value in extra_kwargs.items(): - self.write(f"{key!r}: {value}, ") + for key, value in extra_kwargs.items(): + self.write(f"{key!r}: {value}, ") if node.dyn_kwargs is not None: - self.write("}, **") + self.write("}, **") self.visit(node.dyn_kwargs, frame) - self.write(")") + self.write(")") else: - self.write("}") + self.write("}") elif node.dyn_kwargs is not None: - self.write(", **") + self.write(", **") self.visit(node.dyn_kwargs, frame) - def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None: - """Find all filter and test names used in the template and - assign them to variables in the compiled namespace. Checking - that the names are registered with the environment is done when - compiling the Filter and Test nodes. If the node is in an If or - CondExpr node, the check is done at runtime instead. - - .. versionchanged:: 3.0 - Filters and tests in If and CondExpr nodes are checked at - runtime instead of compile time. - """ + def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None: + """Find all filter and test names used in the template and + assign them to variables in the compiled namespace. Checking + that the names are registered with the environment is done when + compiling the Filter and Test nodes. If the node is in an If or + CondExpr node, the check is done at runtime instead. + + .. versionchanged:: 3.0 + Filters and tests in If and CondExpr nodes are checked at + runtime instead of compile time. + """ visitor = DependencyFinderVisitor() - + for node in nodes: visitor.visit(node) - - for id_map, names, dependency in (self.filters, visitor.filters, "filters"), ( - self.tests, - visitor.tests, - "tests", - ): - for name in sorted(names): - if name not in id_map: - id_map[name] = self.temporary_identifier() - - # add check during runtime that dependencies used inside of executed - # blocks are defined, as this step may be skipped during compile time - self.writeline("try:") - self.indent() - self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]") - self.outdent() - self.writeline("except KeyError:") - self.indent() - self.writeline("@internalcode") - self.writeline(f"def {id_map[name]}(*unused):") - self.indent() - self.writeline( - f'raise TemplateRuntimeError("No {dependency[:-1]}' - f' named {name!r} found.")' - ) - self.outdent() - self.outdent() - - def enter_frame(self, frame: Frame) -> None: + + for id_map, names, dependency in (self.filters, visitor.filters, "filters"), ( + self.tests, + visitor.tests, + "tests", + ): + for name in sorted(names): + if name not in id_map: + id_map[name] = self.temporary_identifier() + + # add check during runtime that dependencies used inside of executed + # blocks are defined, as this step may be skipped during compile time + self.writeline("try:") + self.indent() + self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]") + self.outdent() + self.writeline("except KeyError:") + self.indent() + self.writeline("@internalcode") + self.writeline(f"def {id_map[name]}(*unused):") + self.indent() + self.writeline( + f'raise TemplateRuntimeError("No {dependency[:-1]}' + f' named {name!r} found.")' + ) + self.outdent() + self.outdent() + + def enter_frame(self, frame: Frame) -> None: undefs = [] - for target, (action, param) in frame.symbols.loads.items(): + for target, (action, param) in frame.symbols.loads.items(): if action == VAR_LOAD_PARAMETER: pass elif action == VAR_LOAD_RESOLVE: - self.writeline(f"{target} = {self.get_resolve_func()}({param!r})") + self.writeline(f"{target} = {self.get_resolve_func()}({param!r})") elif action == VAR_LOAD_ALIAS: - self.writeline(f"{target} = {param}") + self.writeline(f"{target} = {param}") elif action == VAR_LOAD_UNDEFINED: undefs.append(target) else: - raise NotImplementedError("unknown load instruction") + raise NotImplementedError("unknown load instruction") if undefs: - self.writeline(f"{' = '.join(undefs)} = missing") + self.writeline(f"{' = '.join(undefs)} = missing") - def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None: + def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None: if not with_python_scope: undefs = [] - for target in frame.symbols.loads: + for target in frame.symbols.loads: undefs.append(target) if undefs: - self.writeline(f"{' = '.join(undefs)} = missing") + self.writeline(f"{' = '.join(undefs)} = missing") - def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str: - return async_value if self.environment.is_async else sync_value + def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str: + return async_value if self.environment.is_async else sync_value - def func(self, name: str) -> str: - return f"{self.choose_async()}def {name}" - - def macro_body( - self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame - ) -> t.Tuple[Frame, MacroRef]: + def func(self, name: str) -> str: + return f"{self.choose_async()}def {name}" + + def macro_body( + self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame + ) -> t.Tuple[Frame, MacroRef]: """Dump the function def of a macro or call block.""" frame = frame.inner() frame.symbols.analyze_node(node) @@ -619,18 +619,18 @@ class CodeGenerator(NodeVisitor): explicit_caller = None skip_special_params = set() args = [] - + for idx, arg in enumerate(node.args): - if arg.name == "caller": + if arg.name == "caller": explicit_caller = idx - if arg.name in ("kwargs", "varargs"): + if arg.name in ("kwargs", "varargs"): skip_special_params.add(arg.name) args.append(frame.symbols.ref(arg.name)) - undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs")) + undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs")) - if "caller" in undeclared: - # In older Jinja versions there was a bug that allowed caller + if "caller" in undeclared: + # In older Jinja versions there was a bug that allowed caller # to retain the special behavior even if it was mentioned in # the argument list. However thankfully this was only really # working if it was the last argument. So we are explicitly @@ -640,26 +640,26 @@ class CodeGenerator(NodeVisitor): try: node.defaults[explicit_caller - len(node.args)] except IndexError: - self.fail( - "When defining macros or call blocks the " - 'special "caller" argument must be omitted ' - "or be given a default.", - node.lineno, - ) + self.fail( + "When defining macros or call blocks the " + 'special "caller" argument must be omitted ' + "or be given a default.", + node.lineno, + ) else: - args.append(frame.symbols.declare_parameter("caller")) + args.append(frame.symbols.declare_parameter("caller")) macro_ref.accesses_caller = True - if "kwargs" in undeclared and "kwargs" not in skip_special_params: - args.append(frame.symbols.declare_parameter("kwargs")) + if "kwargs" in undeclared and "kwargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("kwargs")) macro_ref.accesses_kwargs = True - if "varargs" in undeclared and "varargs" not in skip_special_params: - args.append(frame.symbols.declare_parameter("varargs")) + if "varargs" in undeclared and "varargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("varargs")) macro_ref.accesses_varargs = True # macros are delayed, they never require output checks frame.require_output_check = False frame.symbols.analyze_node(node) - self.writeline(f"{self.func('macro')}({', '.join(args)}):", node) + self.writeline(f"{self.func('macro')}({', '.join(args)}):", node) self.indent() self.buffer(frame) @@ -668,17 +668,17 @@ class CodeGenerator(NodeVisitor): self.push_parameter_definitions(frame) for idx, arg in enumerate(node.args): ref = frame.symbols.ref(arg.name) - self.writeline(f"if {ref} is missing:") + self.writeline(f"if {ref} is missing:") self.indent() try: default = node.defaults[idx - len(node.args)] except IndexError: - self.writeline( - f'{ref} = undefined("parameter {arg.name!r} was not provided",' - f" name={arg.name!r})" - ) + self.writeline( + f'{ref} = undefined("parameter {arg.name!r} was not provided",' + f" name={arg.name!r})" + ) else: - self.writeline(f"{ref} = ") + self.writeline(f"{ref} = ") self.visit(default, frame) self.mark_parameter_stored(ref) self.outdent() @@ -691,45 +691,45 @@ class CodeGenerator(NodeVisitor): return frame, macro_ref - def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None: + def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None: """Dump the macro definition for the def created by macro_body.""" - arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args) - name = getattr(macro_ref.node, "name", None) + arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args) + name = getattr(macro_ref.node, "name", None) if len(macro_ref.node.args) == 1: - arg_tuple += "," - self.write( - f"Macro(environment, macro, {name!r}, ({arg_tuple})," - f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r}," - f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)" - ) - - def position(self, node: nodes.Node) -> str: + arg_tuple += "," + self.write( + f"Macro(environment, macro, {name!r}, ({arg_tuple})," + f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r}," + f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)" + ) + + def position(self, node: nodes.Node) -> str: """Return a human readable position for the node.""" - rv = f"line {node.lineno}" + rv = f"line {node.lineno}" if self.name is not None: - rv = f"{rv} in {self.name!r}" + rv = f"{rv} in {self.name!r}" return rv - def dump_local_context(self, frame: Frame) -> str: - items_kv = ", ".join( - f"{name!r}: {target}" - for name, target in frame.symbols.dump_stores().items() - ) - return f"{{{items_kv}}}" + def dump_local_context(self, frame: Frame) -> str: + items_kv = ", ".join( + f"{name!r}: {target}" + for name, target in frame.symbols.dump_stores().items() + ) + return f"{{{items_kv}}}" - def write_commons(self) -> None: + def write_commons(self) -> None: """Writes a common preamble that is used by root and block functions. Primarily this sets up common local helpers and enforces a generator through a dead branch. """ - self.writeline("resolve = context.resolve_or_missing") - self.writeline("undefined = environment.undefined") - # always use the standard Undefined class for the implicit else of - # conditional expressions - self.writeline("cond_expr_undefined = Undefined") - self.writeline("if 0: yield None") - - def push_parameter_definitions(self, frame: Frame) -> None: + self.writeline("resolve = context.resolve_or_missing") + self.writeline("undefined = environment.undefined") + # always use the standard Undefined class for the implicit else of + # conditional expressions + self.writeline("cond_expr_undefined = Undefined") + self.writeline("if 0: yield None") + + def push_parameter_definitions(self, frame: Frame) -> None: """Pushes all parameter targets from the given frame into a local stack that permits tracking of yet to be assigned parameters. In particular this enables the optimization from `visit_Name` to skip @@ -738,109 +738,109 @@ class CodeGenerator(NodeVisitor): """ self._param_def_block.append(frame.symbols.dump_param_targets()) - def pop_parameter_definitions(self) -> None: + def pop_parameter_definitions(self) -> None: """Pops the current parameter definitions set.""" self._param_def_block.pop() - def mark_parameter_stored(self, target: str) -> None: + def mark_parameter_stored(self, target: str) -> None: """Marks a parameter in the current parameter definitions as stored. This will skip the enforced undefined checks. """ if self._param_def_block: self._param_def_block[-1].discard(target) - def push_context_reference(self, target: str) -> None: + def push_context_reference(self, target: str) -> None: self._context_reference_stack.append(target) - def pop_context_reference(self) -> None: + def pop_context_reference(self) -> None: self._context_reference_stack.pop() - def get_context_ref(self) -> str: + def get_context_ref(self) -> str: return self._context_reference_stack[-1] - def get_resolve_func(self) -> str: + def get_resolve_func(self) -> str: target = self._context_reference_stack[-1] - if target == "context": - return "resolve" - return f"{target}.resolve" + if target == "context": + return "resolve" + return f"{target}.resolve" - def derive_context(self, frame: Frame) -> str: - return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})" + def derive_context(self, frame: Frame) -> str: + return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})" - def parameter_is_undeclared(self, target: str) -> bool: + def parameter_is_undeclared(self, target: str) -> bool: """Checks if a given target is an undeclared parameter.""" if not self._param_def_block: return False return target in self._param_def_block[-1] - def push_assign_tracking(self) -> None: + def push_assign_tracking(self) -> None: """Pushes a new layer for assignment tracking.""" self._assign_stack.append(set()) - def pop_assign_tracking(self, frame: Frame) -> None: + def pop_assign_tracking(self, frame: Frame) -> None: """Pops the topmost level for assignment tracking and updates the context variables if necessary. """ vars = self._assign_stack.pop() - if ( - not frame.block_frame - and not frame.loop_frame - and not frame.toplevel - or not vars - ): + if ( + not frame.block_frame + and not frame.loop_frame + and not frame.toplevel + or not vars + ): return - public_names = [x for x in vars if x[:1] != "_"] + public_names = [x for x in vars if x[:1] != "_"] if len(vars) == 1: name = next(iter(vars)) ref = frame.symbols.ref(name) - if frame.loop_frame: - self.writeline(f"_loop_vars[{name!r}] = {ref}") - return - if frame.block_frame: - self.writeline(f"_block_vars[{name!r}] = {ref}") - return - self.writeline(f"context.vars[{name!r}] = {ref}") + if frame.loop_frame: + self.writeline(f"_loop_vars[{name!r}] = {ref}") + return + if frame.block_frame: + self.writeline(f"_block_vars[{name!r}] = {ref}") + return + self.writeline(f"context.vars[{name!r}] = {ref}") else: - if frame.loop_frame: - self.writeline("_loop_vars.update({") - elif frame.block_frame: - self.writeline("_block_vars.update({") - else: - self.writeline("context.vars.update({") + if frame.loop_frame: + self.writeline("_loop_vars.update({") + elif frame.block_frame: + self.writeline("_block_vars.update({") + else: + self.writeline("context.vars.update({") for idx, name in enumerate(vars): if idx: - self.write(", ") + self.write(", ") ref = frame.symbols.ref(name) - self.write(f"{name!r}: {ref}") - self.write("})") - if not frame.block_frame and not frame.loop_frame and public_names: + self.write(f"{name!r}: {ref}") + self.write("})") + if not frame.block_frame and not frame.loop_frame and public_names: if len(public_names) == 1: - self.writeline(f"context.exported_vars.add({public_names[0]!r})") + self.writeline(f"context.exported_vars.add({public_names[0]!r})") else: - names_str = ", ".join(map(repr, public_names)) - self.writeline(f"context.exported_vars.update(({names_str}))") + names_str = ", ".join(map(repr, public_names)) + self.writeline(f"context.exported_vars.update(({names_str}))") # -- Statement Visitors - def visit_Template( - self, node: nodes.Template, frame: t.Optional[Frame] = None - ) -> None: - assert frame is None, "no root frame allowed" + def visit_Template( + self, node: nodes.Template, frame: t.Optional[Frame] = None + ) -> None: + assert frame is None, "no root frame allowed" eval_ctx = EvalContext(self.environment, self.name) - from .runtime import exported, async_exported + from .runtime import exported, async_exported if self.environment.is_async: - exported_names = sorted(exported + async_exported) - else: - exported_names = sorted(exported) - - self.writeline("from __future__ import generator_stop") # Python < 3.7 - self.writeline("from jinja2.runtime import " + ", ".join(exported_names)) + exported_names = sorted(exported + async_exported) + 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 # environment into a local name - envenv = "" if self.defer_init else ", environment=environment" + envenv = "" if self.defer_init else ", environment=environment" # do we have an extends tag at all? If not, we can save some # overhead by just not processing any inheritance code. @@ -849,7 +849,7 @@ class CodeGenerator(NodeVisitor): # find all blocks for block in node.find_all(nodes.Block): if block.name in self.blocks: - self.fail(f"block {block.name!r} defined twice", block.lineno) + self.fail(f"block {block.name!r} defined twice", block.lineno) self.blocks[block.name] = block # find all imports and import them @@ -857,32 +857,32 @@ class CodeGenerator(NodeVisitor): if import_.importname not in self.import_aliases: imp = import_.importname self.import_aliases[imp] = alias = self.temporary_identifier() - if "." in imp: - module, obj = imp.rsplit(".", 1) - self.writeline(f"from {module} import {obj} as {alias}") + if "." in imp: + module, obj = imp.rsplit(".", 1) + self.writeline(f"from {module} import {obj} as {alias}") else: - self.writeline(f"import {imp} as {alias}") + self.writeline(f"import {imp} as {alias}") # add the load name - self.writeline(f"name = {self.name!r}") + self.writeline(f"name = {self.name!r}") # generate the root render function. - self.writeline( - f"{self.func('root')}(context, missing=missing{envenv}):", extra=1 - ) + self.writeline( + f"{self.func('root')}(context, missing=missing{envenv}):", extra=1 + ) self.indent() self.write_commons() # process the root frame = Frame(eval_ctx) - if "self" in find_undeclared(node.body, ("self",)): - ref = frame.symbols.declare_parameter("self") - self.writeline(f"{ref} = TemplateReference(context)") + if "self" in find_undeclared(node.body, ("self",)): + ref = frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") frame.symbols.analyze_node(node) frame.toplevel = frame.rootlevel = True frame.require_output_check = have_extends and not self.has_known_extends if have_extends: - self.writeline("parent_template = None") + self.writeline("parent_template = None") self.enter_frame(frame) self.pull_dependencies(node.body) self.blockvisit(node.body, frame) @@ -893,55 +893,55 @@ class CodeGenerator(NodeVisitor): if have_extends: if not self.has_known_extends: self.indent() - self.writeline("if parent_template is not None:") + self.writeline("if parent_template is not None:") self.indent() - if not self.environment.is_async: - self.writeline("yield from parent_template.root_render_func(context)") + if not self.environment.is_async: + self.writeline("yield from parent_template.root_render_func(context)") else: - self.writeline( - "async for event in parent_template.root_render_func(context):" - ) + self.writeline( + "async for event in parent_template.root_render_func(context):" + ) self.indent() - self.writeline("yield event") + self.writeline("yield event") self.outdent() self.outdent(1 + (not self.has_known_extends)) # at this point we now have the blocks collected and can visit them too. - for name, block in self.blocks.items(): - self.writeline( - f"{self.func('block_' + name)}(context, missing=missing{envenv}):", - block, - 1, - ) + for name, block in self.blocks.items(): + self.writeline( + f"{self.func('block_' + name)}(context, missing=missing{envenv}):", + block, + 1, + ) self.indent() self.write_commons() # It's important that we do not make this frame a child of the # toplevel template. This would cause a variety of # interesting issues with identifier tracking. block_frame = Frame(eval_ctx) - block_frame.block_frame = True - undeclared = find_undeclared(block.body, ("self", "super")) - if "self" in undeclared: - ref = block_frame.symbols.declare_parameter("self") - self.writeline(f"{ref} = TemplateReference(context)") - if "super" in undeclared: - ref = block_frame.symbols.declare_parameter("super") - self.writeline(f"{ref} = context.super({name!r}, block_{name})") + block_frame.block_frame = True + undeclared = find_undeclared(block.body, ("self", "super")) + if "self" in undeclared: + ref = block_frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") + if "super" in undeclared: + ref = block_frame.symbols.declare_parameter("super") + self.writeline(f"{ref} = context.super({name!r}, block_{name})") block_frame.symbols.analyze_node(block) block_frame.block = name - self.writeline("_block_vars = {}") + self.writeline("_block_vars = {}") self.enter_frame(block_frame) self.pull_dependencies(block.body) self.blockvisit(block.body, block_frame) self.leave_frame(block_frame, with_python_scope=True) self.outdent() - blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks) - self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1) - debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info) - self.writeline(f"debug_info = {debug_kv_str!r}") + blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks) + self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1) + debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info) + self.writeline(f"debug_info = {debug_kv_str!r}") - def visit_Block(self, node: nodes.Block, frame: Frame) -> None: + def visit_Block(self, node: nodes.Block, frame: Frame) -> None: """Call a block and register it for the template.""" level = 0 if frame.toplevel: @@ -950,7 +950,7 @@ class CodeGenerator(NodeVisitor): if self.has_known_extends: return if self.extends_so_far > 0: - self.writeline("if parent_template is None:") + self.writeline("if parent_template is None:") self.indent() level += 1 @@ -959,35 +959,35 @@ class CodeGenerator(NodeVisitor): else: context = self.get_context_ref() - if node.required: - self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node) - self.indent() - self.writeline( - f'raise TemplateRuntimeError("Required block {node.name!r} not found")', - node, - ) - self.outdent() - - if not self.environment.is_async and frame.buffer is None: - self.writeline( - f"yield from context.blocks[{node.name!r}][0]({context})", node - ) + if node.required: + self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node) + self.indent() + self.writeline( + f'raise TemplateRuntimeError("Required block {node.name!r} not found")', + node, + ) + self.outdent() + + if not self.environment.is_async and frame.buffer is None: + self.writeline( + f"yield from context.blocks[{node.name!r}][0]({context})", node + ) else: - self.writeline( - f"{self.choose_async()}for event in" - f" context.blocks[{node.name!r}][0]({context}):", - node, - ) + self.writeline( + f"{self.choose_async()}for event in" + f" context.blocks[{node.name!r}][0]({context}):", + node, + ) self.indent() - self.simple_write("event", frame) + self.simple_write("event", frame) self.outdent() self.outdent(level) - def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None: + def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None: """Calls the extender.""" if not frame.toplevel: - self.fail("cannot use extend from a non top-level scope", node.lineno) + self.fail("cannot use extend from a non top-level scope", node.lineno) # if the number of extends statements in general is zero so # far, we don't have to add a check if something extended @@ -999,9 +999,9 @@ class CodeGenerator(NodeVisitor): # time too, but i welcome it not to confuse users by throwing the # same error at different times just "because we can". if not self.has_known_extends: - self.writeline("if parent_template is not None:") + self.writeline("if parent_template is not None:") self.indent() - self.writeline('raise TemplateRuntimeError("extended multiple times")') + self.writeline('raise TemplateRuntimeError("extended multiple times")') # if we have a known extends already we don't need that code here # as we know that the template execution will end here. @@ -1010,12 +1010,12 @@ class CodeGenerator(NodeVisitor): else: self.outdent() - self.writeline("parent_template = environment.get_template(", node) + self.writeline("parent_template = environment.get_template(", node) self.visit(node.template, frame) - self.write(f", {self.name!r})") - self.writeline("for name, parent_block in parent_template.blocks.items():") + self.write(f", {self.name!r})") + self.writeline("for name, parent_block in parent_template.blocks.items():") self.indent() - self.writeline("context.blocks.setdefault(name, []).append(parent_block)") + self.writeline("context.blocks.setdefault(name, []).append(parent_block)") self.outdent() # if this extends statement was in the root level we can take @@ -1027,88 +1027,88 @@ class CodeGenerator(NodeVisitor): # and now we have one more self.extends_so_far += 1 - def visit_Include(self, node: nodes.Include, frame: Frame) -> None: + def visit_Include(self, node: nodes.Include, frame: Frame) -> None: """Handles includes.""" if node.ignore_missing: - self.writeline("try:") + self.writeline("try:") self.indent() - func_name = "get_or_select_template" + func_name = "get_or_select_template" if isinstance(node.template, nodes.Const): - if isinstance(node.template.value, str): - func_name = "get_template" + if isinstance(node.template.value, str): + func_name = "get_template" elif isinstance(node.template.value, (tuple, list)): - func_name = "select_template" + func_name = "select_template" elif isinstance(node.template, (nodes.Tuple, nodes.List)): - func_name = "select_template" + func_name = "select_template" - self.writeline(f"template = environment.{func_name}(", node) + self.writeline(f"template = environment.{func_name}(", node) self.visit(node.template, frame) - self.write(f", {self.name!r})") + self.write(f", {self.name!r})") if node.ignore_missing: self.outdent() - self.writeline("except TemplateNotFound:") + self.writeline("except TemplateNotFound:") self.indent() - self.writeline("pass") + self.writeline("pass") self.outdent() - self.writeline("else:") + self.writeline("else:") self.indent() skip_event_yield = False if node.with_context: - self.writeline( - f"{self.choose_async()}for event in template.root_render_func(" - "template.new_context(context.get_all(), True," - f" {self.dump_local_context(frame)})):" - ) + self.writeline( + f"{self.choose_async()}for event in template.root_render_func(" + "template.new_context(context.get_all(), True," + f" {self.dump_local_context(frame)})):" + ) elif self.environment.is_async: - self.writeline( - "for event in (await template._get_default_module_async())" - "._body_stream:" - ) + self.writeline( + "for event in (await template._get_default_module_async())" + "._body_stream:" + ) else: - self.writeline("yield from template._get_default_module()._body_stream") - skip_event_yield = True + self.writeline("yield from template._get_default_module()._body_stream") + skip_event_yield = True if not skip_event_yield: self.indent() - self.simple_write("event", frame) + self.simple_write("event", frame) self.outdent() if node.ignore_missing: self.outdent() - def _import_common( - self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame - ) -> None: - self.write(f"{self.choose_async('await ')}environment.get_template(") + def _import_common( + self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame + ) -> None: + self.write(f"{self.choose_async('await ')}environment.get_template(") self.visit(node.template, frame) - self.write(f", {self.name!r}).") - + self.write(f", {self.name!r}).") + if node.with_context: - f_name = f"make_module{self.choose_async('_async')}" - self.write( - f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})" - ) + f_name = f"make_module{self.choose_async('_async')}" + self.write( + f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})" + ) else: - self.write(f"_get_default_module{self.choose_async('_async')}(context)") - - def visit_Import(self, node: nodes.Import, frame: Frame) -> None: - """Visit regular imports.""" - self.writeline(f"{frame.symbols.ref(node.target)} = ", node) - if frame.toplevel: - self.write(f"context.vars[{node.target!r}] = ") - - self._import_common(node, frame) - - if frame.toplevel and not node.target.startswith("_"): - self.writeline(f"context.exported_vars.discard({node.target!r})") - - def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None: + self.write(f"_get_default_module{self.choose_async('_async')}(context)") + + def visit_Import(self, node: nodes.Import, frame: Frame) -> None: + """Visit regular imports.""" + self.writeline(f"{frame.symbols.ref(node.target)} = ", node) + if frame.toplevel: + self.write(f"context.vars[{node.target!r}] = ") + + self._import_common(node, frame) + + if frame.toplevel and not node.target.startswith("_"): + self.writeline(f"context.exported_vars.discard({node.target!r})") + + def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None: """Visit named imports.""" self.newline(node) - self.write("included_template = ") - self._import_common(node, frame) + self.write("included_template = ") + self._import_common(node, frame) var_names = [] discarded_names = [] for name in node.names: @@ -1116,85 +1116,85 @@ class CodeGenerator(NodeVisitor): name, alias = name else: alias = name - self.writeline( - f"{frame.symbols.ref(alias)} =" - f" getattr(included_template, {name!r}, missing)" - ) - self.writeline(f"if {frame.symbols.ref(alias)} is missing:") + self.writeline( + f"{frame.symbols.ref(alias)} =" + f" getattr(included_template, {name!r}, missing)" + ) + self.writeline(f"if {frame.symbols.ref(alias)} is missing:") self.indent() - message = ( - "the template {included_template.__name__!r}" - f" (imported on {self.position(node)})" - f" does not export the requested name {name!r}" - ) - self.writeline( - f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})" - ) + message = ( + "the template {included_template.__name__!r}" + f" (imported on {self.position(node)})" + f" does not export the requested name {name!r}" + ) + self.writeline( + f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})" + ) self.outdent() if frame.toplevel: var_names.append(alias) - if not alias.startswith("_"): + if not alias.startswith("_"): discarded_names.append(alias) if var_names: if len(var_names) == 1: name = var_names[0] - self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}") + self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}") else: - names_kv = ", ".join( - f"{name!r}: {frame.symbols.ref(name)}" for name in var_names - ) - self.writeline(f"context.vars.update({{{names_kv}}})") + names_kv = ", ".join( + f"{name!r}: {frame.symbols.ref(name)}" for name in var_names + ) + self.writeline(f"context.vars.update({{{names_kv}}})") if discarded_names: if len(discarded_names) == 1: - self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})") + self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})") else: - names_str = ", ".join(map(repr, discarded_names)) - self.writeline( - f"context.exported_vars.difference_update(({names_str}))" - ) + names_str = ", ".join(map(repr, discarded_names)) + self.writeline( + f"context.exported_vars.difference_update(({names_str}))" + ) - def visit_For(self, node: nodes.For, frame: Frame) -> None: + def visit_For(self, node: nodes.For, frame: Frame) -> None: loop_frame = frame.inner() - loop_frame.loop_frame = True + loop_frame.loop_frame = True test_frame = frame.inner() else_frame = frame.inner() # try to figure out if we have an extended loop. An extended loop # is necessary if the loop is in recursive mode if the special loop - # variable is accessed in the body if the body is a scoped block. - extended_loop = ( - node.recursive - or "loop" - in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",)) - or any(block.scoped for block in node.find_all(nodes.Block)) - ) + # variable is accessed in the body if the body is a scoped block. + extended_loop = ( + node.recursive + or "loop" + in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",)) + or any(block.scoped for block in node.find_all(nodes.Block)) + ) loop_ref = None if extended_loop: - loop_ref = loop_frame.symbols.declare_parameter("loop") + loop_ref = loop_frame.symbols.declare_parameter("loop") - loop_frame.symbols.analyze_node(node, for_branch="body") + loop_frame.symbols.analyze_node(node, for_branch="body") if node.else_: - else_frame.symbols.analyze_node(node, for_branch="else") + else_frame.symbols.analyze_node(node, for_branch="else") if node.test: loop_filter_func = self.temporary_identifier() - test_frame.symbols.analyze_node(node, for_branch="test") - self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test) + test_frame.symbols.analyze_node(node, for_branch="test") + self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test) self.indent() self.enter_frame(test_frame) - self.writeline(self.choose_async("async for ", "for ")) + self.writeline(self.choose_async("async for ", "for ")) self.visit(node.target, loop_frame) - self.write(" in ") - self.write(self.choose_async("auto_aiter(fiter)", "fiter")) - self.write(":") + self.write(" in ") + self.write(self.choose_async("auto_aiter(fiter)", "fiter")) + self.write(":") self.indent() - self.writeline("if ", node.test) + self.writeline("if ", node.test) self.visit(node.test, test_frame) - self.write(":") + self.write(":") self.indent() - self.writeline("yield ") + self.writeline("yield ") self.visit(node.target, loop_frame) self.outdent(3) self.leave_frame(test_frame, with_python_scope=True) @@ -1203,9 +1203,9 @@ class CodeGenerator(NodeVisitor): # variables at that point. Because loops can be nested but the loop # variable is a special one we have to enforce aliasing for it. if node.recursive: - self.writeline( - f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node - ) + self.writeline( + f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node + ) self.indent() self.buffer(loop_frame) @@ -1215,58 +1215,58 @@ class CodeGenerator(NodeVisitor): # make sure the loop variable is a special one and raise a template # assertion error if a loop tries to write to loop if extended_loop: - self.writeline(f"{loop_ref} = missing") + self.writeline(f"{loop_ref} = missing") for name in node.find_all(nodes.Name): - if name.ctx == "store" and name.name == "loop": - self.fail( - "Can't assign to special loop variable in for-loop target", - name.lineno, - ) + if name.ctx == "store" and name.name == "loop": + self.fail( + "Can't assign to special loop variable in for-loop target", + name.lineno, + ) if node.else_: iteration_indicator = self.temporary_identifier() - self.writeline(f"{iteration_indicator} = 1") + self.writeline(f"{iteration_indicator} = 1") - self.writeline(self.choose_async("async for ", "for "), node) + self.writeline(self.choose_async("async for ", "for "), node) self.visit(node.target, loop_frame) if extended_loop: - self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(") + self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(") else: - self.write(" in ") + self.write(" in ") if node.test: - self.write(f"{loop_filter_func}(") + self.write(f"{loop_filter_func}(") if node.recursive: - self.write("reciter") + self.write("reciter") else: if self.environment.is_async and not extended_loop: - self.write("auto_aiter(") + self.write("auto_aiter(") self.visit(node.iter, frame) if self.environment.is_async and not extended_loop: - self.write(")") + self.write(")") if node.test: - self.write(")") + self.write(")") if node.recursive: - self.write(", undefined, loop_render_func, depth):") + self.write(", undefined, loop_render_func, depth):") else: - self.write(", undefined):" if extended_loop else ":") + self.write(", undefined):" if extended_loop else ":") self.indent() self.enter_frame(loop_frame) - self.writeline("_loop_vars = {}") + self.writeline("_loop_vars = {}") self.blockvisit(node.body, loop_frame) if node.else_: - self.writeline(f"{iteration_indicator} = 0") + self.writeline(f"{iteration_indicator} = 0") self.outdent() - self.leave_frame( - loop_frame, with_python_scope=node.recursive and not node.else_ - ) + self.leave_frame( + loop_frame, with_python_scope=node.recursive and not node.else_ + ) if node.else_: - self.writeline(f"if {iteration_indicator}:") + self.writeline(f"if {iteration_indicator}:") self.indent() self.enter_frame(else_frame) self.blockvisit(node.else_, else_frame) @@ -1279,60 +1279,60 @@ class CodeGenerator(NodeVisitor): self.return_buffer_contents(loop_frame) self.outdent() self.start_write(frame, node) - self.write(f"{self.choose_async('await ')}loop(") + self.write(f"{self.choose_async('await ')}loop(") if self.environment.is_async: - self.write("auto_aiter(") + self.write("auto_aiter(") self.visit(node.iter, frame) if self.environment.is_async: - self.write(")") - self.write(", loop)") + self.write(")") + self.write(", loop)") self.end_write(frame) - # at the end of the iteration, clear any assignments made in the - # loop from the top level - if self._assign_stack: - self._assign_stack[-1].difference_update(loop_frame.symbols.stores) - - def visit_If(self, node: nodes.If, frame: Frame) -> None: + # at the end of the iteration, clear any assignments made in the + # loop from the top level + if self._assign_stack: + self._assign_stack[-1].difference_update(loop_frame.symbols.stores) + + def visit_If(self, node: nodes.If, frame: Frame) -> None: if_frame = frame.soft() - self.writeline("if ", node) + self.writeline("if ", node) self.visit(node.test, if_frame) - self.write(":") + self.write(":") self.indent() self.blockvisit(node.body, if_frame) self.outdent() for elif_ in node.elif_: - self.writeline("elif ", elif_) + self.writeline("elif ", elif_) self.visit(elif_.test, if_frame) - self.write(":") + self.write(":") self.indent() self.blockvisit(elif_.body, if_frame) self.outdent() if node.else_: - self.writeline("else:") + self.writeline("else:") self.indent() self.blockvisit(node.else_, if_frame) self.outdent() - def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None: + def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None: macro_frame, macro_ref = self.macro_body(node, frame) self.newline() if frame.toplevel: - if not node.name.startswith("_"): - self.write(f"context.exported_vars.add({node.name!r})") - self.writeline(f"context.vars[{node.name!r}] = ") - self.write(f"{frame.symbols.ref(node.name)} = ") + if not node.name.startswith("_"): + self.write(f"context.exported_vars.add({node.name!r})") + self.writeline(f"context.vars[{node.name!r}] = ") + self.write(f"{frame.symbols.ref(node.name)} = ") self.macro_def(macro_ref, macro_frame) - def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None: + def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None: call_frame, macro_ref = self.macro_body(node, frame) - self.writeline("caller = ") + self.writeline("caller = ") self.macro_def(macro_ref, call_frame) self.start_write(frame, node) self.visit_Call(node.call, frame, forward_caller=True) self.end_write(frame) - def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None: + def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None: filter_frame = frame.inner() filter_frame.symbols.analyze_node(node) self.enter_frame(filter_frame) @@ -1343,227 +1343,227 @@ class CodeGenerator(NodeVisitor): self.end_write(frame) self.leave_frame(filter_frame) - def visit_With(self, node: nodes.With, frame: Frame) -> None: + def visit_With(self, node: nodes.With, frame: Frame) -> None: with_frame = frame.inner() with_frame.symbols.analyze_node(node) self.enter_frame(with_frame) - for target, expr in zip(node.targets, node.values): + for target, expr in zip(node.targets, node.values): self.newline() self.visit(target, with_frame) - self.write(" = ") + self.write(" = ") self.visit(expr, frame) self.blockvisit(node.body, with_frame) self.leave_frame(with_frame) - def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None: + def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None: self.newline(node) self.visit(node.node, frame) - class _FinalizeInfo(t.NamedTuple): - const: t.Optional[t.Callable[..., str]] - src: t.Optional[str] - - @staticmethod - def _default_finalize(value: t.Any) -> t.Any: - """The default finalize function if the environment isn't - configured with one. Or, if the environment has one, this is - called on that function's output for constants. - """ - return str(value) - - _finalize: t.Optional[_FinalizeInfo] = None - - def _make_finalize(self) -> _FinalizeInfo: - """Build the finalize function to be used on constants and at - runtime. Cached so it's only created once for all output nodes. - - Returns a ``namedtuple`` with the following attributes: - - ``const`` - A function to finalize constant data at compile time. - - ``src`` - Source code to output around nodes to be evaluated at - runtime. - """ - if self._finalize is not None: - return self._finalize - - finalize: t.Optional[t.Callable[..., t.Any]] - finalize = default = self._default_finalize - src = None - + class _FinalizeInfo(t.NamedTuple): + const: t.Optional[t.Callable[..., str]] + src: t.Optional[str] + + @staticmethod + def _default_finalize(value: t.Any) -> t.Any: + """The default finalize function if the environment isn't + configured with one. Or, if the environment has one, this is + called on that function's output for constants. + """ + return str(value) + + _finalize: t.Optional[_FinalizeInfo] = None + + def _make_finalize(self) -> _FinalizeInfo: + """Build the finalize function to be used on constants and at + runtime. Cached so it's only created once for all output nodes. + + Returns a ``namedtuple`` with the following attributes: + + ``const`` + A function to finalize constant data at compile time. + + ``src`` + Source code to output around nodes to be evaluated at + runtime. + """ + if self._finalize is not None: + return self._finalize + + finalize: t.Optional[t.Callable[..., t.Any]] + finalize = default = self._default_finalize + src = None + if self.environment.finalize: - src = "environment.finalize(" - env_finalize = self.environment.finalize - pass_arg = { - _PassArg.context: "context", - _PassArg.eval_context: "context.eval_ctx", - _PassArg.environment: "environment", - }.get( - _PassArg.from_obj(env_finalize) # type: ignore - ) - finalize = None - - if pass_arg is None: - - def finalize(value: t.Any) -> t.Any: - return default(env_finalize(value)) - - else: - src = f"{src}{pass_arg}, " - - if pass_arg == "environment": - - def finalize(value: t.Any) -> t.Any: - return default(env_finalize(self.environment, value)) - - self._finalize = self._FinalizeInfo(finalize, src) - return self._finalize - - def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: - """Given a group of constant values converted from ``Output`` - child nodes, produce a string to write to the template module - source. - """ - return repr(concat(group)) - - def _output_child_to_const( - self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo - ) -> str: - """Try to optimize a child of an ``Output`` node by trying to - convert it to constant, finalized data at compile time. - - If :exc:`Impossible` is raised, the node is not constant and - will be evaluated at runtime. Any other exception will also be - evaluated at runtime for easier debugging. - """ - const = node.as_const(frame.eval_ctx) - - if frame.eval_ctx.autoescape: - const = escape(const) - - # Template data doesn't go through finalize. - if isinstance(node, nodes.TemplateData): - return str(const) - - return finalize.const(const) # type: ignore - - def _output_child_pre( - self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo - ) -> None: - """Output extra source code before visiting a child of an - ``Output`` node. - """ - if frame.eval_ctx.volatile: - self.write("(escape if context.eval_ctx.autoescape else str)(") - elif frame.eval_ctx.autoescape: - self.write("escape(") + src = "environment.finalize(" + env_finalize = self.environment.finalize + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(env_finalize) # type: ignore + ) + finalize = None + + if pass_arg is None: + + def finalize(value: t.Any) -> t.Any: + return default(env_finalize(value)) + + else: + src = f"{src}{pass_arg}, " + + if pass_arg == "environment": + + def finalize(value: t.Any) -> t.Any: + return default(env_finalize(self.environment, value)) + + self._finalize = self._FinalizeInfo(finalize, src) + return self._finalize + + def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: + """Given a group of constant values converted from ``Output`` + child nodes, produce a string to write to the template module + source. + """ + return repr(concat(group)) + + def _output_child_to_const( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> str: + """Try to optimize a child of an ``Output`` node by trying to + convert it to constant, finalized data at compile time. + + If :exc:`Impossible` is raised, the node is not constant and + will be evaluated at runtime. Any other exception will also be + evaluated at runtime for easier debugging. + """ + const = node.as_const(frame.eval_ctx) + + if frame.eval_ctx.autoescape: + const = escape(const) + + # Template data doesn't go through finalize. + if isinstance(node, nodes.TemplateData): + return str(const) + + return finalize.const(const) # type: ignore + + def _output_child_pre( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code before visiting a child of an + ``Output`` node. + """ + if frame.eval_ctx.volatile: + self.write("(escape if context.eval_ctx.autoescape else str)(") + elif frame.eval_ctx.autoescape: + self.write("escape(") else: - self.write("str(") - - if finalize.src is not None: - self.write(finalize.src) - - def _output_child_post( - self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo - ) -> None: - """Output extra source code after visiting a child of an - ``Output`` node. - """ - self.write(")") - - if finalize.src is not None: - self.write(")") - - def visit_Output(self, node: nodes.Output, frame: Frame) -> None: - # If an extends is active, don't render outside a block. + self.write("str(") + + if finalize.src is not None: + self.write(finalize.src) + + def _output_child_post( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code after visiting a child of an + ``Output`` node. + """ + self.write(")") + + if finalize.src is not None: + self.write(")") + + def visit_Output(self, node: nodes.Output, frame: Frame) -> None: + # If an extends is active, don't render outside a block. if frame.require_output_check: - # A top-level extends is known to exist at compile time. - if self.has_known_extends: - return - - self.writeline("if parent_template is None:") + # A top-level extends is known to exist at compile time. + if self.has_known_extends: + return + + self.writeline("if parent_template is None:") self.indent() - finalize = self._make_finalize() - body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = [] - - # Evaluate constants at compile time if possible. Each item in - # body will be either a list of static data or a node to be - # evaluated at runtime. + finalize = self._make_finalize() + body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = [] + + # Evaluate constants at compile time if possible. Each item in + # body will be either a list of static data or a node to be + # evaluated at runtime. for child in node.nodes: try: - if not ( - # If the finalize function requires runtime context, - # constants can't be evaluated at compile time. - finalize.const - # Unless it's basic template data that won't be - # finalized anyway. - or isinstance(child, nodes.TemplateData) - ): + if not ( + # If the finalize function requires runtime context, + # constants can't be evaluated at compile time. + finalize.const + # Unless it's basic template data that won't be + # finalized anyway. + or isinstance(child, nodes.TemplateData) + ): raise nodes.Impossible() - - const = self._output_child_to_const(child, frame, finalize) - except (nodes.Impossible, Exception): - # The node was not constant and needs to be evaluated at - # runtime. Or another error was raised, which is easier - # to debug at runtime. + + const = self._output_child_to_const(child, frame, finalize) + except (nodes.Impossible, Exception): + # The node was not constant and needs to be evaluated at + # runtime. Or another error was raised, which is easier + # to debug at runtime. body.append(child) continue - + if body and isinstance(body[-1], list): body[-1].append(const) else: body.append([const]) - if frame.buffer is not None: - if len(body) == 1: - self.writeline(f"{frame.buffer}.append(") - else: - self.writeline(f"{frame.buffer}.extend((") - - self.indent() - - for item in body: - if isinstance(item, list): - # A group of constant data to join and output. - val = self._output_const_repr(item) - - if frame.buffer is None: - self.writeline("yield " + val) + if frame.buffer is not None: + if len(body) == 1: + self.writeline(f"{frame.buffer}.append(") + else: + self.writeline(f"{frame.buffer}.extend((") + + self.indent() + + for item in body: + if isinstance(item, list): + # A group of constant data to join and output. + val = self._output_const_repr(item) + + if frame.buffer is None: + self.writeline("yield " + val) else: - self.writeline(val + ",") - else: - if frame.buffer is None: - self.writeline("yield ", item) + self.writeline(val + ",") + else: + if frame.buffer is None: + self.writeline("yield ", item) else: - self.newline(item) - - # A node to be evaluated at runtime. - self._output_child_pre(item, frame, finalize) - self.visit(item, frame) - self._output_child_post(item, frame, finalize) - - if frame.buffer is not None: - self.write(",") - - if frame.buffer is not None: + self.newline(item) + + # A node to be evaluated at runtime. + self._output_child_pre(item, frame, finalize) + self.visit(item, frame) + self._output_child_post(item, frame, finalize) + + if frame.buffer is not None: + self.write(",") + + if frame.buffer is not None: self.outdent() - self.writeline(")" if len(body) == 1 else "))") + self.writeline(")" if len(body) == 1 else "))") - if frame.require_output_check: + if frame.require_output_check: self.outdent() - def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None: + def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None: self.push_assign_tracking() self.newline(node) self.visit(node.target, frame) - self.write(" = ") + self.write(" = ") self.visit(node.node, frame) self.pop_assign_tracking(frame) - def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None: + def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None: self.push_assign_tracking() block_frame = frame.inner() # This is a special case. Since a set block always captures we @@ -1576,21 +1576,21 @@ class CodeGenerator(NodeVisitor): self.blockvisit(node.body, block_frame) self.newline(node) self.visit(node.target, frame) - self.write(" = (Markup if context.eval_ctx.autoescape else identity)(") + self.write(" = (Markup if context.eval_ctx.autoescape else identity)(") if node.filter is not None: self.visit_Filter(node.filter, block_frame) else: - self.write(f"concat({block_frame.buffer})") - self.write(")") + self.write(f"concat({block_frame.buffer})") + self.write(")") self.pop_assign_tracking(frame) self.leave_frame(block_frame) # -- Expression Visitors - def visit_Name(self, node: nodes.Name, frame: Frame) -> None: - if node.ctx == "store" and ( - frame.toplevel or frame.loop_frame or frame.block_frame - ): + def visit_Name(self, node: nodes.Name, frame: Frame) -> None: + if node.ctx == "store" and ( + frame.toplevel or frame.loop_frame or frame.block_frame + ): if self._assign_stack: self._assign_stack[-1].add(node.name) ref = frame.symbols.ref(node.name) @@ -1598,329 +1598,329 @@ class CodeGenerator(NodeVisitor): # If we are looking up a variable we might have to deal with the # case where it's undefined. We can skip that case if the load # instruction indicates a parameter which are always defined. - if node.ctx == "load": + if node.ctx == "load": load = frame.symbols.find_load(ref) - if not ( - load is not None - and load[0] == VAR_LOAD_PARAMETER - and not self.parameter_is_undeclared(ref) - ): - self.write( - f"(undefined(name={node.name!r}) if {ref} is missing else {ref})" - ) + if not ( + load is not None + and load[0] == VAR_LOAD_PARAMETER + and not self.parameter_is_undeclared(ref) + ): + self.write( + f"(undefined(name={node.name!r}) if {ref} is missing else {ref})" + ) return self.write(ref) - def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None: + def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None: # NSRefs can only be used to store values; since they use the normal # `foo.bar` notation they will be parsed as a normal attribute access # when used anywhere but in a `set` context ref = frame.symbols.ref(node.name) - self.writeline(f"if not isinstance({ref}, Namespace):") + self.writeline(f"if not isinstance({ref}, Namespace):") self.indent() - self.writeline( - "raise TemplateRuntimeError" - '("cannot assign attribute on non-namespace object")' - ) + self.writeline( + "raise TemplateRuntimeError" + '("cannot assign attribute on non-namespace object")' + ) self.outdent() - self.writeline(f"{ref}[{node.attr!r}]") + self.writeline(f"{ref}[{node.attr!r}]") - def visit_Const(self, node: nodes.Const, frame: Frame) -> None: + def visit_Const(self, node: nodes.Const, frame: Frame) -> None: val = node.as_const(frame.eval_ctx) if isinstance(val, float): self.write(str(val)) else: self.write(repr(val)) - def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None: + def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None: try: self.write(repr(node.as_const(frame.eval_ctx))) except nodes.Impossible: - self.write( - f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})" - ) + self.write( + f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})" + ) - def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None: - self.write("(") + def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None: + self.write("(") idx = -1 for idx, item in enumerate(node.items): if idx: - self.write(", ") + self.write(", ") self.visit(item, frame) - self.write(",)" if idx == 0 else ")") + self.write(",)" if idx == 0 else ")") - def visit_List(self, node: nodes.List, frame: Frame) -> None: - self.write("[") + def visit_List(self, node: nodes.List, frame: Frame) -> None: + self.write("[") for idx, item in enumerate(node.items): if idx: - self.write(", ") + self.write(", ") self.visit(item, frame) - self.write("]") + self.write("]") - def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None: - self.write("{") + def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None: + self.write("{") for idx, item in enumerate(node.items): if idx: - self.write(", ") + self.write(", ") self.visit(item.key, frame) - self.write(": ") + self.write(": ") self.visit(item.value, frame) - self.write("}") - - visit_Add = _make_binop("+") - visit_Sub = _make_binop("-") - visit_Mul = _make_binop("*") - visit_Div = _make_binop("/") - visit_FloorDiv = _make_binop("//") - visit_Pow = _make_binop("**") - visit_Mod = _make_binop("%") - visit_And = _make_binop("and") - visit_Or = _make_binop("or") - visit_Pos = _make_unop("+") - visit_Neg = _make_unop("-") - visit_Not = _make_unop("not ") - + self.write("}") + + visit_Add = _make_binop("+") + visit_Sub = _make_binop("-") + visit_Mul = _make_binop("*") + visit_Div = _make_binop("/") + visit_FloorDiv = _make_binop("//") + visit_Pow = _make_binop("**") + visit_Mod = _make_binop("%") + visit_And = _make_binop("and") + visit_Or = _make_binop("or") + visit_Pos = _make_unop("+") + visit_Neg = _make_unop("-") + visit_Not = _make_unop("not ") + @optimizeconst - def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None: + def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None: if frame.eval_ctx.volatile: - func_name = "(markup_join if context.eval_ctx.volatile else str_join)" + func_name = "(markup_join if context.eval_ctx.volatile else str_join)" elif frame.eval_ctx.autoescape: - func_name = "markup_join" + func_name = "markup_join" else: - func_name = "str_join" - self.write(f"{func_name}((") + func_name = "str_join" + self.write(f"{func_name}((") for arg in node.nodes: self.visit(arg, frame) - self.write(", ") - self.write("))") + self.write(", ") + self.write("))") @optimizeconst - def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None: - self.write("(") + def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None: + self.write("(") self.visit(node.expr, frame) for op in node.ops: self.visit(op, frame) - self.write(")") + self.write(")") - def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None: - self.write(f" {operators[node.op]} ") + def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None: + self.write(f" {operators[node.op]} ") self.visit(node.expr, frame) @optimizeconst - def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None: - if self.environment.is_async: - self.write("(await auto_await(") - - self.write("environment.getattr(") + def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None: + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getattr(") self.visit(node.node, frame) - self.write(f", {node.attr!r})") - - if self.environment.is_async: - self.write("))") + self.write(f", {node.attr!r})") + if self.environment.is_async: + self.write("))") + @optimizeconst - def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None: + def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None: # slices bypass the environment getitem method. if isinstance(node.arg, nodes.Slice): self.visit(node.node, frame) - self.write("[") + self.write("[") self.visit(node.arg, frame) - self.write("]") + self.write("]") else: - if self.environment.is_async: - self.write("(await auto_await(") - - self.write("environment.getitem(") + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getitem(") self.visit(node.node, frame) - self.write(", ") + self.write(", ") self.visit(node.arg, frame) - self.write(")") - - if self.environment.is_async: - self.write("))") + self.write(")") - def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None: + if self.environment.is_async: + self.write("))") + + def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None: if node.start is not None: self.visit(node.start, frame) - self.write(":") + self.write(":") if node.stop is not None: self.visit(node.stop, frame) if node.step is not None: - self.write(":") + self.write(":") self.visit(node.step, frame) - @contextmanager - def _filter_test_common( - self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool - ) -> t.Iterator[None]: + @contextmanager + def _filter_test_common( + 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]}(") - func = self.environment.filters.get(node.name) + if is_filter: + self.write(f"{self.filters[node.name]}(") + func = self.environment.filters.get(node.name) else: - self.write(f"{self.tests[node.name]}(") - func = self.environment.tests.get(node.name) - - # When inside an If or CondExpr frame, allow the filter to be - # undefined at compile time and only raise an error if it's - # actually called at runtime. See pull_dependencies. - if func is None and not frame.soft_frame: - type_name = "filter" if is_filter else "test" - self.fail(f"No {type_name} named {node.name!r}.", node.lineno) - - pass_arg = { - _PassArg.context: "context", - _PassArg.eval_context: "context.eval_ctx", - _PassArg.environment: "environment", - }.get( - _PassArg.from_obj(func) # type: ignore - ) - - if pass_arg is not None: - self.write(f"{pass_arg}, ") - - # Back to the visitor function to handle visiting the target of - # the filter or test. - yield - + self.write(f"{self.tests[node.name]}(") + func = self.environment.tests.get(node.name) + + # When inside an If or CondExpr frame, allow the filter to be + # undefined at compile time and only raise an error if it's + # actually called at runtime. See pull_dependencies. + if func is None and not frame.soft_frame: + type_name = "filter" if is_filter else "test" + self.fail(f"No {type_name} named {node.name!r}.", node.lineno) + + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(func) # type: ignore + ) + + if pass_arg is not None: + self.write(f"{pass_arg}, ") + + # Back to the visitor function to handle visiting the target of + # the filter or test. + yield + self.signature(node, frame) - self.write(")") - + self.write(")") + if self.environment.is_async: - self.write(")") + self.write(")") @optimizeconst - def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: - with self._filter_test_common(node, frame, True): - # if the filter node is None we are inside a filter block - # and want to write to the current buffer - if node.node is not None: - self.visit(node.node, frame) - elif frame.eval_ctx.volatile: - self.write( - f"(Markup(concat({frame.buffer}))" - f" if context.eval_ctx.autoescape else concat({frame.buffer}))" - ) - elif frame.eval_ctx.autoescape: - self.write(f"Markup(concat({frame.buffer}))") - else: - self.write(f"concat({frame.buffer})") + def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: + with self._filter_test_common(node, frame, True): + # if the filter node is None we are inside a filter block + # and want to write to the current buffer + if node.node is not None: + self.visit(node.node, frame) + elif frame.eval_ctx.volatile: + self.write( + f"(Markup(concat({frame.buffer}))" + f" if context.eval_ctx.autoescape else concat({frame.buffer}))" + ) + elif frame.eval_ctx.autoescape: + self.write(f"Markup(concat({frame.buffer}))") + else: + self.write(f"concat({frame.buffer})") @optimizeconst - def visit_Test(self, node: nodes.Test, frame: Frame) -> None: - with self._filter_test_common(node, frame, False): - self.visit(node.node, frame) - - @optimizeconst - def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None: - frame = frame.soft() - - def write_expr2() -> None: + def visit_Test(self, node: nodes.Test, frame: Frame) -> None: + with self._filter_test_common(node, frame, False): + self.visit(node.node, frame) + + @optimizeconst + def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None: + frame = frame.soft() + + def write_expr2() -> None: if node.expr2 is not None: - self.visit(node.expr2, frame) - return - - self.write( - f'cond_expr_undefined("the inline if-expression on' - f" {self.position(node)} evaluated to false and no else" - f' section was defined.")' - ) - - self.write("(") + self.visit(node.expr2, frame) + return + + self.write( + f'cond_expr_undefined("the inline if-expression on' + f" {self.position(node)} evaluated to false and no else" + f' section was defined.")' + ) + + self.write("(") self.visit(node.expr1, frame) - self.write(" if ") + self.write(" if ") self.visit(node.test, frame) - self.write(" else ") + self.write(" else ") write_expr2() - self.write(")") + self.write(")") @optimizeconst - def visit_Call( - self, node: nodes.Call, frame: Frame, forward_caller: bool = False - ) -> None: + def visit_Call( + 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, ") + self.write("environment.call(context, ") else: - self.write("context.call(") + self.write("context.call(") self.visit(node.node, frame) - extra_kwargs = {"caller": "caller"} if forward_caller else None - loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {} - block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {} - if extra_kwargs: - extra_kwargs.update(loop_kwargs, **block_kwargs) - elif loop_kwargs or block_kwargs: - extra_kwargs = dict(loop_kwargs, **block_kwargs) + extra_kwargs = {"caller": "caller"} if forward_caller else None + loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {} + block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {} + if extra_kwargs: + extra_kwargs.update(loop_kwargs, **block_kwargs) + elif loop_kwargs or block_kwargs: + extra_kwargs = dict(loop_kwargs, **block_kwargs) self.signature(node, frame, extra_kwargs) - self.write(")") + 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 + "=") + def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None: + self.write(node.key + "=") self.visit(node.value, frame) # -- Unused nodes for extensions - def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None: - self.write("Markup(") + def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None: + self.write("Markup(") self.visit(node.expr, frame) - self.write(")") + self.write(")") - def visit_MarkSafeIfAutoescape( - self, node: nodes.MarkSafeIfAutoescape, frame: Frame - ) -> None: - self.write("(Markup if context.eval_ctx.autoescape else identity)(") + def visit_MarkSafeIfAutoescape( + self, node: nodes.MarkSafeIfAutoescape, frame: Frame + ) -> None: + self.write("(Markup if context.eval_ctx.autoescape else identity)(") self.visit(node.expr, frame) - self.write(")") + self.write(")") - def visit_EnvironmentAttribute( - self, node: nodes.EnvironmentAttribute, frame: Frame - ) -> None: - self.write("environment." + node.name) + def visit_EnvironmentAttribute( + self, node: nodes.EnvironmentAttribute, frame: Frame + ) -> None: + self.write("environment." + node.name) - def visit_ExtensionAttribute( - self, node: nodes.ExtensionAttribute, frame: Frame - ) -> None: - self.write(f"environment.extensions[{node.identifier!r}].{node.name}") + def visit_ExtensionAttribute( + self, node: nodes.ExtensionAttribute, frame: Frame + ) -> None: + self.write(f"environment.extensions[{node.identifier!r}].{node.name}") - def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None: + def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None: self.write(self.import_aliases[node.importname]) - def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None: + def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None: self.write(node.name) - def visit_ContextReference( - self, node: nodes.ContextReference, frame: Frame - ) -> None: - self.write("context") - - def visit_DerivedContextReference( - self, node: nodes.DerivedContextReference, frame: Frame - ) -> None: - self.write(self.derive_context(frame)) + def visit_ContextReference( + self, node: nodes.ContextReference, frame: Frame + ) -> None: + self.write("context") - def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None: - self.writeline("continue", node) + def visit_DerivedContextReference( + self, node: nodes.DerivedContextReference, frame: Frame + ) -> None: + self.write(self.derive_context(frame)) + + def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None: + self.writeline("continue", node) - def visit_Break(self, node: nodes.Break, frame: Frame) -> None: - self.writeline("break", node) + def visit_Break(self, node: nodes.Break, frame: Frame) -> None: + self.writeline("break", node) - def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None: + def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None: scope_frame = frame.inner() scope_frame.symbols.analyze_node(node) self.enter_frame(scope_frame) self.blockvisit(node.body, scope_frame) self.leave_frame(scope_frame) - def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None: + def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None: ctx = self.temporary_identifier() - self.writeline(f"{ctx} = {self.derive_context(frame)}") - self.writeline(f"{ctx}.vars = ") + self.writeline(f"{ctx} = {self.derive_context(frame)}") + self.writeline(f"{ctx}.vars = ") self.visit(node.context, frame) self.push_context_reference(ctx) @@ -1931,11 +1931,11 @@ class CodeGenerator(NodeVisitor): self.leave_frame(scope_frame) self.pop_context_reference() - def visit_EvalContextModifier( - self, node: nodes.EvalContextModifier, frame: Frame - ) -> None: + def visit_EvalContextModifier( + self, node: nodes.EvalContextModifier, frame: Frame + ) -> None: for keyword in node.options: - self.writeline(f"context.eval_ctx.{keyword.key} = ") + self.writeline(f"context.eval_ctx.{keyword.key} = ") self.visit(keyword.value, frame) try: val = keyword.value.as_const(frame.eval_ctx) @@ -1944,14 +1944,14 @@ class CodeGenerator(NodeVisitor): else: setattr(frame.eval_ctx, keyword.key, val) - def visit_ScopedEvalContextModifier( - self, node: nodes.ScopedEvalContextModifier, frame: Frame - ) -> None: + def visit_ScopedEvalContextModifier( + self, node: nodes.ScopedEvalContextModifier, frame: Frame + ) -> None: old_ctx_name = self.temporary_identifier() saved_ctx = frame.eval_ctx.save() - self.writeline(f"{old_ctx_name} = context.eval_ctx.save()") + self.writeline(f"{old_ctx_name} = context.eval_ctx.save()") self.visit_EvalContextModifier(node, frame) for child in node.body: self.visit(child, frame) frame.eval_ctx.revert(saved_ctx) - self.writeline(f"context.eval_ctx.revert({old_ctx_name})") + self.writeline(f"context.eval_ctx.revert({old_ctx_name})") diff --git a/contrib/python/Jinja2/py3/jinja2/constants.py b/contrib/python/Jinja2/py3/jinja2/constants.py index 41a1c23b0a..532c748eec 100644 --- a/contrib/python/Jinja2/py3/jinja2/constants.py +++ b/contrib/python/Jinja2/py3/jinja2/constants.py @@ -1,5 +1,5 @@ #: list of lorem ipsum words used by the lipsum() helper function -LOREM_IPSUM_WORDS = """\ +LOREM_IPSUM_WORDS = """\ a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at auctor augue bibendum blandit class commodo condimentum congue consectetuer consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus @@ -17,4 +17,4 @@ ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus -viverra volutpat vulputate""" +viverra volutpat vulputate""" diff --git a/contrib/python/Jinja2/py3/jinja2/debug.py b/contrib/python/Jinja2/py3/jinja2/debug.py index 805866bd6f..704341b476 100644 --- a/contrib/python/Jinja2/py3/jinja2/debug.py +++ b/contrib/python/Jinja2/py3/jinja2/debug.py @@ -1,113 +1,113 @@ -import platform +import platform import sys -import typing as t -from types import CodeType -from types import TracebackType - -from .exceptions import TemplateSyntaxError -from .utils import internal_code -from .utils import missing - -if t.TYPE_CHECKING: - from .runtime import Context - - -def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException: - """Rewrite the current exception to replace any tracebacks from - within compiled template code with tracebacks that look like they - came from the template source. - - This must be called within an ``except`` block. - - :param source: For ``TemplateSyntaxError``, the original source if - known. - :return: The original exception with the rewritten traceback. - """ - _, exc_value, tb = sys.exc_info() - exc_value = t.cast(BaseException, exc_value) - tb = t.cast(TracebackType, tb) - - if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated: - exc_value.translated = True - exc_value.source = source - # Remove the old traceback, otherwise the frames from the - # compiler still show up. - exc_value.with_traceback(None) - # Outside of runtime, so the frame isn't executing template - # code, but it still needs to point at the template. - tb = fake_traceback( - exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno - ) - else: - # Skip the frame for the render function. - tb = tb.tb_next - - stack = [] - - # Build the stack of traceback object, replacing any in template - # code with the source file and line information. - while tb is not None: - # Skip frames decorated with @internalcode. These are internal - # calls that aren't useful in template debugging output. - if tb.tb_frame.f_code in internal_code: - tb = tb.tb_next - continue - - template = tb.tb_frame.f_globals.get("__jinja_template__") - - if template is not None: - lineno = template.get_corresponding_lineno(tb.tb_lineno) - fake_tb = fake_traceback(exc_value, tb, template.filename, lineno) - stack.append(fake_tb) +import typing as t +from types import CodeType +from types import TracebackType + +from .exceptions import TemplateSyntaxError +from .utils import internal_code +from .utils import missing + +if t.TYPE_CHECKING: + from .runtime import Context + + +def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException: + """Rewrite the current exception to replace any tracebacks from + within compiled template code with tracebacks that look like they + came from the template source. + + This must be called within an ``except`` block. + + :param source: For ``TemplateSyntaxError``, the original source if + known. + :return: The original exception with the rewritten traceback. + """ + _, exc_value, tb = sys.exc_info() + exc_value = t.cast(BaseException, exc_value) + tb = t.cast(TracebackType, tb) + + if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated: + exc_value.translated = True + exc_value.source = source + # Remove the old traceback, otherwise the frames from the + # compiler still show up. + exc_value.with_traceback(None) + # Outside of runtime, so the frame isn't executing template + # code, but it still needs to point at the template. + tb = fake_traceback( + exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno + ) + else: + # Skip the frame for the render function. + tb = tb.tb_next + + stack = [] + + # Build the stack of traceback object, replacing any in template + # code with the source file and line information. + while tb is not None: + # Skip frames decorated with @internalcode. These are internal + # calls that aren't useful in template debugging output. + if tb.tb_frame.f_code in internal_code: + tb = tb.tb_next + continue + + template = tb.tb_frame.f_globals.get("__jinja_template__") + + if template is not None: + lineno = template.get_corresponding_lineno(tb.tb_lineno) + fake_tb = fake_traceback(exc_value, tb, template.filename, lineno) + stack.append(fake_tb) else: - stack.append(tb) - - tb = tb.tb_next - - tb_next = None - - # Assign tb_next in reverse to avoid circular references. - for tb in reversed(stack): - tb_next = tb_set_next(tb, tb_next) - - return exc_value.with_traceback(tb_next) - - -def fake_traceback( # type: ignore - exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int -) -> TracebackType: - """Produce a new traceback object that looks like it came from the - template source instead of the compiled code. The filename, line - number, and location name will point to the template, and the local - variables will be the current template context. - - :param exc_value: The original exception to be re-raised to create - the new traceback. - :param tb: The original traceback to get the local variables and - code info from. - :param filename: The template filename. - :param lineno: The line number in the template source. - """ - if tb is not None: - # Replace the real locals with the context that would be - # available at that point in the template. - locals = get_template_locals(tb.tb_frame.f_locals) - locals.pop("__jinja_exception__", None) + stack.append(tb) + + tb = tb.tb_next + + tb_next = None + + # Assign tb_next in reverse to avoid circular references. + for tb in reversed(stack): + tb_next = tb_set_next(tb, tb_next) + + return exc_value.with_traceback(tb_next) + + +def fake_traceback( # type: ignore + exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int +) -> TracebackType: + """Produce a new traceback object that looks like it came from the + template source instead of the compiled code. The filename, line + number, and location name will point to the template, and the local + variables will be the current template context. + + :param exc_value: The original exception to be re-raised to create + the new traceback. + :param tb: The original traceback to get the local variables and + code info from. + :param filename: The template filename. + :param lineno: The line number in the template source. + """ + if tb is not None: + # Replace the real locals with the context that would be + # available at that point in the template. + locals = get_template_locals(tb.tb_frame.f_locals) + locals.pop("__jinja_exception__", None) else: - locals = {} - - globals = { - "__name__": filename, - "__file__": filename, - "__jinja_exception__": exc_value, - } - # Raise an exception at the correct line number. + locals = {} + + globals = { + "__name__": filename, + "__file__": filename, + "__jinja_exception__": exc_value, + } + # Raise an exception at the correct line number. code: CodeType = compile( "\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec" ) - # Build a new code object that points to the template file and - # replaces the location with a block name. + # Build a new code object that points to the template file and + # replaces the location with a block name. location = "template" if tb is not None: @@ -139,121 +139,121 @@ def fake_traceback( # type: ignore code.co_cellvars, ) - # Execute the new code, which is guaranteed to raise, and return - # the new traceback without this frame. - try: - exec(code, globals, locals) - except BaseException: - return sys.exc_info()[2].tb_next # type: ignore + # Execute the new code, which is guaranteed to raise, and return + # the new traceback without this frame. + try: + exec(code, globals, locals) + except BaseException: + return sys.exc_info()[2].tb_next # type: ignore -def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]: - """Based on the runtime locals, get the context that would be - available at that point in the template. - """ - # Start with the current template context. - ctx: "t.Optional[Context]" = real_locals.get("context") +def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]: + """Based on the runtime locals, get the context that would be + available at that point in the template. + """ + # Start with the current template context. + ctx: "t.Optional[Context]" = real_locals.get("context") - if ctx is not None: - data: t.Dict[str, t.Any] = ctx.get_all().copy() + if ctx is not None: + data: t.Dict[str, t.Any] = ctx.get_all().copy() else: - data = {} + data = {} - # Might be in a derived context that only sets local variables - # rather than pushing a context. Local variables follow the scheme - # l_depth_name. Find the highest-depth local that has a value for - # each name. - local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {} + # Might be in a derived context that only sets local variables + # rather than pushing a context. Local variables follow the scheme + # l_depth_name. Find the highest-depth local that has a value for + # each name. + local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {} - for name, value in real_locals.items(): - if not name.startswith("l_") or value is missing: - # Not a template variable, or no longer relevant. + for name, value in real_locals.items(): + if not name.startswith("l_") or value is missing: + # Not a template variable, or no longer relevant. continue - + try: - _, depth_str, name = name.split("_", 2) - depth = int(depth_str) + _, depth_str, name = name.split("_", 2) + depth = int(depth_str) except ValueError: continue - + cur_depth = local_overrides.get(name, (-1,))[0] - + if cur_depth < depth: local_overrides[name] = (depth, value) - # Modify the context with any derived context. - for name, (_, value) in local_overrides.items(): + # Modify the context with any derived context. + for name, (_, value) in local_overrides.items(): if value is missing: - data.pop(name, None) + data.pop(name, None) else: - data[name] = value + data[name] = value - return data + return data -if sys.version_info >= (3, 7): - # tb_next is directly assignable as of Python 3.7 - def tb_set_next( - tb: TracebackType, tb_next: t.Optional[TracebackType] - ) -> TracebackType: - tb.tb_next = tb_next - return tb +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. +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 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), + 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)) + 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. + # 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) + 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 + # 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 + return tb diff --git a/contrib/python/Jinja2/py3/jinja2/defaults.py b/contrib/python/Jinja2/py3/jinja2/defaults.py index 638cad3d2d..032876ed56 100644 --- a/contrib/python/Jinja2/py3/jinja2/defaults.py +++ b/contrib/python/Jinja2/py3/jinja2/defaults.py @@ -1,48 +1,48 @@ -import typing as t - -from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401 -from .tests import TESTS as DEFAULT_TESTS # noqa: F401 -from .utils import Cycler -from .utils import generate_lorem_ipsum -from .utils import Joiner -from .utils import Namespace - -if t.TYPE_CHECKING: - import typing_extensions as te +import typing as t + +from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401 +from .tests import TESTS as DEFAULT_TESTS # noqa: F401 +from .utils import Cycler +from .utils import generate_lorem_ipsum +from .utils import Joiner +from .utils import Namespace +if t.TYPE_CHECKING: + import typing_extensions as te + # defaults for the parser / lexer -BLOCK_START_STRING = "{%" -BLOCK_END_STRING = "%}" -VARIABLE_START_STRING = "{{" -VARIABLE_END_STRING = "}}" -COMMENT_START_STRING = "{#" -COMMENT_END_STRING = "#}" -LINE_STATEMENT_PREFIX: t.Optional[str] = None -LINE_COMMENT_PREFIX: t.Optional[str] = None +BLOCK_START_STRING = "{%" +BLOCK_END_STRING = "%}" +VARIABLE_START_STRING = "{{" +VARIABLE_END_STRING = "}}" +COMMENT_START_STRING = "{#" +COMMENT_END_STRING = "#}" +LINE_STATEMENT_PREFIX: t.Optional[str] = None +LINE_COMMENT_PREFIX: t.Optional[str] = None TRIM_BLOCKS = False LSTRIP_BLOCKS = False -NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n" +NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n" KEEP_TRAILING_NEWLINE = False -# default filters, tests and namespace +# default filters, tests and namespace DEFAULT_NAMESPACE = { - "range": range, - "dict": dict, - "lipsum": generate_lorem_ipsum, - "cycler": Cycler, - "joiner": Joiner, - "namespace": Namespace, + "range": range, + "dict": dict, + "lipsum": generate_lorem_ipsum, + "cycler": Cycler, + "joiner": Joiner, + "namespace": Namespace, } # default policies -DEFAULT_POLICIES: t.Dict[str, t.Any] = { - "compiler.ascii_str": True, - "urlize.rel": "noopener", - "urlize.target": None, - "urlize.extra_schemes": None, - "truncate.leeway": 5, - "json.dumps_function": None, - "json.dumps_kwargs": {"sort_keys": True}, - "ext.i18n.trimmed": False, +DEFAULT_POLICIES: t.Dict[str, t.Any] = { + "compiler.ascii_str": True, + "urlize.rel": "noopener", + "urlize.target": None, + "urlize.extra_schemes": None, + "truncate.leeway": 5, + "json.dumps_function": None, + "json.dumps_kwargs": {"sort_keys": True}, + "ext.i18n.trimmed": False, } diff --git a/contrib/python/Jinja2/py3/jinja2/environment.py b/contrib/python/Jinja2/py3/jinja2/environment.py index a231d9cd57..fa1f292325 100644 --- a/contrib/python/Jinja2/py3/jinja2/environment.py +++ b/contrib/python/Jinja2/py3/jinja2/environment.py @@ -1,146 +1,146 @@ -"""Classes for managing templates and their runtime and compile time -options. +"""Classes for managing templates and their runtime and compile time +options. """ import os import sys -import typing -import typing as t +import typing +import typing as t import weakref -from collections import ChainMap -from functools import lru_cache -from functools import partial -from functools import reduce -from types import CodeType - -from markupsafe import Markup - -from . import nodes -from .compiler import CodeGenerator -from .compiler import generate -from .defaults import BLOCK_END_STRING -from .defaults import BLOCK_START_STRING -from .defaults import COMMENT_END_STRING -from .defaults import COMMENT_START_STRING -from .defaults import DEFAULT_FILTERS -from .defaults import DEFAULT_NAMESPACE -from .defaults import DEFAULT_POLICIES -from .defaults import DEFAULT_TESTS -from .defaults import KEEP_TRAILING_NEWLINE -from .defaults import LINE_COMMENT_PREFIX -from .defaults import LINE_STATEMENT_PREFIX -from .defaults import LSTRIP_BLOCKS -from .defaults import NEWLINE_SEQUENCE -from .defaults import TRIM_BLOCKS -from .defaults import VARIABLE_END_STRING -from .defaults import VARIABLE_START_STRING -from .exceptions import TemplateNotFound -from .exceptions import TemplateRuntimeError -from .exceptions import TemplatesNotFound -from .exceptions import TemplateSyntaxError -from .exceptions import UndefinedError -from .lexer import get_lexer -from .lexer import Lexer -from .lexer import TokenStream -from .nodes import EvalContext -from .parser import Parser -from .runtime import Context -from .runtime import new_context -from .runtime import Undefined -from .utils import _PassArg -from .utils import concat -from .utils import consume -from .utils import import_string -from .utils import internalcode -from .utils import LRUCache -from .utils import missing - -if t.TYPE_CHECKING: - import typing_extensions as te - from .bccache import BytecodeCache - from .ext import Extension - from .loaders import BaseLoader - -_env_bound = t.TypeVar("_env_bound", bound="Environment") - - -# for direct template usage we have up to ten living environments -@lru_cache(maxsize=10) -def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound: - """Return a new spontaneous environment. A spontaneous environment - is used for templates created directly rather than through an - existing environment. - - :param cls: Environment class to create. - :param args: Positional arguments passed to environment. +from collections import ChainMap +from functools import lru_cache +from functools import partial +from functools import reduce +from types import CodeType + +from markupsafe import Markup + +from . import nodes +from .compiler import CodeGenerator +from .compiler import generate +from .defaults import BLOCK_END_STRING +from .defaults import BLOCK_START_STRING +from .defaults import COMMENT_END_STRING +from .defaults import COMMENT_START_STRING +from .defaults import DEFAULT_FILTERS +from .defaults import DEFAULT_NAMESPACE +from .defaults import DEFAULT_POLICIES +from .defaults import DEFAULT_TESTS +from .defaults import KEEP_TRAILING_NEWLINE +from .defaults import LINE_COMMENT_PREFIX +from .defaults import LINE_STATEMENT_PREFIX +from .defaults import LSTRIP_BLOCKS +from .defaults import NEWLINE_SEQUENCE +from .defaults import TRIM_BLOCKS +from .defaults import VARIABLE_END_STRING +from .defaults import VARIABLE_START_STRING +from .exceptions import TemplateNotFound +from .exceptions import TemplateRuntimeError +from .exceptions import TemplatesNotFound +from .exceptions import TemplateSyntaxError +from .exceptions import UndefinedError +from .lexer import get_lexer +from .lexer import Lexer +from .lexer import TokenStream +from .nodes import EvalContext +from .parser import Parser +from .runtime import Context +from .runtime import new_context +from .runtime import Undefined +from .utils import _PassArg +from .utils import concat +from .utils import consume +from .utils import import_string +from .utils import internalcode +from .utils import LRUCache +from .utils import missing + +if t.TYPE_CHECKING: + import typing_extensions as te + from .bccache import BytecodeCache + from .ext import Extension + from .loaders import BaseLoader + +_env_bound = t.TypeVar("_env_bound", bound="Environment") + + +# for direct template usage we have up to ten living environments +@lru_cache(maxsize=10) +def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound: + """Return a new spontaneous environment. A spontaneous environment + is used for templates created directly rather than through an + existing environment. + + :param cls: Environment class to create. + :param args: Positional arguments passed to environment. """ - env = cls(*args) - env.shared = True - return env - - -def create_cache( - size: int, -) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]: + env = cls(*args) + env.shared = True + return env + + +def create_cache( + size: int, +) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]: """Return the cache class for the given size.""" if size == 0: return None - + if size < 0: return {} - return LRUCache(size) # type: ignore + return LRUCache(size) # type: ignore - -def copy_cache( - cache: t.Optional[t.MutableMapping], -) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]: + +def copy_cache( + cache: t.Optional[t.MutableMapping], +) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]: """Create an empty copy of the given cache.""" if cache is None: return None - - if type(cache) is dict: + + if type(cache) is dict: return {} - return LRUCache(cache.capacity) # type: ignore - + return LRUCache(cache.capacity) # type: ignore -def load_extensions( - environment: "Environment", - extensions: t.Sequence[t.Union[str, t.Type["Extension"]]], -) -> t.Dict[str, "Extension"]: + +def load_extensions( + environment: "Environment", + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]], +) -> t.Dict[str, "Extension"]: """Load the extensions from the list and bind it to the environment. - Returns a dict of instantiated extensions. + Returns a dict of instantiated extensions. """ result = {} - + for extension in extensions: - if isinstance(extension, str): - extension = t.cast(t.Type["Extension"], import_string(extension)) - + if isinstance(extension, str): + extension = t.cast(t.Type["Extension"], import_string(extension)) + result[extension.identifier] = extension(environment) - + return result -def _environment_config_check(environment: "Environment") -> "Environment": +def _environment_config_check(environment: "Environment") -> "Environment": """Perform a sanity check on the environment.""" - assert issubclass( - environment.undefined, Undefined - ), "'undefined' must be a subclass of 'jinja2.Undefined'." - assert ( - environment.block_start_string - != environment.variable_start_string - != environment.comment_start_string - ), "block, variable and comment start strings must be different." - assert environment.newline_sequence in { - "\r", - "\r\n", - "\n", - }, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'." + assert issubclass( + environment.undefined, Undefined + ), "'undefined' must be a subclass of 'jinja2.Undefined'." + assert ( + environment.block_start_string + != environment.variable_start_string + != environment.comment_start_string + ), "block, variable and comment start strings must be different." + assert environment.newline_sequence in { + "\r", + "\r\n", + "\n", + }, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'." return environment -class Environment: +class Environment: r"""The core component of Jinja is the `Environment`. It contains important shared variables like configuration, filters, tests, globals and others. Instances of this class may be modified if @@ -221,7 +221,7 @@ class Environment: `autoescape` If set to ``True`` the XML/HTML autoescaping feature is enabled by default. For more details about autoescaping see - :class:`~markupsafe.Markup`. As of Jinja 2.4 this can also + :class:`~markupsafe.Markup`. As of Jinja 2.4 this can also be a callable that is passed the template name and has to return ``True`` or ``False`` depending on autoescape should be enabled by default. @@ -258,8 +258,8 @@ class Environment: See :ref:`bytecode-cache` for more information. `enable_async` - If set to true this enables async template execution which - allows using async functions and generators. + If set to true this enables async template execution which + allows using async functions and generators. """ #: if this environment is sandboxed. Modifying this variable won't make @@ -272,7 +272,7 @@ class Environment: overlayed = False #: the environment this environment is linked to if it is an overlay - linked_to: t.Optional["Environment"] = None + linked_to: t.Optional["Environment"] = None #: shared environments have this set to `True`. A shared environment #: must not be modified @@ -280,39 +280,39 @@ class Environment: #: the class that is used for code generation. See #: :class:`~jinja2.compiler.CodeGenerator` for more information. - code_generator_class: t.Type["CodeGenerator"] = CodeGenerator + code_generator_class: t.Type["CodeGenerator"] = CodeGenerator - #: the context class that is used for templates. See + #: the context class that is used for templates. See #: :class:`~jinja2.runtime.Context` for more information. - context_class: t.Type[Context] = Context - - template_class: t.Type["Template"] - - def __init__( - self, - block_start_string: str = BLOCK_START_STRING, - block_end_string: str = BLOCK_END_STRING, - variable_start_string: str = VARIABLE_START_STRING, - variable_end_string: str = VARIABLE_END_STRING, - comment_start_string: str = COMMENT_START_STRING, - comment_end_string: str = COMMENT_END_STRING, - line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX, - line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX, - trim_blocks: bool = TRIM_BLOCKS, - lstrip_blocks: bool = LSTRIP_BLOCKS, - newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE, - keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE, - extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (), - optimized: bool = True, - undefined: t.Type[Undefined] = Undefined, - finalize: t.Optional[t.Callable[..., t.Any]] = None, - autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False, - loader: t.Optional["BaseLoader"] = None, - cache_size: int = 400, - auto_reload: bool = True, - bytecode_cache: t.Optional["BytecodeCache"] = None, - enable_async: bool = False, - ): + context_class: t.Type[Context] = Context + + template_class: t.Type["Template"] + + def __init__( + self, + block_start_string: str = BLOCK_START_STRING, + block_end_string: str = BLOCK_END_STRING, + variable_start_string: str = VARIABLE_START_STRING, + variable_end_string: str = VARIABLE_END_STRING, + comment_start_string: str = COMMENT_START_STRING, + comment_end_string: str = COMMENT_END_STRING, + line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX, + line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX, + trim_blocks: bool = TRIM_BLOCKS, + lstrip_blocks: bool = LSTRIP_BLOCKS, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE, + keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (), + optimized: bool = True, + undefined: t.Type[Undefined] = Undefined, + finalize: t.Optional[t.Callable[..., t.Any]] = None, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False, + loader: t.Optional["BaseLoader"] = None, + cache_size: int = 400, + auto_reload: bool = True, + bytecode_cache: t.Optional["BytecodeCache"] = None, + enable_async: bool = False, + ): # !!Important notice!! # The constructor accepts quite a few arguments that should be # passed by keyword rather than position. However it's important to @@ -339,7 +339,7 @@ class Environment: self.keep_trailing_newline = keep_trailing_newline # runtime information - self.undefined: t.Type[Undefined] = undefined + self.undefined: t.Type[Undefined] = undefined self.optimized = optimized self.finalize = finalize self.autoescape = autoescape @@ -361,47 +361,47 @@ class Environment: # load extensions self.extensions = load_extensions(self, extensions) - self.is_async = enable_async - _environment_config_check(self) + self.is_async = enable_async + _environment_config_check(self) - def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None: + def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None: """Adds an extension after the environment was created. .. versionadded:: 2.5 """ self.extensions.update(load_extensions(self, [extension])) - def extend(self, **attributes: t.Any) -> None: + def extend(self, **attributes: t.Any) -> None: """Add the items to the instance of the environment if they do not exist yet. This is used by :ref:`extensions <writing-extensions>` to register callbacks and configuration values without breaking inheritance. """ - for key, value in attributes.items(): + for key, value in attributes.items(): if not hasattr(self, key): setattr(self, key, value) - def overlay( - self, - block_start_string: str = missing, - block_end_string: str = missing, - variable_start_string: str = missing, - variable_end_string: str = missing, - comment_start_string: str = missing, - comment_end_string: str = missing, - line_statement_prefix: t.Optional[str] = missing, - line_comment_prefix: t.Optional[str] = missing, - trim_blocks: bool = missing, - lstrip_blocks: bool = missing, - extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing, - optimized: bool = missing, - undefined: t.Type[Undefined] = missing, - finalize: t.Optional[t.Callable[..., t.Any]] = missing, - autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing, - loader: t.Optional["BaseLoader"] = missing, - cache_size: int = missing, - auto_reload: bool = missing, - bytecode_cache: t.Optional["BytecodeCache"] = missing, - ) -> "Environment": + def overlay( + self, + block_start_string: str = missing, + block_end_string: str = missing, + variable_start_string: str = missing, + variable_end_string: str = missing, + comment_start_string: str = missing, + comment_end_string: str = missing, + line_statement_prefix: t.Optional[str] = missing, + line_comment_prefix: t.Optional[str] = missing, + trim_blocks: bool = missing, + lstrip_blocks: bool = missing, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing, + optimized: bool = missing, + undefined: t.Type[Undefined] = missing, + finalize: t.Optional[t.Callable[..., t.Any]] = missing, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing, + loader: t.Optional["BaseLoader"] = missing, + cache_size: int = missing, + auto_reload: bool = missing, + bytecode_cache: t.Optional["BytecodeCache"] = missing, + ) -> "Environment": """Create a new overlay environment that shares all the data with the current environment except for cache and the overridden attributes. Extensions cannot be removed for an overlayed environment. An overlayed @@ -414,14 +414,14 @@ class Environment: through. """ args = dict(locals()) - del args["self"], args["cache_size"], args["extensions"] + del args["self"], args["cache_size"], args["extensions"] rv = object.__new__(self.__class__) rv.__dict__.update(self.__dict__) rv.overlayed = True rv.linked_to = self - for key, value in args.items(): + for key, value in args.items(): if value is not missing: setattr(rv, key, value) @@ -431,30 +431,30 @@ class Environment: rv.cache = copy_cache(self.cache) rv.extensions = {} - for key, value in self.extensions.items(): + for key, value in self.extensions.items(): rv.extensions[key] = value.bind(rv) if extensions is not missing: rv.extensions.update(load_extensions(rv, extensions)) - return _environment_config_check(rv) + return _environment_config_check(rv) - @property - def lexer(self) -> Lexer: - """The lexer for this environment.""" - return get_lexer(self) + @property + def lexer(self) -> Lexer: + """The lexer for this environment.""" + return get_lexer(self) - def iter_extensions(self) -> t.Iterator["Extension"]: + def iter_extensions(self) -> t.Iterator["Extension"]: """Iterates over the extensions by priority.""" - return iter(sorted(self.extensions.values(), key=lambda x: x.priority)) + return iter(sorted(self.extensions.values(), key=lambda x: x.priority)) - def getitem( - self, obj: t.Any, argument: t.Union[str, t.Any] - ) -> t.Union[t.Any, Undefined]: + def getitem( + self, obj: t.Any, argument: t.Union[str, t.Any] + ) -> t.Union[t.Any, Undefined]: """Get an item or attribute of an object but prefer the item.""" try: return obj[argument] except (AttributeError, TypeError, LookupError): - if isinstance(argument, str): + if isinstance(argument, str): try: attr = str(argument) except Exception: @@ -466,9 +466,9 @@ class Environment: pass return self.undefined(obj=obj, name=argument) - def getattr(self, obj: t.Any, attribute: str) -> t.Any: + def getattr(self, obj: t.Any, attribute: str) -> t.Any: """Get an item or attribute of an object but prefer the attribute. - Unlike :meth:`getitem` the attribute *must* be a string. + Unlike :meth:`getitem` the attribute *must* be a string. """ try: return getattr(obj, attribute) @@ -479,138 +479,138 @@ class Environment: except (TypeError, LookupError, AttributeError): return self.undefined(obj=obj, name=attribute) - def _filter_test_common( - self, - name: t.Union[str, Undefined], - value: t.Any, - args: t.Optional[t.Sequence[t.Any]], - kwargs: t.Optional[t.Mapping[str, t.Any]], - context: t.Optional[Context], - eval_ctx: t.Optional[EvalContext], - is_filter: bool, - ) -> t.Any: - if is_filter: - env_map = self.filters - type_name = "filter" - else: - env_map = self.tests - type_name = "test" - - func = env_map.get(name) # type: ignore + def _filter_test_common( + self, + name: t.Union[str, Undefined], + value: t.Any, + args: t.Optional[t.Sequence[t.Any]], + kwargs: t.Optional[t.Mapping[str, t.Any]], + context: t.Optional[Context], + eval_ctx: t.Optional[EvalContext], + is_filter: bool, + ) -> t.Any: + if is_filter: + env_map = self.filters + type_name = "filter" + else: + env_map = self.tests + type_name = "test" + + func = env_map.get(name) # type: ignore if func is None: - msg = f"No {type_name} named {name!r}." - - if isinstance(name, Undefined): - try: - name._fail_with_undefined_error() - except Exception as e: - msg = f"{msg} ({e}; did you forget to quote the callable name?)" - - raise TemplateRuntimeError(msg) - - args = [value, *(args if args is not None else ())] - kwargs = kwargs if kwargs is not None else {} - pass_arg = _PassArg.from_obj(func) - - if pass_arg is _PassArg.context: + msg = f"No {type_name} named {name!r}." + + if isinstance(name, Undefined): + try: + name._fail_with_undefined_error() + except Exception as e: + msg = f"{msg} ({e}; did you forget to quote the callable name?)" + + raise TemplateRuntimeError(msg) + + args = [value, *(args if args is not None else ())] + kwargs = kwargs if kwargs is not None else {} + pass_arg = _PassArg.from_obj(func) + + if pass_arg is _PassArg.context: if context is None: - raise TemplateRuntimeError( - f"Attempted to invoke a context {type_name} without context." - ) - + raise TemplateRuntimeError( + f"Attempted to invoke a context {type_name} without context." + ) + args.insert(0, context) - elif pass_arg is _PassArg.eval_context: + elif pass_arg is _PassArg.eval_context: if eval_ctx is None: if context is not None: eval_ctx = context.eval_ctx else: eval_ctx = EvalContext(self) - + args.insert(0, eval_ctx) - elif pass_arg is _PassArg.environment: + elif pass_arg is _PassArg.environment: args.insert(0, self) - return func(*args, **kwargs) - - def call_filter( - self, - name: str, - value: t.Any, - args: t.Optional[t.Sequence[t.Any]] = None, - kwargs: t.Optional[t.Mapping[str, t.Any]] = None, - context: t.Optional[Context] = None, - eval_ctx: t.Optional[EvalContext] = None, - ) -> t.Any: - """Invoke a filter on a value the same way the compiler does. - - This might return a coroutine if the filter is running from an - environment in async mode and the filter supports async - execution. It's your responsibility to await this if needed. - - .. versionadded:: 2.7 - """ - return self._filter_test_common( - name, value, args, kwargs, context, eval_ctx, True - ) - - def call_test( - self, - name: str, - value: t.Any, - args: t.Optional[t.Sequence[t.Any]] = None, - kwargs: t.Optional[t.Mapping[str, t.Any]] = None, - context: t.Optional[Context] = None, - eval_ctx: t.Optional[EvalContext] = None, - ) -> t.Any: - """Invoke a test on a value the same way the compiler does. - - This might return a coroutine if the test is running from an - environment in async mode and the test supports async execution. - It's your responsibility to await this if needed. - - .. versionchanged:: 3.0 - Tests support ``@pass_context``, etc. decorators. Added - the ``context`` and ``eval_ctx`` parameters. - + return func(*args, **kwargs) + + def call_filter( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a filter on a value the same way the compiler does. + + This might return a coroutine if the filter is running from an + environment in async mode and the filter supports async + execution. It's your responsibility to await this if needed. + .. versionadded:: 2.7 """ - return self._filter_test_common( - name, value, args, kwargs, context, eval_ctx, False - ) - + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, True + ) + + def call_test( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a test on a value the same way the compiler does. + + This might return a coroutine if the test is running from an + environment in async mode and the test supports async execution. + It's your responsibility to await this if needed. + + .. versionchanged:: 3.0 + Tests support ``@pass_context``, etc. decorators. Added + the ``context`` and ``eval_ctx`` parameters. + + .. versionadded:: 2.7 + """ + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, False + ) + @internalcode - def parse( - self, - source: str, - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - ) -> nodes.Template: + def parse( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> nodes.Template: """Parse the sourcecode and return the abstract syntax tree. This tree of nodes is used by the compiler to convert the template into executable source- or bytecode. This is useful for debugging or to extract information from templates. - If you are :ref:`developing Jinja extensions <writing-extensions>` + If you are :ref:`developing Jinja extensions <writing-extensions>` this gives you a good overview of the node tree generated. """ try: return self._parse(source, name, filename) except TemplateSyntaxError: - self.handle_exception(source=source) + self.handle_exception(source=source) - def _parse( - self, source: str, name: t.Optional[str], filename: t.Optional[str] - ) -> nodes.Template: + def _parse( + self, source: str, name: t.Optional[str], filename: t.Optional[str] + ) -> nodes.Template: """Internal parsing function used by `parse` and `compile`.""" - return Parser(self, source, name, filename).parse() - - def lex( - self, - source: str, - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - ) -> t.Iterator[t.Tuple[int, str, str]]: + return Parser(self, source, name, filename).parse() + + def lex( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> t.Iterator[t.Tuple[int, str, str]]: """Lex the given sourcecode and return a generator that yields tokens as tuples in the form ``(lineno, token_type, value)``. This can be useful for :ref:`extension development <writing-extensions>` @@ -620,109 +620,109 @@ class Environment: of the extensions to be applied you have to filter source through the :meth:`preprocess` method. """ - source = str(source) + source = str(source) try: return self.lexer.tokeniter(source, name, filename) except TemplateSyntaxError: - self.handle_exception(source=source) - - def preprocess( - self, - source: str, - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - ) -> str: + self.handle_exception(source=source) + + def preprocess( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> str: """Preprocesses the source with all extensions. This is automatically called for all parsing and compiling methods but *not* for :meth:`lex` because there you usually only want the actual source tokenized. """ - return reduce( - lambda s, e: e.preprocess(s, name, filename), - self.iter_extensions(), - str(source), - ) - - def _tokenize( - self, - source: str, - name: t.Optional[str], - filename: t.Optional[str] = None, - state: t.Optional[str] = None, - ) -> TokenStream: + return reduce( + lambda s, e: e.preprocess(s, name, filename), + self.iter_extensions(), + str(source), + ) + + def _tokenize( + self, + source: str, + name: t.Optional[str], + filename: t.Optional[str] = None, + state: t.Optional[str] = None, + ) -> TokenStream: """Called by the parser to do the preprocessing and filtering for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`. """ source = self.preprocess(source, name, filename) stream = self.lexer.tokenize(source, name, filename, state) - + for ext in self.iter_extensions(): - stream = ext.filter_stream(stream) # type: ignore - + stream = ext.filter_stream(stream) # type: ignore + if not isinstance(stream, TokenStream): - stream = TokenStream(stream, name, filename) # type: ignore - + stream = TokenStream(stream, name, filename) # type: ignore + return stream - def _generate( - self, - source: nodes.Template, - name: t.Optional[str], - filename: t.Optional[str], - defer_init: bool = False, - ) -> str: + def _generate( + self, + source: nodes.Template, + name: t.Optional[str], + filename: t.Optional[str], + defer_init: bool = False, + ) -> str: """Internal hook that can be overridden to hook a different generate method in. .. versionadded:: 2.5 """ - return generate( # type: ignore - source, - self, - name, - filename, - defer_init=defer_init, - optimized=self.optimized, - ) - - def _compile(self, source: str, filename: str) -> CodeType: + return generate( # type: ignore + source, + self, + name, + filename, + defer_init=defer_init, + optimized=self.optimized, + ) + + def _compile(self, source: str, filename: str) -> CodeType: """Internal hook that can be overridden to hook a different compile method in. .. versionadded:: 2.5 """ - return compile(source, filename, "exec") # type: ignore - - @typing.overload - def compile( # type: ignore - self, - source: t.Union[str, nodes.Template], - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - raw: "te.Literal[False]" = False, - defer_init: bool = False, - ) -> CodeType: - ... - - @typing.overload - def compile( - self, - source: t.Union[str, nodes.Template], - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - raw: "te.Literal[True]" = ..., - defer_init: bool = False, - ) -> str: - ... - + return compile(source, filename, "exec") # type: ignore + + @typing.overload + def compile( # type: ignore + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[False]" = False, + defer_init: bool = False, + ) -> CodeType: + ... + + @typing.overload + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[True]" = ..., + defer_init: bool = False, + ) -> str: + ... + @internalcode - def compile( - self, - source: t.Union[str, nodes.Template], - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - raw: bool = False, - defer_init: bool = False, - ) -> t.Union[str, CodeType]: + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: bool = False, + defer_init: bool = False, + ) -> t.Union[str, CodeType]: """Compile a node or template source code. The `name` parameter is the load name of the template after it was joined using :meth:`join_path` if necessary, not the filename on the file system. @@ -744,21 +744,21 @@ class Environment: """ source_hint = None try: - if isinstance(source, str): + if isinstance(source, str): source_hint = source source = self._parse(source, name, filename) - source = self._generate(source, name, filename, defer_init=defer_init) + source = self._generate(source, name, filename, defer_init=defer_init) if raw: return source if filename is None: - filename = "<template>" + filename = "<template>" return self._compile(source, filename) except TemplateSyntaxError: - self.handle_exception(source=source_hint) + self.handle_exception(source=source_hint) - def compile_expression( - self, source: str, undefined_to_none: bool = True - ) -> "TemplateExpression": + def compile_expression( + self, source: str, undefined_to_none: bool = True + ) -> "TemplateExpression": """A handy helper method that returns a callable that accepts keyword arguments that appear as variables in the expression. If called it returns the result of the expression. @@ -786,30 +786,30 @@ class Environment: .. versionadded:: 2.1 """ - parser = Parser(self, source, state="variable") + parser = Parser(self, source, state="variable") try: expr = parser.parse_expression() if not parser.stream.eos: - raise TemplateSyntaxError( - "chunk after expression", parser.stream.current.lineno, None, None - ) + raise TemplateSyntaxError( + "chunk after expression", parser.stream.current.lineno, None, None + ) expr.set_environment(self) except TemplateSyntaxError: - self.handle_exception(source=source) - - body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)] + self.handle_exception(source=source) + + body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)] template = self.from_string(nodes.Template(body, lineno=1)) return TemplateExpression(template, undefined_to_none) - def compile_templates( - self, - target: t.Union[str, os.PathLike], - extensions: t.Optional[t.Collection[str]] = None, - filter_func: t.Optional[t.Callable[[str], bool]] = None, - zip: t.Optional[str] = "deflated", - log_function: t.Optional[t.Callable[[str], None]] = None, - ignore_errors: bool = True, - ) -> None: + def compile_templates( + self, + target: t.Union[str, os.PathLike], + extensions: t.Optional[t.Collection[str]] = None, + filter_func: t.Optional[t.Callable[[str], bool]] = None, + zip: t.Optional[str] = "deflated", + log_function: t.Optional[t.Callable[[str], None]] = None, + ignore_errors: bool = True, + ) -> None: """Finds all the templates the loader can find, compiles them and stores them in `target`. If `zip` is `None`, instead of in a zipfile, the templates will be stored in a directory. @@ -827,36 +827,36 @@ class Environment: .. versionadded:: 2.4 """ - from .loaders import ModuleLoader + from .loaders import ModuleLoader if log_function is None: - def log_function(x: str) -> None: - pass - - assert log_function is not None - assert self.loader is not None, "No loader configured." - - def write_file(filename: str, data: str) -> None: + def log_function(x: str) -> None: + pass + + assert log_function is not None + assert self.loader is not None, "No loader configured." + + def write_file(filename: str, data: str) -> None: if zip: info = ZipInfo(filename) info.external_attr = 0o755 << 16 zip_file.writestr(info, data) else: - with open(os.path.join(target, filename), "wb") as f: - f.write(data.encode("utf8")) + with open(os.path.join(target, filename), "wb") as f: + f.write(data.encode("utf8")) if zip is not None: from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED - - zip_file = ZipFile( - target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip] - ) - log_function(f"Compiling into Zip archive {target!r}") + + zip_file = ZipFile( + target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip] + ) + log_function(f"Compiling into Zip archive {target!r}") else: if not os.path.isdir(target): os.makedirs(target) - log_function(f"Compiling into folder {target!r}") + log_function(f"Compiling into folder {target!r}") try: for name in self.list_templates(extensions, filter_func): @@ -866,24 +866,24 @@ class Environment: except TemplateSyntaxError as e: if not ignore_errors: raise - log_function(f'Could not compile "{name}": {e}') + log_function(f'Could not compile "{name}": {e}') continue filename = ModuleLoader.get_module_filename(name) - write_file(filename, code) - log_function(f'Compiled "{name}" as {filename}') + write_file(filename, code) + log_function(f'Compiled "{name}" as {filename}') finally: if zip: zip_file.close() - log_function("Finished compiling templates") + log_function("Finished compiling templates") - def list_templates( - self, - extensions: t.Optional[t.Collection[str]] = None, - filter_func: t.Optional[t.Callable[[str], bool]] = None, - ) -> t.List[str]: + def list_templates( + self, + extensions: t.Optional[t.Collection[str]] = None, + filter_func: t.Optional[t.Callable[[str], bool]] = None, + ) -> t.List[str]: """Returns a list of templates for this environment. This requires that the loader supports the loader's :meth:`~BaseLoader.list_templates` method. @@ -899,32 +899,32 @@ class Environment: .. versionadded:: 2.4 """ - assert self.loader is not None, "No loader configured." - names = self.loader.list_templates() - + assert self.loader is not None, "No loader configured." + names = self.loader.list_templates() + if extensions is not None: if filter_func is not None: - raise TypeError( - "either extensions or filter_func can be passed, but not both" - ) - - def filter_func(x: str) -> bool: - return "." in x and x.rsplit(".", 1)[1] in extensions # type: ignore - + raise TypeError( + "either extensions or filter_func can be passed, but not both" + ) + + def filter_func(x: str) -> bool: + return "." in x and x.rsplit(".", 1)[1] in extensions # type: ignore + if filter_func is not None: - names = [name for name in names if filter_func(name)] - - return names + names = [name for name in names if filter_func(name)] - def handle_exception(self, source: t.Optional[str] = None) -> "te.NoReturn": + return names + + def handle_exception(self, source: t.Optional[str] = None) -> "te.NoReturn": """Exception handling helper. This is used internally to either raise rewritten exceptions or return a rendered traceback for the template. """ - from .debug import rewrite_traceback_stack + from .debug import rewrite_traceback_stack - raise rewrite_traceback_stack(source=source) + raise rewrite_traceback_stack(source=source) - def join_path(self, template: str, parent: str) -> str: + def join_path(self, template: str, parent: str) -> str: """Join a template with the parent. By default all the lookups are relative to the loader root so this method returns the `template` parameter unchanged, but if the paths should be relative to the @@ -937,108 +937,108 @@ class Environment: return template @internalcode - def _load_template( - self, name: str, globals: t.Optional[t.Mapping[str, t.Any]] - ) -> "Template": + def _load_template( + self, name: str, globals: t.Optional[t.Mapping[str, t.Any]] + ) -> "Template": if self.loader is None: - raise TypeError("no loader for this environment specified") + raise TypeError("no loader for this environment specified") cache_key = (weakref.ref(self.loader), name) if self.cache is not None: template = self.cache.get(cache_key) - if template is not None and ( - not self.auto_reload or template.is_up_to_date - ): - # template.globals is a ChainMap, modifying it will only - # affect the template, not the environment globals. - if globals: - template.globals.update(globals) - + if template is not None and ( + not self.auto_reload or template.is_up_to_date + ): + # template.globals is a ChainMap, modifying it will only + # affect the template, not the environment globals. + if globals: + template.globals.update(globals) + return template - - template = self.loader.load(self, name, self.make_globals(globals)) - + + template = self.loader.load(self, name, self.make_globals(globals)) + if self.cache is not None: self.cache[cache_key] = template return template @internalcode - def get_template( - self, - name: t.Union[str, "Template"], - parent: t.Optional[str] = None, - globals: t.Optional[t.Mapping[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 parent: The name of the parent template importing this - template. :meth:`join_path` can be used to implement name - transformations with this. - :param globals: Extend the environment :attr:`globals` with - these extra variables available for all renders of this - template. If the template has already been loaded and - cached, its globals are updated with any new items. - - .. versionchanged:: 3.0 - If a template is loaded from cache, ``globals`` will update - the template's globals instead of ignoring the new values. + def get_template( + self, + name: t.Union[str, "Template"], + parent: t.Optional[str] = None, + globals: t.Optional[t.Mapping[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 parent: The name of the parent template importing this + template. :meth:`join_path` can be used to implement name + transformations with this. + :param globals: Extend the environment :attr:`globals` with + these extra variables available for all renders of this + template. If the template has already been loaded and + cached, its globals are updated with any new items. + + .. versionchanged:: 3.0 + If a template is loaded from cache, ``globals`` will update + the template's globals instead of ignoring the new values. .. versionchanged:: 2.4 - If ``name`` is a :class:`Template` object it is returned - unchanged. + If ``name`` is a :class:`Template` object it is returned + unchanged. """ if isinstance(name, Template): return name if parent is not None: name = self.join_path(name, parent) - return self._load_template(name, globals) - + return self._load_template(name, globals) + @internalcode - def select_template( - self, - names: t.Iterable[t.Union[str, "Template"]], - parent: t.Optional[str] = None, - globals: t.Optional[t.Mapping[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` - exception is raised. - - :param names: List of template names to try loading in order. - :param parent: The name of the parent template importing this - template. :meth:`join_path` can be used to implement name - transformations with this. - :param globals: Extend the environment :attr:`globals` with - these extra variables available for all renders of this - template. If the template has already been loaded and - cached, its globals are updated with any new items. - - .. versionchanged:: 3.0 - If a template is loaded from cache, ``globals`` will update - the template's globals instead of ignoring the new values. - - .. versionchanged:: 2.11 - If ``names`` is :class:`Undefined`, an :exc:`UndefinedError` - is raised instead. If no templates were found and ``names`` - contains :class:`Undefined`, the message is more helpful. + def select_template( + self, + names: t.Iterable[t.Union[str, "Template"]], + parent: t.Optional[str] = None, + globals: t.Optional[t.Mapping[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` + exception is raised. + + :param names: List of template names to try loading in order. + :param parent: The name of the parent template importing this + template. :meth:`join_path` can be used to implement name + transformations with this. + :param globals: Extend the environment :attr:`globals` with + these extra variables available for all renders of this + template. If the template has already been loaded and + cached, its globals are updated with any new items. + + .. versionchanged:: 3.0 + If a template is loaded from cache, ``globals`` will update + the template's globals instead of ignoring the new values. + + .. versionchanged:: 2.11 + If ``names`` is :class:`Undefined`, an :exc:`UndefinedError` + is raised instead. If no templates were found and ``names`` + contains :class:`Undefined`, the message is more helpful. .. versionchanged:: 2.4 - If ``names`` contains a :class:`Template` object it is - returned unchanged. - - .. versionadded:: 2.3 + If ``names`` contains a :class:`Template` object it is + returned unchanged. + + .. versionadded:: 2.3 """ - if isinstance(names, Undefined): - names._fail_with_undefined_error() - + if isinstance(names, Undefined): + names._fail_with_undefined_error() + if not names: - raise TemplatesNotFound( - message="Tried to select from an empty list of templates." - ) - + raise TemplatesNotFound( + message="Tried to select from an empty list of templates." + ) + for name in names: if isinstance(name, Template): return name @@ -1046,75 +1046,75 @@ class Environment: name = self.join_path(name, parent) try: return self._load_template(name, globals) - except (TemplateNotFound, UndefinedError): + except (TemplateNotFound, UndefinedError): pass - raise TemplatesNotFound(names) # type: ignore + raise TemplatesNotFound(names) # type: ignore @internalcode - def get_or_select_template( - self, - template_name_or_list: t.Union[ - str, "Template", t.List[t.Union[str, "Template"]] - ], - parent: t.Optional[str] = None, - globals: t.Optional[t.Mapping[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. + def get_or_select_template( + self, + template_name_or_list: t.Union[ + str, "Template", t.List[t.Union[str, "Template"]] + ], + parent: t.Optional[str] = None, + globals: t.Optional[t.Mapping[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. .. versionadded:: 2.3 """ - if isinstance(template_name_or_list, (str, Undefined)): + if isinstance(template_name_or_list, (str, Undefined)): return self.get_template(template_name_or_list, parent, globals) elif isinstance(template_name_or_list, Template): return template_name_or_list return self.select_template(template_name_or_list, parent, globals) - def from_string( - self, - source: t.Union[str, nodes.Template], - globals: t.Optional[t.Mapping[str, t.Any]] = None, - template_class: t.Optional[t.Type["Template"]] = None, - ) -> "Template": - """Load a template from a source string without using - :attr:`loader`. - - :param source: Jinja source to compile into a template. - :param globals: Extend the environment :attr:`globals` with - these extra variables available for all renders of this - template. If the template has already been loaded and - cached, its globals are updated with any new items. - :param template_class: Return an instance of this - :class:`Template` class. + def from_string( + self, + source: t.Union[str, nodes.Template], + globals: t.Optional[t.Mapping[str, t.Any]] = None, + template_class: t.Optional[t.Type["Template"]] = None, + ) -> "Template": + """Load a template from a source string without using + :attr:`loader`. + + :param source: Jinja source to compile into a template. + :param globals: Extend the environment :attr:`globals` with + these extra variables available for all renders of this + template. If the template has already been loaded and + cached, its globals are updated with any new items. + :param template_class: Return an instance of this + :class:`Template` class. """ - gs = self.make_globals(globals) + gs = self.make_globals(globals) cls = template_class or self.template_class - return cls.from_code(self, self.compile(source), gs, None) - - def make_globals( - self, d: t.Optional[t.Mapping[str, t.Any]] - ) -> t.MutableMapping[str, t.Any]: - """Make the globals map for a template. Any given template - globals overlay the environment :attr:`globals`. - - Returns a :class:`collections.ChainMap`. This allows any changes - to a template's globals to only affect that template, while - changes to the environment's globals are still reflected. - However, avoid modifying any globals after a template is loaded. - - :param d: Dict of template-specific globals. - - .. versionchanged:: 3.0 - Use :class:`collections.ChainMap` to always prevent mutating - environment globals. - """ - if d is None: - d = {} - - return ChainMap(d, self.globals) - - -class Template: + return cls.from_code(self, self.compile(source), gs, None) + + def make_globals( + self, d: t.Optional[t.Mapping[str, t.Any]] + ) -> t.MutableMapping[str, t.Any]: + """Make the globals map for a template. Any given template + globals overlay the environment :attr:`globals`. + + Returns a :class:`collections.ChainMap`. This allows any changes + to a template's globals to only affect that template, while + changes to the environment's globals are still reflected. + However, avoid modifying any globals after a template is loaded. + + :param d: Dict of template-specific globals. + + .. versionchanged:: 3.0 + Use :class:`collections.ChainMap` to always prevent mutating + environment globals. + """ + if d is None: + d = {} + + return ChainMap(d, self.globals) + + +class Template: """A compiled template that can be rendered. Use the methods on :class:`Environment` to create or load templates. @@ -1131,93 +1131,93 @@ class Template: the object are not supported. """ - #: Type of environment to create when creating a template directly - #: rather than through an existing environment. - environment_class: t.Type[Environment] = Environment - - environment: Environment - globals: t.MutableMapping[str, t.Any] - name: t.Optional[str] - filename: t.Optional[str] - blocks: t.Dict[str, t.Callable[[Context], t.Iterator[str]]] - root_render_func: t.Callable[[Context], t.Iterator[str]] - _module: t.Optional["TemplateModule"] - _debug_info: str - _uptodate: t.Optional[t.Callable[[], bool]] - - def __new__( - cls, - source: t.Union[str, nodes.Template], - block_start_string: str = BLOCK_START_STRING, - block_end_string: str = BLOCK_END_STRING, - variable_start_string: str = VARIABLE_START_STRING, - variable_end_string: str = VARIABLE_END_STRING, - comment_start_string: str = COMMENT_START_STRING, - comment_end_string: str = COMMENT_END_STRING, - line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX, - line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX, - trim_blocks: bool = TRIM_BLOCKS, - lstrip_blocks: bool = LSTRIP_BLOCKS, - newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE, - keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE, - extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (), - optimized: bool = True, - undefined: t.Type[Undefined] = Undefined, - finalize: t.Optional[t.Callable[..., t.Any]] = None, - autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False, - enable_async: bool = False, - ) -> t.Any: # it returns a `Template`, but this breaks the sphinx build... + #: Type of environment to create when creating a template directly + #: rather than through an existing environment. + environment_class: t.Type[Environment] = Environment + + environment: Environment + globals: t.MutableMapping[str, t.Any] + name: t.Optional[str] + filename: t.Optional[str] + blocks: t.Dict[str, t.Callable[[Context], t.Iterator[str]]] + root_render_func: t.Callable[[Context], t.Iterator[str]] + _module: t.Optional["TemplateModule"] + _debug_info: str + _uptodate: t.Optional[t.Callable[[], bool]] + + def __new__( + cls, + source: t.Union[str, nodes.Template], + block_start_string: str = BLOCK_START_STRING, + block_end_string: str = BLOCK_END_STRING, + variable_start_string: str = VARIABLE_START_STRING, + variable_end_string: str = VARIABLE_END_STRING, + comment_start_string: str = COMMENT_START_STRING, + comment_end_string: str = COMMENT_END_STRING, + line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX, + line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX, + trim_blocks: bool = TRIM_BLOCKS, + lstrip_blocks: bool = LSTRIP_BLOCKS, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE, + keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (), + optimized: bool = True, + undefined: t.Type[Undefined] = Undefined, + finalize: t.Optional[t.Callable[..., t.Any]] = None, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False, + enable_async: bool = False, + ) -> t.Any: # it returns a `Template`, but this breaks the sphinx build... env = get_spontaneous_environment( - cls.environment_class, # type: ignore - block_start_string, - block_end_string, - variable_start_string, - variable_end_string, - comment_start_string, - comment_end_string, - line_statement_prefix, - line_comment_prefix, - trim_blocks, - lstrip_blocks, - newline_sequence, - keep_trailing_newline, - frozenset(extensions), - optimized, - undefined, # type: ignore - finalize, - autoescape, - None, - 0, - False, - None, - enable_async, - ) + cls.environment_class, # type: ignore + block_start_string, + block_end_string, + variable_start_string, + variable_end_string, + comment_start_string, + comment_end_string, + line_statement_prefix, + line_comment_prefix, + trim_blocks, + lstrip_blocks, + newline_sequence, + keep_trailing_newline, + frozenset(extensions), + optimized, + undefined, # type: ignore + finalize, + autoescape, + None, + 0, + False, + None, + enable_async, + ) return env.from_string(source, template_class=cls) @classmethod - def from_code( - cls, - environment: Environment, - code: CodeType, - globals: t.MutableMapping[str, t.Any], - uptodate: t.Optional[t.Callable[[], bool]] = None, - ) -> "Template": + def from_code( + cls, + environment: Environment, + code: CodeType, + globals: t.MutableMapping[str, t.Any], + uptodate: t.Optional[t.Callable[[], bool]] = None, + ) -> "Template": """Creates a template object from compiled code and the globals. This is used by the loaders and environment to create a template object. """ - namespace = {"environment": environment, "__file__": code.co_filename} + namespace = {"environment": environment, "__file__": code.co_filename} exec(code, namespace) rv = cls._from_namespace(environment, namespace, globals) rv._uptodate = uptodate return rv @classmethod - def from_module_dict( - cls, - environment: Environment, - module_dict: t.MutableMapping[str, t.Any], - globals: t.MutableMapping[str, t.Any], - ) -> "Template": + def from_module_dict( + cls, + environment: Environment, + module_dict: t.MutableMapping[str, t.Any], + globals: t.MutableMapping[str, t.Any], + ) -> "Template": """Creates a template object from a module. This is used by the module loader to create a template object. @@ -1226,34 +1226,34 @@ class Template: return cls._from_namespace(environment, module_dict, globals) @classmethod - def _from_namespace( - cls, - environment: Environment, - namespace: t.MutableMapping[str, t.Any], - globals: t.MutableMapping[str, t.Any], - ) -> "Template": - t: "Template" = object.__new__(cls) + def _from_namespace( + cls, + environment: Environment, + namespace: t.MutableMapping[str, t.Any], + globals: t.MutableMapping[str, t.Any], + ) -> "Template": + t: "Template" = object.__new__(cls) t.environment = environment t.globals = globals - t.name = namespace["name"] - t.filename = namespace["__file__"] - t.blocks = namespace["blocks"] + t.name = namespace["name"] + t.filename = namespace["__file__"] + t.blocks = namespace["blocks"] # render function and module - t.root_render_func = namespace["root"] # type: ignore + t.root_render_func = namespace["root"] # type: ignore t._module = None # debug and loader helpers - t._debug_info = namespace["debug_info"] + t._debug_info = namespace["debug_info"] t._uptodate = None # store the reference - namespace["environment"] = environment - namespace["__jinja_template__"] = t + namespace["environment"] = environment + namespace["__jinja_template__"] = t return t - def render(self, *args: t.Any, **kwargs: t.Any) -> str: + def render(self, *args: t.Any, **kwargs: t.Any) -> str: """This method accepts the same arguments as the `dict` constructor: A dict, a dict subclass or some keyword arguments. If no arguments are given the context will be empty. These two calls do the same:: @@ -1261,36 +1261,36 @@ class Template: template.render(knights='that say nih') template.render({'knights': 'that say nih'}) - This will return the rendered template as a string. + This will return the rendered template as a string. """ - if self.environment.is_async: - import asyncio - - 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: - return loop.run_until_complete(self.render_async(*args, **kwargs)) - finally: - if close: - loop.close() - - ctx = self.new_context(dict(*args, **kwargs)) - + if self.environment.is_async: + import asyncio + + 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: + return loop.run_until_complete(self.render_async(*args, **kwargs)) + finally: + if close: + loop.close() + + ctx = self.new_context(dict(*args, **kwargs)) + try: - return concat(self.root_render_func(ctx)) # type: ignore + return concat(self.root_render_func(ctx)) # type: ignore except Exception: - self.environment.handle_exception() + self.environment.handle_exception() - async def render_async(self, *args: t.Any, **kwargs: t.Any) -> str: + async def render_async(self, *args: t.Any, **kwargs: t.Any) -> str: """This works similar to :meth:`render` but returns a coroutine that when awaited returns the entire rendered template string. This requires the async feature to be enabled. @@ -1299,164 +1299,164 @@ class Template: await template.render_async(knights='that say nih; asynchronously') """ - if not self.environment.is_async: - raise RuntimeError( - "The environment was not created with async mode enabled." - ) - - ctx = self.new_context(dict(*args, **kwargs)) - - try: - return concat([n async for n in self.root_render_func(ctx)]) # type: ignore - except Exception: - return self.environment.handle_exception() - - def stream(self, *args: t.Any, **kwargs: t.Any) -> "TemplateStream": + if not self.environment.is_async: + raise RuntimeError( + "The environment was not created with async mode enabled." + ) + + ctx = self.new_context(dict(*args, **kwargs)) + + try: + return concat([n async for n in self.root_render_func(ctx)]) # type: ignore + except Exception: + return self.environment.handle_exception() + + def stream(self, *args: t.Any, **kwargs: t.Any) -> "TemplateStream": """Works exactly like :meth:`generate` but returns a :class:`TemplateStream`. """ return TemplateStream(self.generate(*args, **kwargs)) - def generate(self, *args: t.Any, **kwargs: t.Any) -> t.Iterator[str]: + def generate(self, *args: t.Any, **kwargs: t.Any) -> t.Iterator[str]: """For very large templates it can be useful to not render the whole template at once but evaluate each statement after another and yield piece for piece. This method basically does exactly that and returns - a generator that yields one item after another as strings. + a generator that yields one item after another as strings. It accepts the same arguments as :meth:`render`. """ - if self.environment.is_async: - import asyncio - - 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 - return - - ctx = self.new_context(dict(*args, **kwargs)) - + if self.environment.is_async: + import asyncio + + 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 + return + + ctx = self.new_context(dict(*args, **kwargs)) + try: - yield from self.root_render_func(ctx) # type: ignore + yield from self.root_render_func(ctx) # type: ignore except Exception: - yield self.environment.handle_exception() + yield self.environment.handle_exception() - async def generate_async( - self, *args: t.Any, **kwargs: t.Any - ) -> t.AsyncIterator[str]: + async def generate_async( + self, *args: t.Any, **kwargs: t.Any + ) -> t.AsyncIterator[str]: """An async version of :meth:`generate`. Works very similarly but returns an async iterator instead. """ - if not self.environment.is_async: - raise RuntimeError( - "The environment was not created with async mode enabled." - ) - - ctx = self.new_context(dict(*args, **kwargs)) - - try: - async for event in self.root_render_func(ctx): # type: ignore - yield event - except Exception: - yield self.environment.handle_exception() - - def new_context( - self, - vars: t.Optional[t.Dict[str, t.Any]] = None, - shared: bool = False, - locals: t.Optional[t.Mapping[str, t.Any]] = None, - ) -> Context: + if not self.environment.is_async: + raise RuntimeError( + "The environment was not created with async mode enabled." + ) + + ctx = self.new_context(dict(*args, **kwargs)) + + try: + async for event in self.root_render_func(ctx): # type: ignore + yield event + except Exception: + yield self.environment.handle_exception() + + def new_context( + self, + vars: t.Optional[t.Dict[str, t.Any]] = None, + shared: bool = False, + locals: t.Optional[t.Mapping[str, t.Any]] = None, + ) -> Context: """Create a new :class:`Context` for this template. The vars provided will be passed to the template. Per default the globals are added to the context. If shared is set to `True` the data - is passed as is to the context without adding the globals. + is passed as is to the context without adding the globals. `locals` can be a dict of local variables for internal usage. """ - return new_context( - self.environment, self.name, self.blocks, vars, shared, self.globals, locals - ) - - def make_module( - self, - vars: t.Optional[t.Dict[str, t.Any]] = None, - shared: bool = False, - locals: t.Optional[t.Mapping[str, t.Any]] = None, - ) -> "TemplateModule": + return new_context( + self.environment, self.name, self.blocks, vars, shared, self.globals, locals + ) + + def make_module( + self, + vars: t.Optional[t.Dict[str, t.Any]] = None, + shared: bool = False, + locals: t.Optional[t.Mapping[str, t.Any]] = None, + ) -> "TemplateModule": """This method works like the :attr:`module` attribute when called without arguments but it will evaluate the template on every call rather than caching it. It's also possible to provide a dict which is then used as context. The arguments are the same as for the :meth:`new_context` method. """ - ctx = self.new_context(vars, shared, locals) - return TemplateModule(self, ctx) - - async def make_module_async( - self, - vars: t.Optional[t.Dict[str, t.Any]] = None, - shared: bool = False, - locals: t.Optional[t.Mapping[str, t.Any]] = None, - ) -> "TemplateModule": + ctx = self.new_context(vars, shared, locals) + return TemplateModule(self, ctx) + + async def make_module_async( + self, + vars: t.Optional[t.Dict[str, t.Any]] = None, + shared: bool = False, + locals: t.Optional[t.Mapping[str, t.Any]] = None, + ) -> "TemplateModule": """As template module creation can invoke template code for - asynchronous executions this method must be used instead of the + asynchronous executions this method must be used instead of the normal :meth:`make_module` one. Likewise the module attribute becomes unavailable in async mode. """ - ctx = self.new_context(vars, shared, locals) - return TemplateModule( - self, ctx, [x async for x in self.root_render_func(ctx)] # type: ignore - ) + ctx = self.new_context(vars, shared, locals) + return TemplateModule( + self, ctx, [x async for x in self.root_render_func(ctx)] # type: ignore + ) @internalcode - def _get_default_module(self, ctx: t.Optional[Context] = None) -> "TemplateModule": - """If a context is passed in, this means that the template was - imported. Imported templates have access to the current - template's globals by default, but they can only be accessed via - the context during runtime. - - If there are new globals, we need to create a new module because - the cached module is already rendered and will not have access - to globals from the current context. This new module is not - cached because the template can be imported elsewhere, and it - should have access to only the current template's globals. - """ - if self.environment.is_async: - raise RuntimeError("Module is not available in async mode.") - - if ctx is not None: - keys = ctx.globals_keys - self.globals.keys() - - if keys: - return self.make_module({k: ctx.parent[k] for k in keys}) - - if self._module is None: - self._module = self.make_module() - - return self._module - - async def _get_default_module_async( - self, ctx: t.Optional[Context] = None - ) -> "TemplateModule": - if ctx is not None: - keys = ctx.globals_keys - self.globals.keys() - - if keys: - return await self.make_module_async({k: ctx.parent[k] for k in keys}) - - if self._module is None: - self._module = await self.make_module_async() - - return self._module - + def _get_default_module(self, ctx: t.Optional[Context] = None) -> "TemplateModule": + """If a context is passed in, this means that the template was + imported. Imported templates have access to the current + template's globals by default, but they can only be accessed via + the context during runtime. + + If there are new globals, we need to create a new module because + the cached module is already rendered and will not have access + to globals from the current context. This new module is not + cached because the template can be imported elsewhere, and it + should have access to only the current template's globals. + """ + if self.environment.is_async: + raise RuntimeError("Module is not available in async mode.") + + if ctx is not None: + keys = ctx.globals_keys - self.globals.keys() + + if keys: + return self.make_module({k: ctx.parent[k] for k in keys}) + + if self._module is None: + self._module = self.make_module() + + return self._module + + async def _get_default_module_async( + self, ctx: t.Optional[Context] = None + ) -> "TemplateModule": + if ctx is not None: + keys = ctx.globals_keys - self.globals.keys() + + if keys: + return await self.make_module_async({k: ctx.parent[k] for k in keys}) + + if self._module is None: + self._module = await self.make_module_async() + + return self._module + @property - def module(self) -> "TemplateModule": + def module(self) -> "TemplateModule": """The template as module. This is used for imports in the template runtime but is also useful if one wants to access exported template variables from the Python layer: @@ -1471,7 +1471,7 @@ class Template: """ return self._get_default_module() - def get_corresponding_lineno(self, lineno: int) -> int: + def get_corresponding_lineno(self, lineno: int) -> int: """Return the source line number of a line number in the generated bytecode as they are not in sync. """ @@ -1481,113 +1481,113 @@ class Template: return 1 @property - def is_up_to_date(self) -> bool: + def is_up_to_date(self) -> bool: """If this variable is `False` there is a newer version available.""" if self._uptodate is None: return True return self._uptodate() @property - def debug_info(self) -> t.List[t.Tuple[int, int]]: + def debug_info(self) -> t.List[t.Tuple[int, int]]: """The debug info mapping.""" - if self._debug_info: - return [ - tuple(map(int, x.split("="))) # type: ignore - for x in self._debug_info.split("&") - ] - - return [] - - def __repr__(self) -> str: + if self._debug_info: + return [ + tuple(map(int, x.split("="))) # type: ignore + for x in self._debug_info.split("&") + ] + + return [] + + def __repr__(self) -> str: if self.name is None: - name = f"memory:{id(self):x}" + name = f"memory:{id(self):x}" else: name = repr(self.name) - return f"<{type(self).__name__} {name}>" + return f"<{type(self).__name__} {name}>" -class TemplateModule: +class TemplateModule: """Represents an imported template. All the exported names of the template are available as attributes on this object. Additionally - converting it into a string renders the contents. + converting it into a string renders the contents. """ - def __init__( - self, - template: Template, - context: Context, - body_stream: t.Optional[t.Iterable[str]] = None, - ) -> None: + def __init__( + self, + template: Template, + context: Context, + body_stream: t.Optional[t.Iterable[str]] = None, + ) -> None: if body_stream is None: if context.environment.is_async: - raise RuntimeError( - "Async mode requires a body stream to be passed to" - " a template module. Use the async methods of the" - " API you are using." - ) - - body_stream = list(template.root_render_func(context)) # type: ignore - + raise RuntimeError( + "Async mode requires a body stream to be passed to" + " a template module. Use the async methods of the" + " API you are using." + ) + + body_stream = list(template.root_render_func(context)) # type: ignore + self._body_stream = body_stream self.__dict__.update(context.get_exported()) self.__name__ = template.name - def __html__(self) -> Markup: + def __html__(self) -> Markup: return Markup(concat(self._body_stream)) - def __str__(self) -> str: + def __str__(self) -> str: return concat(self._body_stream) - def __repr__(self) -> str: + def __repr__(self) -> str: if self.__name__ is None: - name = f"memory:{id(self):x}" + name = f"memory:{id(self):x}" else: name = repr(self.__name__) - return f"<{type(self).__name__} {name}>" + return f"<{type(self).__name__} {name}>" -class TemplateExpression: +class TemplateExpression: """The :meth:`jinja2.Environment.compile_expression` method returns an instance of this object. It encapsulates the expression-like access to the template with an expression it wraps. """ - def __init__(self, template: Template, undefined_to_none: bool) -> None: + def __init__(self, template: Template, undefined_to_none: bool) -> None: self._template = template self._undefined_to_none = undefined_to_none - def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]: + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]: context = self._template.new_context(dict(*args, **kwargs)) - consume(self._template.root_render_func(context)) # type: ignore - rv = context.vars["result"] + consume(self._template.root_render_func(context)) # type: ignore + rv = context.vars["result"] if self._undefined_to_none and isinstance(rv, Undefined): rv = None return rv -class TemplateStream: +class TemplateStream: """A template stream works pretty much like an ordinary python generator but it can buffer multiple items to reduce the number of total iterations. Per default the output is unbuffered which means that for every unbuffered - instruction in the template one string is yielded. + instruction in the template one string is yielded. If buffering is enabled with a buffer size of 5, five items are combined - into a new string. This is mainly useful if you are streaming + into a new string. This is mainly useful if you are streaming big templates to a client via WSGI which flushes after each iteration. """ - def __init__(self, gen: t.Iterator[str]) -> None: + def __init__(self, gen: t.Iterator[str]) -> None: self._gen = gen self.disable_buffering() - def dump( - self, - fp: t.Union[str, t.IO], - encoding: t.Optional[str] = None, - errors: t.Optional[str] = "strict", - ) -> None: + def dump( + self, + fp: t.Union[str, t.IO], + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + ) -> None: """Dump the complete stream into a file or file-like object. - Per default strings are written, if you want to encode + Per default strings are written, if you want to encode before writing specify an `encoding`. Example usage:: @@ -1595,20 +1595,20 @@ class TemplateStream: Template('Hello {{ name }}!').stream(name='foo').dump('hello.html') """ close = False - - if isinstance(fp, str): + + if isinstance(fp, str): if encoding is None: - encoding = "utf-8" - - fp = open(fp, "wb") + encoding = "utf-8" + + fp = open(fp, "wb") close = True try: if encoding is not None: - iterable = (x.encode(encoding, errors) for x in self) # type: ignore + iterable = (x.encode(encoding, errors) for x in self) # type: ignore else: - iterable = self # type: ignore - - if hasattr(fp, "writelines"): + iterable = self # type: ignore + + if hasattr(fp, "writelines"): fp.writelines(iterable) else: for item in iterable: @@ -1617,17 +1617,17 @@ class TemplateStream: if close: fp.close() - def disable_buffering(self) -> None: + def disable_buffering(self) -> None: """Disable the output buffering.""" self._next = partial(next, self._gen) self.buffered = False - def _buffered_generator(self, size: int) -> t.Iterator[str]: - buf: t.List[str] = [] + def _buffered_generator(self, size: int) -> t.Iterator[str]: + buf: t.List[str] = [] c_size = 0 push = buf.append - while True: + while True: try: while c_size < size: c = next(self._gen) @@ -1641,19 +1641,19 @@ class TemplateStream: del buf[:] c_size = 0 - def enable_buffering(self, size: int = 5) -> None: + def enable_buffering(self, size: int = 5) -> None: """Enable buffering. Buffer `size` items before yielding them.""" if size <= 1: - raise ValueError("buffer size too small") + raise ValueError("buffer size too small") self.buffered = True self._next = partial(next, self._buffered_generator(size)) - def __iter__(self) -> "TemplateStream": + def __iter__(self) -> "TemplateStream": return self - def __next__(self) -> str: - return self._next() # type: ignore + def __next__(self) -> str: + return self._next() # type: ignore # hook in default template class. if anyone reads this comment: ignore that diff --git a/contrib/python/Jinja2/py3/jinja2/exceptions.py b/contrib/python/Jinja2/py3/jinja2/exceptions.py index 082ebe8f22..57fcf671a4 100644 --- a/contrib/python/Jinja2/py3/jinja2/exceptions.py +++ b/contrib/python/Jinja2/py3/jinja2/exceptions.py @@ -1,53 +1,53 @@ -import typing as t - -if t.TYPE_CHECKING: - from .runtime import Undefined +import typing as t +if t.TYPE_CHECKING: + from .runtime import Undefined + class TemplateError(Exception): """Baseclass for all template errors.""" - def __init__(self, message: t.Optional[str] = None) -> None: - super().__init__(message) - - @property - def message(self) -> t.Optional[str]: - return self.args[0] if self.args else None + def __init__(self, message: t.Optional[str] = None) -> None: + super().__init__(message) + + @property + def message(self) -> t.Optional[str]: + return self.args[0] if self.args else None class TemplateNotFound(IOError, LookupError, TemplateError): - """Raised if a template does not exist. - - .. versionchanged:: 2.11 - If the given name is :class:`Undefined` and no message was - provided, an :exc:`UndefinedError` is raised. - """ - - # Silence the Python warning about message being deprecated since - # it's not valid here. - message: t.Optional[str] = None - - def __init__( - self, - name: t.Optional[t.Union[str, "Undefined"]], - message: t.Optional[str] = None, - ) -> None: - IOError.__init__(self, name) - + """Raised if a template does not exist. + + .. versionchanged:: 2.11 + If the given name is :class:`Undefined` and no message was + provided, an :exc:`UndefinedError` is raised. + """ + + # Silence the Python warning about message being deprecated since + # it's not valid here. + message: t.Optional[str] = None + + def __init__( + self, + name: t.Optional[t.Union[str, "Undefined"]], + message: t.Optional[str] = None, + ) -> None: + IOError.__init__(self, name) + if message is None: - from .runtime import Undefined - - if isinstance(name, Undefined): - name._fail_with_undefined_error() - + from .runtime import Undefined + + if isinstance(name, Undefined): + name._fail_with_undefined_error() + message = name - + self.message = message self.name = name self.templates = [name] - def __str__(self) -> str: - return str(self.message) + def __str__(self) -> str: + return str(self.message) class TemplatesNotFound(TemplateNotFound): @@ -55,87 +55,87 @@ class TemplatesNotFound(TemplateNotFound): are selected. This is a subclass of :class:`TemplateNotFound` exception, so just catching the base exception will catch both. - .. versionchanged:: 2.11 - If a name in the list of names is :class:`Undefined`, a message - about it being undefined is shown rather than the empty string. - + .. versionchanged:: 2.11 + If a name in the list of names is :class:`Undefined`, a message + about it being undefined is shown rather than the empty string. + .. versionadded:: 2.2 """ - def __init__( - self, - names: t.Sequence[t.Union[str, "Undefined"]] = (), - message: t.Optional[str] = None, - ) -> None: + def __init__( + self, + names: t.Sequence[t.Union[str, "Undefined"]] = (), + message: t.Optional[str] = None, + ) -> None: if message is None: - from .runtime import Undefined - - parts = [] - - for name in names: - if isinstance(name, Undefined): - parts.append(name._undefined_message) - else: - parts.append(name) - - parts_str = ", ".join(map(str, parts)) - message = f"none of the templates given were found: {parts_str}" - - super().__init__(names[-1] if names else None, message) + from .runtime import Undefined + + parts = [] + + for name in names: + if isinstance(name, Undefined): + parts.append(name._undefined_message) + else: + parts.append(name) + + parts_str = ", ".join(map(str, parts)) + message = f"none of the templates given were found: {parts_str}" + + super().__init__(names[-1] if names else None, message) self.templates = list(names) class TemplateSyntaxError(TemplateError): """Raised to tell the user that there is a problem with the template.""" - def __init__( - self, - message: str, - lineno: int, - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - ) -> None: - super().__init__(message) + def __init__( + self, + message: str, + lineno: int, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> None: + super().__init__(message) self.lineno = lineno self.name = name self.filename = filename - self.source: t.Optional[str] = None + self.source: t.Optional[str] = None # this is set to True if the debug.translate_syntax_error # function translated the syntax error into a new traceback self.translated = False - def __str__(self) -> str: + def __str__(self) -> str: # for translated errors we only return the message if self.translated: - return t.cast(str, self.message) + return t.cast(str, self.message) # otherwise attach some stuff - location = f"line {self.lineno}" + location = f"line {self.lineno}" name = self.filename or self.name if name: - location = f'File "{name}", {location}' - lines = [t.cast(str, self.message), " " + location] + location = f'File "{name}", {location}' + lines = [t.cast(str, self.message), " " + location] # if the source is set, add the line to the output if self.source is not None: try: line = self.source.splitlines()[self.lineno - 1] except IndexError: - pass - else: - lines.append(" " + line.strip()) - - return "\n".join(lines) + pass + else: + lines.append(" " + line.strip()) - def __reduce__(self): # type: ignore - # https://bugs.python.org/issue1692335 Exceptions that take - # multiple required arguments have problems with pickling. - # Without this, raises TypeError: __init__() missing 1 required - # positional argument: 'lineno' - return self.__class__, (self.message, self.lineno, self.name, self.filename) + return "\n".join(lines) + def __reduce__(self): # type: ignore + # https://bugs.python.org/issue1692335 Exceptions that take + # multiple required arguments have problems with pickling. + # Without this, raises TypeError: __init__() missing 1 required + # positional argument: 'lineno' + return self.__class__, (self.message, self.lineno, self.name, self.filename) + class TemplateAssertionError(TemplateSyntaxError): """Like a template syntax error, but covers cases where something in the template caused an error at compile time that wasn't necessarily caused diff --git a/contrib/python/Jinja2/py3/jinja2/ext.py b/contrib/python/Jinja2/py3/jinja2/ext.py index 3e982930c4..04f14736a1 100644 --- a/contrib/python/Jinja2/py3/jinja2/ext.py +++ b/contrib/python/Jinja2/py3/jinja2/ext.py @@ -1,58 +1,58 @@ -"""Extension API for adding custom tags and behavior.""" -import pprint +"""Extension API for adding custom tags and behavior.""" +import pprint import re -import typing as t -import warnings - -from markupsafe import Markup - -from . import defaults -from . import nodes -from .environment import Environment -from .exceptions import TemplateAssertionError -from .exceptions import TemplateSyntaxError -from .runtime import concat # type: ignore -from .runtime import Context -from .runtime import Undefined -from .utils import import_string -from .utils import pass_context - -if t.TYPE_CHECKING: - import typing_extensions as te - from .lexer import Token - from .lexer import TokenStream - from .parser import Parser - - class _TranslationsBasic(te.Protocol): - def gettext(self, message: str) -> str: - ... - - def ngettext(self, singular: str, plural: str, n: int) -> str: - pass - - class _TranslationsContext(_TranslationsBasic): - def pgettext(self, context: str, message: str) -> str: - ... - - def npgettext(self, context: str, singular: str, plural: str, n: int) -> str: - ... - - _SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext] - - -# I18N functions available in Jinja templates. If the I18N library -# provides ugettext, it will be assigned to gettext. -GETTEXT_FUNCTIONS: t.Tuple[str, ...] = ( - "_", - "gettext", - "ngettext", - "pgettext", - "npgettext", -) -_ws_re = re.compile(r"\s*\n\s*") - - -class Extension: +import typing as t +import warnings + +from markupsafe import Markup + +from . import defaults +from . import nodes +from .environment import Environment +from .exceptions import TemplateAssertionError +from .exceptions import TemplateSyntaxError +from .runtime import concat # type: ignore +from .runtime import Context +from .runtime import Undefined +from .utils import import_string +from .utils import pass_context + +if t.TYPE_CHECKING: + import typing_extensions as te + from .lexer import Token + from .lexer import TokenStream + from .parser import Parser + + class _TranslationsBasic(te.Protocol): + def gettext(self, message: str) -> str: + ... + + def ngettext(self, singular: str, plural: str, n: int) -> str: + pass + + class _TranslationsContext(_TranslationsBasic): + def pgettext(self, context: str, message: str) -> str: + ... + + def npgettext(self, context: str, singular: str, plural: str, n: int) -> str: + ... + + _SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext] + + +# I18N functions available in Jinja templates. If the I18N library +# provides ugettext, it will be assigned to gettext. +GETTEXT_FUNCTIONS: t.Tuple[str, ...] = ( + "_", + "gettext", + "ngettext", + "pgettext", + "npgettext", +) +_ws_re = re.compile(r"\s*\n\s*") + + +class Extension: """Extensions can be used to add extra functionality to the Jinja template system at the parser level. Custom extensions are bound to an environment but may not store environment specific data on `self`. The reason for @@ -71,13 +71,13 @@ class Extension: name as includes the name of the extension (fragment cache). """ - identifier: t.ClassVar[str] - - def __init_subclass__(cls) -> None: - cls.identifier = f"{cls.__module__}.{cls.__name__}" - + identifier: t.ClassVar[str] + + def __init_subclass__(cls) -> None: + cls.identifier = f"{cls.__module__}.{cls.__name__}" + #: if this extension parses this is the list of tags it's listening to. - tags: t.Set[str] = set() + tags: t.Set[str] = set() #: the priority of that extension. This is especially useful for #: extensions that preprocess values. A lower value means higher @@ -86,28 +86,28 @@ class Extension: #: .. versionadded:: 2.4 priority = 100 - def __init__(self, environment: Environment) -> None: + def __init__(self, environment: Environment) -> None: self.environment = environment - def bind(self, environment: Environment) -> "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 = t.cast(Extension, object.__new__(self.__class__)) rv.__dict__.update(self.__dict__) rv.environment = environment return rv - def preprocess( - self, source: str, name: t.Optional[str], filename: t.Optional[str] = None - ) -> str: + def preprocess( + self, source: str, name: t.Optional[str], filename: t.Optional[str] = None + ) -> str: """This method is called before the actual lexing and can be used to preprocess the source. The `filename` is optional. The return value must be the preprocessed source. """ return source - def filter_stream( - self, stream: "TokenStream" - ) -> t.Union["TokenStream", t.Iterable["Token"]]: + def filter_stream( + self, stream: "TokenStream" + ) -> t.Union["TokenStream", t.Iterable["Token"]]: """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used to filter tokens returned. This method has to return an iterable of :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a @@ -115,7 +115,7 @@ class Extension: """ return stream - def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]: + def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]: """If any of the :attr:`tags` matched this method is called with the parser as first argument. The token the parser stream is pointing at is the name token that matched. This method has to return one or a @@ -123,9 +123,9 @@ class Extension: """ raise NotImplementedError() - def attr( - self, name: str, lineno: t.Optional[int] = None - ) -> nodes.ExtensionAttribute: + def attr( + self, name: str, lineno: t.Optional[int] = None + ) -> nodes.ExtensionAttribute: """Return an attribute node for the current extension. This is useful to pass constants on extensions to generated template code. @@ -135,15 +135,15 @@ class Extension: """ return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) - def call_method( - self, - name: str, - args: t.Optional[t.List[nodes.Expr]] = None, - kwargs: t.Optional[t.List[nodes.Keyword]] = None, - dyn_args: t.Optional[nodes.Expr] = None, - dyn_kwargs: t.Optional[nodes.Expr] = None, - lineno: t.Optional[int] = None, - ) -> nodes.Call: + def call_method( + self, + name: str, + args: t.Optional[t.List[nodes.Expr]] = None, + kwargs: t.Optional[t.List[nodes.Keyword]] = None, + dyn_args: t.Optional[nodes.Expr] = None, + dyn_kwargs: t.Optional[nodes.Expr] = None, + lineno: t.Optional[int] = None, + ) -> nodes.Call: """Call a method of the extension. This is a shortcut for :meth:`attr` + :class:`jinja2.nodes.Call`. """ @@ -151,103 +151,103 @@ class Extension: args = [] if kwargs is None: kwargs = [] - return nodes.Call( - self.attr(name, lineno=lineno), - args, - kwargs, - dyn_args, - dyn_kwargs, - lineno=lineno, - ) - - -@pass_context -def _gettext_alias( - __context: Context, *args: t.Any, **kwargs: t.Any -) -> t.Union[t.Any, Undefined]: - return __context.call(__context.resolve("gettext"), *args, **kwargs) - - -def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]: - @pass_context - def gettext(__context: Context, __string: str, **variables: t.Any) -> str: + return nodes.Call( + self.attr(name, lineno=lineno), + args, + kwargs, + dyn_args, + dyn_kwargs, + lineno=lineno, + ) + + +@pass_context +def _gettext_alias( + __context: Context, *args: t.Any, **kwargs: t.Any +) -> t.Union[t.Any, Undefined]: + return __context.call(__context.resolve("gettext"), *args, **kwargs) + + +def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]: + @pass_context + def gettext(__context: Context, __string: str, **variables: t.Any) -> str: rv = __context.call(func, __string) if __context.eval_ctx.autoescape: rv = Markup(rv) - # Always treat as a format string, even if there are no - # variables. This makes translation strings more consistent - # and predictable. This requires escaping - return rv % variables # type: ignore - + # Always treat as a format string, even if there are no + # variables. This makes translation strings more consistent + # and predictable. This requires escaping + return rv % variables # type: ignore + return gettext -def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]: - @pass_context - def ngettext( - __context: Context, - __singular: str, - __plural: str, - __num: int, - **variables: t.Any, - ) -> str: - variables.setdefault("num", __num) +def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]: + @pass_context + def ngettext( + __context: Context, + __singular: str, + __plural: str, + __num: int, + **variables: t.Any, + ) -> str: + variables.setdefault("num", __num) rv = __context.call(func, __singular, __plural, __num) if __context.eval_ctx.autoescape: rv = Markup(rv) - # Always treat as a format string, see gettext comment above. - return rv % variables # type: ignore - + # Always treat as a format string, see gettext comment above. + return rv % variables # type: ignore + return ngettext -def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]: - @pass_context - def pgettext( - __context: Context, __string_ctx: str, __string: str, **variables: t.Any - ) -> str: - variables.setdefault("context", __string_ctx) - rv = __context.call(func, __string_ctx, __string) - - if __context.eval_ctx.autoescape: - rv = Markup(rv) - - # Always treat as a format string, see gettext comment above. - return rv % variables # type: ignore - - return pgettext - - -def _make_new_npgettext( - func: t.Callable[[str, str, str, int], str] -) -> t.Callable[..., str]: - @pass_context - def npgettext( - __context: Context, - __string_ctx: str, - __singular: str, - __plural: str, - __num: int, - **variables: t.Any, - ) -> str: - variables.setdefault("context", __string_ctx) - variables.setdefault("num", __num) - rv = __context.call(func, __string_ctx, __singular, __plural, __num) - - if __context.eval_ctx.autoescape: - rv = Markup(rv) - - # Always treat as a format string, see gettext comment above. - return rv % variables # type: ignore - - return npgettext - - +def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]: + @pass_context + def pgettext( + __context: Context, __string_ctx: str, __string: str, **variables: t.Any + ) -> str: + variables.setdefault("context", __string_ctx) + rv = __context.call(func, __string_ctx, __string) + + if __context.eval_ctx.autoescape: + rv = Markup(rv) + + # Always treat as a format string, see gettext comment above. + return rv % variables # type: ignore + + return pgettext + + +def _make_new_npgettext( + func: t.Callable[[str, str, str, int], str] +) -> t.Callable[..., str]: + @pass_context + def npgettext( + __context: Context, + __string_ctx: str, + __singular: str, + __plural: str, + __num: int, + **variables: t.Any, + ) -> str: + variables.setdefault("context", __string_ctx) + variables.setdefault("num", __num) + rv = __context.call(func, __string_ctx, __singular, __plural, __num) + + if __context.eval_ctx.autoescape: + rv = Markup(rv) + + # Always treat as a format string, see gettext comment above. + return rv % variables # type: ignore + + return npgettext + + class InternationalizationExtension(Extension): - """This extension adds gettext support to Jinja.""" - - tags = {"trans"} + """This extension adds gettext support to Jinja.""" + tags = {"trans"} + # TODO: the i18n extension is currently reevaluating values in a few # situations. Take this example: # {% trans count=something() %}{{ count }} foo{% pluralize @@ -255,104 +255,104 @@ class InternationalizationExtension(Extension): # something is called twice here. One time for the gettext value and # the other time for the n-parameter of the ngettext function. - def __init__(self, environment: Environment) -> None: - super().__init__(environment) - environment.globals["_"] = _gettext_alias + def __init__(self, environment: Environment) -> None: + super().__init__(environment) + environment.globals["_"] = _gettext_alias environment.extend( install_gettext_translations=self._install, install_null_translations=self._install_null, install_gettext_callables=self._install_callables, uninstall_gettext_translations=self._uninstall, extract_translations=self._extract, - newstyle_gettext=False, + newstyle_gettext=False, ) - def _install( - self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None - ) -> None: - # ugettext and ungettext are preferred in case the I18N library - # is providing compatibility with older Python versions. - gettext = getattr(translations, "ugettext", None) + def _install( + self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None + ) -> None: + # ugettext and ungettext are preferred in case the I18N library + # is providing compatibility with older Python versions. + gettext = getattr(translations, "ugettext", None) if gettext is None: gettext = translations.gettext - ngettext = getattr(translations, "ungettext", None) + ngettext = getattr(translations, "ungettext", None) if ngettext is None: ngettext = translations.ngettext - pgettext = getattr(translations, "pgettext", None) - npgettext = getattr(translations, "npgettext", None) - self._install_callables( - gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext - ) - - def _install_null(self, newstyle: t.Optional[bool] = None) -> None: - import gettext - - translations = gettext.NullTranslations() - - if hasattr(translations, "pgettext"): - # Python < 3.8 - pgettext = translations.pgettext # type: ignore - else: - - def pgettext(c: str, s: str) -> str: - return s - - if hasattr(translations, "npgettext"): - npgettext = translations.npgettext # type: ignore - else: - - def npgettext(c: str, s: str, p: str, n: int) -> str: - return s if n == 1 else p - + pgettext = getattr(translations, "pgettext", None) + npgettext = getattr(translations, "npgettext", None) self._install_callables( - gettext=translations.gettext, - ngettext=translations.ngettext, - newstyle=newstyle, - pgettext=pgettext, - npgettext=npgettext, + gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext ) - def _install_callables( - self, - gettext: t.Callable[[str], str], - ngettext: t.Callable[[str, str, int], str], - newstyle: t.Optional[bool] = None, - pgettext: t.Optional[t.Callable[[str, str], str]] = None, - npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None, - ) -> None: + def _install_null(self, newstyle: t.Optional[bool] = None) -> None: + import gettext + + translations = gettext.NullTranslations() + + if hasattr(translations, "pgettext"): + # Python < 3.8 + pgettext = translations.pgettext # type: ignore + else: + + def pgettext(c: str, s: str) -> str: + return s + + if hasattr(translations, "npgettext"): + npgettext = translations.npgettext # type: ignore + else: + + def npgettext(c: str, s: str, p: str, n: int) -> str: + return s if n == 1 else p + + self._install_callables( + gettext=translations.gettext, + ngettext=translations.ngettext, + newstyle=newstyle, + pgettext=pgettext, + npgettext=npgettext, + ) + + def _install_callables( + self, + gettext: t.Callable[[str], str], + ngettext: t.Callable[[str, str, int], str], + newstyle: t.Optional[bool] = None, + pgettext: t.Optional[t.Callable[[str, str], str]] = None, + npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None, + ) -> None: if newstyle is not None: - self.environment.newstyle_gettext = newstyle # type: ignore - if self.environment.newstyle_gettext: # type: ignore + self.environment.newstyle_gettext = newstyle # type: ignore + if self.environment.newstyle_gettext: # type: ignore gettext = _make_new_gettext(gettext) ngettext = _make_new_ngettext(ngettext) - if pgettext is not None: - pgettext = _make_new_pgettext(pgettext) - - if npgettext is not None: - npgettext = _make_new_npgettext(npgettext) - - self.environment.globals.update( - gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext - ) - - def _uninstall(self, translations: "_SupportedTranslations") -> None: - for key in ("gettext", "ngettext", "pgettext", "npgettext"): + if pgettext is not None: + pgettext = _make_new_pgettext(pgettext) + + if npgettext is not None: + npgettext = _make_new_npgettext(npgettext) + + self.environment.globals.update( + gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext + ) + + def _uninstall(self, translations: "_SupportedTranslations") -> None: + for key in ("gettext", "ngettext", "pgettext", "npgettext"): self.environment.globals.pop(key, None) - def _extract( - self, - source: t.Union[str, nodes.Template], - gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS, - ) -> t.Iterator[ - t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]] - ]: - if isinstance(source, str): + def _extract( + self, + source: t.Union[str, nodes.Template], + gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS, + ) -> t.Iterator[ + t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]] + ]: + if isinstance(source, str): source = self.environment.parse(source) return extract_from_ast(source, gettext_functions) - def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]: + def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]: """Parse a translatable tag.""" lineno = next(parser.stream).lineno num_called_num = False @@ -360,48 +360,48 @@ class InternationalizationExtension(Extension): # 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 - variables: t.Dict[str, nodes.Expr] = {} + plural_expr: t.Optional[nodes.Expr] = None + plural_expr_assignment: t.Optional[nodes.Assign] = None + variables: t.Dict[str, nodes.Expr] = {} trimmed = None - while parser.stream.current.type != "block_end": + while parser.stream.current.type != "block_end": if variables: - parser.stream.expect("comma") + parser.stream.expect("comma") # skip colon for python compatibility - if parser.stream.skip_if("colon"): + if parser.stream.skip_if("colon"): break - token = parser.stream.expect("name") - if token.value in variables: - parser.fail( - f"translatable variable {token.value!r} defined twice.", - token.lineno, - exc=TemplateAssertionError, - ) + token = parser.stream.expect("name") + if token.value in variables: + parser.fail( + f"translatable variable {token.value!r} defined twice.", + token.lineno, + exc=TemplateAssertionError, + ) # expressions - if parser.stream.current.type == "assign": + if parser.stream.current.type == "assign": next(parser.stream) - variables[token.value] = var = parser.parse_expression() - elif trimmed is None and token.value in ("trimmed", "notrimmed"): - trimmed = token.value == "trimmed" + variables[token.value] = var = parser.parse_expression() + elif trimmed is None and token.value in ("trimmed", "notrimmed"): + trimmed = token.value == "trimmed" continue else: - variables[token.value] = var = nodes.Name(token.value, "load") + variables[token.value] = var = nodes.Name(token.value, "load") if plural_expr is None: if isinstance(var, nodes.Call): - plural_expr = nodes.Name("_trans", "load") - variables[token.value] = plural_expr + plural_expr = nodes.Name("_trans", "load") + variables[token.value] = plural_expr plural_expr_assignment = nodes.Assign( - nodes.Name("_trans", "store"), var - ) + nodes.Name("_trans", "store"), var + ) else: plural_expr = var - num_called_num = token.value == "num" + num_called_num = token.value == "num" - parser.stream.expect("block_end") + parser.stream.expect("block_end") plural = None have_plural = False @@ -412,24 +412,24 @@ class InternationalizationExtension(Extension): if singular_names: referenced.update(singular_names) if plural_expr is None: - plural_expr = nodes.Name(singular_names[0], "load") - num_called_num = singular_names[0] == "num" + plural_expr = nodes.Name(singular_names[0], "load") + num_called_num = singular_names[0] == "num" # if we have a pluralize block, we parse that too - if parser.stream.current.test("name:pluralize"): + if parser.stream.current.test("name:pluralize"): have_plural = True next(parser.stream) - if parser.stream.current.type != "block_end": - token = parser.stream.expect("name") - if token.value not in variables: - parser.fail( - f"unknown variable {token.value!r} for pluralization", - token.lineno, - exc=TemplateAssertionError, - ) - plural_expr = variables[token.value] - num_called_num = token.value == "num" - parser.stream.expect("block_end") + if parser.stream.current.type != "block_end": + token = parser.stream.expect("name") + if token.value not in variables: + parser.fail( + f"unknown variable {token.value!r} for pluralization", + token.lineno, + exc=TemplateAssertionError, + ) + plural_expr = variables[token.value] + num_called_num = token.value == "num" + parser.stream.expect("block_end") plural_names, plural = self._parse_block(parser, False) next(parser.stream) referenced.update(plural_names) @@ -437,120 +437,120 @@ class InternationalizationExtension(Extension): next(parser.stream) # register free names as simple name expressions - for name in referenced: - if name not in variables: - variables[name] = nodes.Name(name, "load") + for name in referenced: + if name not in variables: + variables[name] = nodes.Name(name, "load") if not have_plural: plural_expr = None elif plural_expr is None: - parser.fail("pluralize without variables", lineno) + parser.fail("pluralize without variables", lineno) if trimmed is None: - trimmed = self.environment.policies["ext.i18n.trimmed"] + trimmed = self.environment.policies["ext.i18n.trimmed"] if trimmed: singular = self._trim_whitespace(singular) if plural: plural = self._trim_whitespace(plural) - node = self._make_node( - singular, - plural, - variables, - plural_expr, - bool(referenced), - num_called_num and have_plural, - ) + node = self._make_node( + singular, + plural, + variables, + plural_expr, + bool(referenced), + num_called_num and have_plural, + ) node.set_lineno(lineno) if plural_expr_assignment is not None: return [plural_expr_assignment, node] else: return node - def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str: - return _ws_re.sub(" ", string.strip()) + def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str: + return _ws_re.sub(" ", string.strip()) - def _parse_block( - self, parser: "Parser", allow_pluralize: bool - ) -> t.Tuple[t.List[str], str]: + def _parse_block( + self, parser: "Parser", allow_pluralize: bool + ) -> t.Tuple[t.List[str], str]: """Parse until the next block tag with a given name.""" referenced = [] buf = [] - - while True: - if parser.stream.current.type == "data": - buf.append(parser.stream.current.value.replace("%", "%%")) + + while True: + if parser.stream.current.type == "data": + buf.append(parser.stream.current.value.replace("%", "%%")) next(parser.stream) - elif parser.stream.current.type == "variable_begin": + elif parser.stream.current.type == "variable_begin": next(parser.stream) - name = parser.stream.expect("name").value + name = parser.stream.expect("name").value referenced.append(name) - buf.append(f"%({name})s") - parser.stream.expect("variable_end") - elif parser.stream.current.type == "block_begin": + buf.append(f"%({name})s") + parser.stream.expect("variable_end") + elif parser.stream.current.type == "block_begin": next(parser.stream) - if parser.stream.current.test("name:endtrans"): + if parser.stream.current.test("name:endtrans"): break - elif parser.stream.current.test("name:pluralize"): + elif parser.stream.current.test("name:pluralize"): if allow_pluralize: break - parser.fail( - "a translatable section can have only one pluralize section" - ) - parser.fail( - "control structures in translatable sections are not allowed" - ) + parser.fail( + "a translatable section can have only one pluralize section" + ) + parser.fail( + "control structures in translatable sections are not allowed" + ) elif parser.stream.eos: - parser.fail("unclosed translation block") + parser.fail("unclosed translation block") else: - raise RuntimeError("internal parser error") + raise RuntimeError("internal parser error") return referenced, concat(buf) - def _make_node( - self, - singular: str, - plural: t.Optional[str], - variables: t.Dict[str, nodes.Expr], - plural_expr: t.Optional[nodes.Expr], - vars_referenced: bool, - num_called_num: bool, - ) -> nodes.Output: + def _make_node( + self, + singular: str, + plural: t.Optional[str], + variables: t.Dict[str, nodes.Expr], + plural_expr: t.Optional[nodes.Expr], + vars_referenced: bool, + num_called_num: bool, + ) -> nodes.Output: """Generates a useful node from the data provided.""" - newstyle = self.environment.newstyle_gettext # type: ignore - node: nodes.Expr - + newstyle = self.environment.newstyle_gettext # type: ignore + node: nodes.Expr + # no variables referenced? no need to escape for old style # gettext invocations only if there are vars. - if not vars_referenced and not newstyle: - singular = singular.replace("%%", "%") + if not vars_referenced and not newstyle: + singular = singular.replace("%%", "%") if plural: - plural = plural.replace("%%", "%") + plural = plural.replace("%%", "%") # singular only: if plural_expr is None: - gettext = nodes.Name("gettext", "load") - node = nodes.Call(gettext, [nodes.Const(singular)], [], None, None) + gettext = nodes.Name("gettext", "load") + node = nodes.Call(gettext, [nodes.Const(singular)], [], None, None) # singular and plural else: - ngettext = nodes.Name("ngettext", "load") - node = nodes.Call( - ngettext, - [nodes.Const(singular), nodes.Const(plural), plural_expr], - [], - None, - None, - ) + ngettext = nodes.Name("ngettext", "load") + node = nodes.Call( + ngettext, + [nodes.Const(singular), nodes.Const(plural), plural_expr], + [], + None, + None, + ) # in case newstyle gettext is used, the method is powerful # enough to handle the variable expansion and autoescape # handling itself - if newstyle: - for key, value in variables.items(): + if newstyle: + for key, value in variables.items(): # the function adds that later anyways in case num was # called num, so just skip it. - if num_called_num and key == "num": + if num_called_num and key == "num": continue node.kwargs.append(nodes.Keyword(key, value)) @@ -560,26 +560,26 @@ class InternationalizationExtension(Extension): # environment with autoescaping turned on node = nodes.MarkSafeIfAutoescape(node) if variables: - node = nodes.Mod( - node, - nodes.Dict( - [ - nodes.Pair(nodes.Const(key), value) - for key, value in variables.items() - ] - ), - ) + node = nodes.Mod( + node, + nodes.Dict( + [ + nodes.Pair(nodes.Const(key), value) + for key, value in variables.items() + ] + ), + ) return nodes.Output([node]) class ExprStmtExtension(Extension): - """Adds a `do` tag to Jinja that works like the print statement just + """Adds a `do` tag to Jinja that works like the print statement just that it doesn't print the return value. """ - tags = {"do"} - - def parse(self, parser: "Parser") -> nodes.ExprStmt: + tags = {"do"} + + def parse(self, parser: "Parser") -> nodes.ExprStmt: node = nodes.ExprStmt(lineno=next(parser.stream).lineno) node.node = parser.parse_tuple() return node @@ -588,84 +588,84 @@ class ExprStmtExtension(Extension): class LoopControlExtension(Extension): """Adds break and continue to the template engine.""" - tags = {"break", "continue"} - - def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]: + tags = {"break", "continue"} + + def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]: token = next(parser.stream) - if token.value == "break": + if token.value == "break": return nodes.Break(lineno=token.lineno) return nodes.Continue(lineno=token.lineno) class WithExtension(Extension): - def __init__(self, environment: Environment) -> None: - super().__init__(environment) - warnings.warn( - "The 'with' extension is deprecated and will be removed in" - " Jinja 3.1. This is built in now.", - DeprecationWarning, - stacklevel=3, - ) + 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. - - .. code-block:: html+jinja - - <pre>{% debug %}</pre> - - .. code-block:: text - - {'context': {'cycler': <class 'jinja2.utils.Cycler'>, - ..., - 'namespace': <class 'jinja2.utils.Namespace'>}, - 'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd', - ..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'], - 'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined', - ..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']} - - .. versionadded:: 2.11.0 - """ - - tags = {"debug"} - - def parse(self, parser: "Parser") -> nodes.Output: - lineno = parser.stream.expect("name:debug").lineno - context = nodes.ContextReference() - result = self.call_method("_render", [context], lineno=lineno) - return nodes.Output([result], lineno=lineno) - - def _render(self, context: Context) -> str: - result = { - "context": context.get_all(), - "filters": sorted(self.environment.filters.keys()), - "tests": sorted(self.environment.tests.keys()), - } - - # Set the depth since the intent is to show the top few names. - return pprint.pformat(result, depth=3, compact=True) - - -def extract_from_ast( - ast: nodes.Template, - gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS, - babel_style: bool = True, -) -> t.Iterator[ - t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]] -]: + 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. + + .. code-block:: html+jinja + + <pre>{% debug %}</pre> + + .. code-block:: text + + {'context': {'cycler': <class 'jinja2.utils.Cycler'>, + ..., + 'namespace': <class 'jinja2.utils.Namespace'>}, + 'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd', + ..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'], + 'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined', + ..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']} + + .. versionadded:: 2.11.0 + """ + + tags = {"debug"} + + def parse(self, parser: "Parser") -> nodes.Output: + lineno = parser.stream.expect("name:debug").lineno + context = nodes.ContextReference() + result = self.call_method("_render", [context], lineno=lineno) + return nodes.Output([result], lineno=lineno) + + def _render(self, context: Context) -> str: + result = { + "context": context.get_all(), + "filters": sorted(self.environment.filters.keys()), + "tests": sorted(self.environment.tests.keys()), + } + + # Set the depth since the intent is to show the top few names. + return pprint.pformat(result, depth=3, compact=True) + + +def extract_from_ast( + ast: nodes.Template, + gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS, + babel_style: bool = True, +) -> t.Iterator[ + t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]] +]: """Extract localizable strings from the given template node. Per default this function returns matches in babel style that means non string parameters as well as keyword arguments are returned as `None`. This @@ -693,31 +693,31 @@ def extract_from_ast( * ``lineno`` is the number of the line on which the string was found, * ``function`` is the name of the ``gettext`` function used (if the string was extracted from embedded Python code), and - * ``message`` is the string, or a tuple of strings for functions - with multiple string arguments. + * ``message`` is the string, or a tuple of strings for functions + with multiple string arguments. This extraction function operates on the AST and is because of that unable to extract any comments. For comment support you have to use the babel extraction interface or extract comments yourself. """ - out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]] - - for node in ast.find_all(nodes.Call): - if ( - not isinstance(node.node, nodes.Name) - or node.node.name not in gettext_functions - ): + out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]] + + for node in ast.find_all(nodes.Call): + if ( + not isinstance(node.node, nodes.Name) + or node.node.name not in gettext_functions + ): continue - strings: t.List[t.Optional[str]] = [] - + strings: t.List[t.Optional[str]] = [] + for arg in node.args: - if isinstance(arg, nodes.Const) and isinstance(arg.value, str): + if isinstance(arg, nodes.Const) and isinstance(arg.value, str): strings.append(arg.value) else: strings.append(None) - for _ in node.kwargs: + for _ in node.kwargs: strings.append(None) if node.dyn_args is not None: strings.append(None) @@ -725,40 +725,40 @@ def extract_from_ast( strings.append(None) if not babel_style: - out = tuple(x for x in strings if x is not None) - - if not out: + out = tuple(x for x in strings if x is not None) + + if not out: continue else: if len(strings) == 1: - out = strings[0] + out = strings[0] else: - out = tuple(strings) - - yield node.lineno, node.node.name, out + out = tuple(strings) + yield node.lineno, node.node.name, out -class _CommentFinder: + +class _CommentFinder: """Helper class to find comments in a token stream. Can only find comments for gettext calls forwards. Once the comment from line 4 is found, a comment for line 1 will not return a usable value. """ - def __init__( - self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str] - ) -> None: + def __init__( + self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str] + ) -> None: self.tokens = tokens self.comment_tags = comment_tags self.offset = 0 self.last_lineno = 0 - def find_backwards(self, offset: int) -> t.List[str]: + def find_backwards(self, offset: int) -> t.List[str]: try: - for _, token_type, token_value in reversed( - self.tokens[self.offset : offset] - ): - if token_type in ("comment", "linecomment"): + for _, token_type, token_value in reversed( + self.tokens[self.offset : offset] + ): + if token_type in ("comment", "linecomment"): try: prefix, comment = token_value.split(None, 1) except ValueError: @@ -769,31 +769,31 @@ class _CommentFinder: finally: self.offset = offset - def find_comments(self, lineno: int) -> t.List[str]: + def find_comments(self, lineno: int) -> t.List[str]: if not self.comment_tags or self.last_lineno > lineno: return [] - for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]): + for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]): if token_lineno > lineno: return self.find_backwards(self.offset + idx) return self.find_backwards(len(self.tokens)) -def babel_extract( - fileobj: t.BinaryIO, - keywords: t.Sequence[str], - comment_tags: t.Sequence[str], - options: t.Dict[str, t.Any], -) -> t.Iterator[ - t.Tuple[ - int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str] - ] -]: +def babel_extract( + fileobj: t.BinaryIO, + keywords: t.Sequence[str], + comment_tags: t.Sequence[str], + options: t.Dict[str, t.Any], +) -> t.Iterator[ + t.Tuple[ + int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str] + ] +]: """Babel extraction method for Jinja templates. .. versionchanged:: 2.3 Basic support for translation comments was added. If `comment_tags` is now set to a list of keywords for extraction, the extractor will - try to find the best preceding comment that begins with one of the + try to find the best preceding comment that begins with one of the keywords. For best results, make sure to not have more than one gettext call in one line of code and the matching comment in the same line or the line before. @@ -815,51 +815,51 @@ def babel_extract( :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. (comments will be empty currently) """ - extensions: t.Dict[t.Type[Extension], None] = {} - - for extension_name in options.get("extensions", "").split(","): - extension_name = extension_name.strip() - - if not extension_name: + extensions: t.Dict[t.Type[Extension], None] = {} + + for extension_name in options.get("extensions", "").split(","): + extension_name = extension_name.strip() + + if not extension_name: continue - - extensions[import_string(extension_name)] = None - + + extensions[import_string(extension_name)] = None + if InternationalizationExtension not in extensions: - extensions[InternationalizationExtension] = None + extensions[InternationalizationExtension] = None - def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool: - return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"} + def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool: + return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"} - silent = getbool(options, "silent", True) + silent = getbool(options, "silent", True) environment = Environment( - options.get("block_start_string", defaults.BLOCK_START_STRING), - options.get("block_end_string", defaults.BLOCK_END_STRING), - options.get("variable_start_string", defaults.VARIABLE_START_STRING), - options.get("variable_end_string", defaults.VARIABLE_END_STRING), - options.get("comment_start_string", defaults.COMMENT_START_STRING), - options.get("comment_end_string", defaults.COMMENT_END_STRING), - options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX, - options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX, - getbool(options, "trim_blocks", defaults.TRIM_BLOCKS), - getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS), - defaults.NEWLINE_SEQUENCE, - getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE), - tuple(extensions), + options.get("block_start_string", defaults.BLOCK_START_STRING), + options.get("block_end_string", defaults.BLOCK_END_STRING), + options.get("variable_start_string", defaults.VARIABLE_START_STRING), + options.get("variable_end_string", defaults.VARIABLE_END_STRING), + options.get("comment_start_string", defaults.COMMENT_START_STRING), + options.get("comment_end_string", defaults.COMMENT_END_STRING), + options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX, + options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX, + getbool(options, "trim_blocks", defaults.TRIM_BLOCKS), + getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS), + defaults.NEWLINE_SEQUENCE, + getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE), + tuple(extensions), cache_size=0, - auto_reload=False, + auto_reload=False, ) - if getbool(options, "trimmed"): - environment.policies["ext.i18n.trimmed"] = True - if getbool(options, "newstyle_gettext"): - environment.newstyle_gettext = True # type: ignore + if getbool(options, "trimmed"): + environment.policies["ext.i18n.trimmed"] = True + if getbool(options, "newstyle_gettext"): + environment.newstyle_gettext = True # type: ignore - source = fileobj.read().decode(options.get("encoding", "utf-8")) + source = fileobj.read().decode(options.get("encoding", "utf-8")) try: node = environment.parse(source) tokens = list(environment.lex(environment.preprocess(source))) - except TemplateSyntaxError: + except TemplateSyntaxError: if not silent: raise # skip templates with syntax errors @@ -876,4 +876,4 @@ do = ExprStmtExtension loopcontrols = LoopControlExtension with_ = WithExtension autoescape = AutoEscapeExtension -debug = DebugExtension +debug = DebugExtension diff --git a/contrib/python/Jinja2/py3/jinja2/filters.py b/contrib/python/Jinja2/py3/jinja2/filters.py index ffb98bf4e3..a719628230 100644 --- a/contrib/python/Jinja2/py3/jinja2/filters.py +++ b/contrib/python/Jinja2/py3/jinja2/filters.py @@ -1,130 +1,130 @@ -"""Built-in template filters used with the ``|`` operator.""" +"""Built-in template filters used with the ``|`` operator.""" import math import random -import re -import typing -import typing as t +import re +import typing +import typing as t import warnings -from collections import abc -from itertools import chain -from itertools import groupby - -from markupsafe import escape -from markupsafe import Markup -from markupsafe import soft_str - -from .async_utils import async_variant -from .async_utils import auto_aiter -from .async_utils import auto_await -from .async_utils import auto_to_list -from .exceptions import FilterArgumentError -from .runtime import Undefined -from .utils import htmlsafe_json_dumps -from .utils import pass_context -from .utils import pass_environment -from .utils import pass_eval_context -from .utils import pformat -from .utils import url_quote -from .utils import urlize - -if t.TYPE_CHECKING: - import typing_extensions as te - from .environment import Environment - from .nodes import EvalContext - from .runtime import Context - from .sandbox import SandboxedEnvironment # noqa: F401 - - class HasHTML(te.Protocol): - def __html__(self) -> str: - pass - - -F = t.TypeVar("F", bound=t.Callable[..., t.Any]) -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. +from collections import abc +from itertools import chain +from itertools import groupby + +from markupsafe import escape +from markupsafe import Markup +from markupsafe import soft_str + +from .async_utils import async_variant +from .async_utils import auto_aiter +from .async_utils import auto_await +from .async_utils import auto_to_list +from .exceptions import FilterArgumentError +from .runtime import Undefined +from .utils import htmlsafe_json_dumps +from .utils import pass_context +from .utils import pass_environment +from .utils import pass_eval_context +from .utils import pformat +from .utils import url_quote +from .utils import urlize + +if t.TYPE_CHECKING: + import typing_extensions as te + from .environment import Environment + from .nodes import EvalContext + from .runtime import Context + from .sandbox import SandboxedEnvironment # noqa: F401 + + class HasHTML(te.Protocol): + def __html__(self) -> str: + pass + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +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. - + 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( + "'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) + warnings.warn( + "'environmentfilter' is renamed to 'pass_environment', the old" + " name will be removed in Jinja 3.1.", + DeprecationWarning, + stacklevel=2, + ) + return pass_environment(f) -def ignore_case(value: V) -> V: +def ignore_case(value: V) -> V: """For use as a postprocessor for :func:`make_attrgetter`. Converts strings to lowercase and returns other types as-is.""" - if isinstance(value, str): - return t.cast(V, value.lower()) - - return value - - -def make_attrgetter( - environment: "Environment", - attribute: t.Optional[t.Union[str, int]], - postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, - default: t.Optional[t.Any] = None, -) -> t.Callable[[t.Any], t.Any]: + if isinstance(value, str): + return t.cast(V, value.lower()) + + return value + + +def make_attrgetter( + environment: "Environment", + attribute: t.Optional[t.Union[str, int]], + postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, + default: t.Optional[t.Any] = None, +) -> t.Callable[[t.Any], t.Any]: """Returns a callable that looks up the given attribute from a passed object with the rules of the environment. Dots are allowed to access attributes of attributes. Integer parts in paths are looked up as integers. """ - parts = _prepare_attribute_parts(attribute) + parts = _prepare_attribute_parts(attribute) - def attrgetter(item: t.Any) -> t.Any: - for part in parts: + def attrgetter(item: t.Any) -> t.Any: + for part in parts: item = environment.getitem(item, part) - if default is not None and isinstance(item, Undefined): - item = default - + if default is not None and isinstance(item, Undefined): + item = default + if postprocess is not None: item = postprocess(item) @@ -133,102 +133,102 @@ def make_attrgetter( return attrgetter -def make_multi_attrgetter( - environment: "Environment", - attribute: t.Optional[t.Union[str, int]], - postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, -) -> t.Callable[[t.Any], t.List[t.Any]]: - """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. - """ - if isinstance(attribute, str): - split: t.Sequence[t.Union[str, int, None]] = attribute.split(",") - else: - split = [attribute] - - parts = [_prepare_attribute_parts(item) for item in split] - - def attrgetter(item: t.Any) -> t.List[t.Any]: - items = [None] * len(parts) - - for i, attribute_part in enumerate(parts): - 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: t.Optional[t.Union[str, int]] -) -> t.List[t.Union[str, int]]: - if attr is None: - return [] - - if isinstance(attr, str): - return [int(x) if x.isdigit() else x for x in attr.split(".")] - - return [attr] - - -def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup: +def make_multi_attrgetter( + environment: "Environment", + attribute: t.Optional[t.Union[str, int]], + postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, +) -> t.Callable[[t.Any], t.List[t.Any]]: + """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. + """ + if isinstance(attribute, str): + split: t.Sequence[t.Union[str, int, None]] = attribute.split(",") + else: + split = [attribute] + + parts = [_prepare_attribute_parts(item) for item in split] + + def attrgetter(item: t.Any) -> t.List[t.Any]: + items = [None] * len(parts) + + for i, attribute_part in enumerate(parts): + 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: t.Optional[t.Union[str, int]] +) -> t.List[t.Union[str, int]]: + if attr is None: + return [] + + if isinstance(attr, str): + return [int(x) if x.isdigit() else x for x in attr.split(".")] + + return [attr] + + +def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup: """Enforce HTML escaping. This will probably double escape variables.""" - if hasattr(value, "__html__"): - value = t.cast("HasHTML", value).__html__() - - return escape(str(value)) - - -def do_urlencode( - value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]] -) -> str: - """Quote data for use in a URL path or query using UTF-8. - - Basic wrapper around :func:`urllib.parse.quote` when given a - string, or :func:`urllib.parse.urlencode` for a dict or iterable. - - :param value: Data to quote. A string will be quoted directly. A - dict or iterable of ``(key, value)`` pairs will be joined as a - query string. - - When given a string, "/" is not quoted. HTTP servers treat "/" and - "%2F" equivalently in paths. If you need quoted slashes, use the - ``|replace("/", "%2F")`` filter. - + if hasattr(value, "__html__"): + value = t.cast("HasHTML", value).__html__() + + return escape(str(value)) + + +def do_urlencode( + value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]] +) -> str: + """Quote data for use in a URL path or query using UTF-8. + + Basic wrapper around :func:`urllib.parse.quote` when given a + string, or :func:`urllib.parse.urlencode` for a dict or iterable. + + :param value: Data to quote. A string will be quoted directly. A + dict or iterable of ``(key, value)`` pairs will be joined as a + query string. + + When given a string, "/" is not quoted. HTTP servers treat "/" and + "%2F" equivalently in paths. If you need quoted slashes, use the + ``|replace("/", "%2F")`` filter. + .. versionadded:: 2.7 """ - if isinstance(value, str) or not isinstance(value, abc.Iterable): - return url_quote(value) - - if isinstance(value, dict): - items: t.Iterable[t.Tuple[str, t.Any]] = value.items() - else: - items = value # type: ignore - - return "&".join( - f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items - ) - - -@pass_eval_context -def do_replace( - eval_ctx: "EvalContext", s: str, old: str, new: str, count: t.Optional[int] = None -) -> str: + if isinstance(value, str) or not isinstance(value, abc.Iterable): + return url_quote(value) + + if isinstance(value, dict): + items: t.Iterable[t.Tuple[str, t.Any]] = value.items() + else: + items = value # type: ignore + + return "&".join( + f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items + ) + + +@pass_eval_context +def do_replace( + eval_ctx: "EvalContext", s: str, old: str, new: str, count: t.Optional[int] = None +) -> str: """Return a copy of the value with all occurrences of a substring replaced with a new one. The first argument is the substring that should be replaced, the second is the replacement string. @@ -245,36 +245,36 @@ def do_replace( """ if count is None: count = -1 - + if not eval_ctx.autoescape: - return str(s).replace(str(old), str(new), count) - - if ( - hasattr(old, "__html__") - or hasattr(new, "__html__") - and not hasattr(s, "__html__") - ): + return str(s).replace(str(old), str(new), count) + + if ( + hasattr(old, "__html__") + or hasattr(new, "__html__") + and not hasattr(s, "__html__") + ): s = escape(s) else: - s = soft_str(s) + s = soft_str(s) - return s.replace(soft_str(old), soft_str(new), count) + return s.replace(soft_str(old), soft_str(new), count) - -def do_upper(s: str) -> str: + +def do_upper(s: str) -> str: """Convert a value to uppercase.""" - return soft_str(s).upper() + return soft_str(s).upper() -def do_lower(s: str) -> str: +def do_lower(s: str) -> str: """Convert a value to lowercase.""" - return soft_str(s).lower() + return soft_str(s).lower() -@pass_eval_context -def do_xmlattr( - eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True -) -> str: +@pass_eval_context +def do_xmlattr( + eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True +) -> str: """Create an SGML/XML attribute string based on the items in a dict. All values that are neither `none` nor `undefined` are automatically escaped: @@ -297,75 +297,75 @@ def do_xmlattr( As you can see it automatically prepends a space in front of the item if the filter returned something unless the second parameter is false. """ - rv = " ".join( - f'{escape(key)}="{escape(value)}"' - for key, value in d.items() + rv = " ".join( + f'{escape(key)}="{escape(value)}"' + for key, value in d.items() if value is not None and not isinstance(value, Undefined) ) - + if autospace and rv: - rv = " " + rv - - if eval_ctx.autoescape: + rv = " " + rv + + if eval_ctx.autoescape: rv = Markup(rv) - + return rv -def do_capitalize(s: str) -> str: +def do_capitalize(s: str) -> str: """Capitalize a value. The first character will be uppercase, all others lowercase. """ - return soft_str(s).capitalize() - + return soft_str(s).capitalize() -_word_beginning_split_re = re.compile(r"([-\s({\[<]+)") - -def do_title(s: str) -> str: +_word_beginning_split_re = re.compile(r"([-\s({\[<]+)") + + +def do_title(s: str) -> str: """Return a titlecased version of the value. I.e. words will start with uppercase letters, all remaining characters are lowercase. """ - return "".join( - [ - item[0].upper() + item[1:].lower() - for item in _word_beginning_split_re.split(soft_str(s)) - if item - ] - ) - - -def do_dictsort( - value: t.Mapping[K, V], - case_sensitive: bool = False, - by: 'te.Literal["key", "value"]' = "key", - reverse: bool = False, -) -> t.List[t.Tuple[K, V]]: - """Sort a dict and yield (key, value) pairs. Python dicts may not - be in the order you want to display them in, so sort them first. + return "".join( + [ + item[0].upper() + item[1:].lower() + for item in _word_beginning_split_re.split(soft_str(s)) + if item + ] + ) + + +def do_dictsort( + value: t.Mapping[K, V], + case_sensitive: bool = False, + by: 'te.Literal["key", "value"]' = "key", + reverse: bool = False, +) -> t.List[t.Tuple[K, V]]: + """Sort a dict and yield (key, value) pairs. Python dicts may not + be in the order you want to display them in, so sort them first. .. sourcecode:: jinja - {% for key, value in mydict|dictsort %} + {% for key, value in mydict|dictsort %} sort the dict by key, case insensitive - {% for key, value in mydict|dictsort(reverse=true) %} + {% for key, value in mydict|dictsort(reverse=true) %} sort the dict by key, case insensitive, reverse order - {% for key, value in mydict|dictsort(true) %} + {% for key, value in mydict|dictsort(true) %} sort the dict by key, case sensitive - {% for key, value in mydict|dictsort(false, 'value') %} + {% for key, value in mydict|dictsort(false, 'value') %} sort the dict by value, case insensitive """ - if by == "key": + if by == "key": pos = 0 - elif by == "value": + elif by == "value": pos = 1 else: - raise FilterArgumentError('You can only sort by either "key" or "value"') + raise FilterArgumentError('You can only sort by either "key" or "value"') - def sort_func(item: t.Tuple[t.Any, t.Any]) -> t.Any: + def sort_func(item: t.Tuple[t.Any, t.Any]) -> t.Any: value = item[pos] if not case_sensitive: @@ -376,74 +376,74 @@ def do_dictsort( return sorted(value.items(), key=sort_func, reverse=reverse) -@pass_environment -def do_sort( - environment: "Environment", - value: "t.Iterable[V]", - reverse: bool = False, - case_sensitive: bool = False, - attribute: t.Optional[t.Union[str, int]] = None, -) -> "t.List[V]": - """Sort an iterable using Python's :func:`sorted`. - - .. sourcecode:: jinja - - {% for city in cities|sort %} - ... - {% endfor %} - - :param reverse: Sort descending instead of ascending. - :param case_sensitive: When sorting strings, sort upper and lower - case separately. - :param attribute: When sorting objects or dicts, an attribute or - key to sort by. Can use dot notation like ``"address.city"``. - Can be a list of attributes like ``"age,name"``. - - The sort is stable, it does not change the relative order of - elements that compare equal. This makes it is possible to chain - sorts on different attributes and ordering. - +@pass_environment +def do_sort( + environment: "Environment", + value: "t.Iterable[V]", + reverse: bool = False, + case_sensitive: bool = False, + attribute: t.Optional[t.Union[str, int]] = None, +) -> "t.List[V]": + """Sort an iterable using Python's :func:`sorted`. + + .. sourcecode:: jinja + + {% for city in cities|sort %} + ... + {% endfor %} + + :param reverse: Sort descending instead of ascending. + :param case_sensitive: When sorting strings, sort upper and lower + case separately. + :param attribute: When sorting objects or dicts, an attribute or + key to sort by. Can use dot notation like ``"address.city"``. + Can be a list of attributes like ``"age,name"``. + + The sort is stable, it does not change the relative order of + elements that compare equal. This makes it is possible to chain + sorts on different attributes and ordering. + .. sourcecode:: jinja - {% for user in users|sort(attribute="name") - |sort(reverse=true, attribute="age") %} + {% for user in users|sort(attribute="name") + |sort(reverse=true, attribute="age") %} ... {% endfor %} - As a shortcut to chaining when the direction is the same for all - attributes, pass a comma separate list of attributes. + As a shortcut to chaining when the direction is the same for all + attributes, pass a comma separate list of attributes. .. sourcecode:: jinja - {% for user users|sort(attribute="age,name") %} + {% for user users|sort(attribute="age,name") %} ... {% endfor %} - .. versionchanged:: 2.11.0 - The ``attribute`` parameter can be a comma separated list of - attributes, e.g. ``"age,name"``. - + .. versionchanged:: 2.11.0 + The ``attribute`` parameter can be a comma separated list of + attributes, e.g. ``"age,name"``. + .. versionchanged:: 2.6 - The ``attribute`` parameter was added. + The ``attribute`` parameter was added. """ - key_func = make_multi_attrgetter( - environment, attribute, postprocess=ignore_case if not case_sensitive else None + key_func = make_multi_attrgetter( + environment, attribute, postprocess=ignore_case if not case_sensitive else None ) return sorted(value, key=key_func, reverse=reverse) -@pass_environment -def do_unique( - environment: "Environment", - value: "t.Iterable[V]", - case_sensitive: bool = False, - attribute: t.Optional[t.Union[str, int]] = None, -) -> "t.Iterator[V]": - """Returns a list of unique items from the given iterable. +@pass_environment +def do_unique( + environment: "Environment", + value: "t.Iterable[V]", + case_sensitive: bool = False, + attribute: t.Optional[t.Union[str, int]] = None, +) -> "t.Iterator[V]": + """Returns a list of unique items from the given iterable. .. sourcecode:: jinja - {{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }} + {{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }} -> ['foo', 'bar', 'foobar'] The unique items are yielded in the same order as their first occurrence in @@ -453,7 +453,7 @@ def do_unique( :param attribute: Filter objects with unique values for this attribute. """ getter = make_attrgetter( - environment, attribute, postprocess=ignore_case if not case_sensitive else None + environment, attribute, postprocess=ignore_case if not case_sensitive else None ) seen = set() @@ -465,33 +465,33 @@ def do_unique( yield item -def _min_or_max( - environment: "Environment", - value: "t.Iterable[V]", - func: "t.Callable[..., V]", - case_sensitive: bool, - attribute: t.Optional[t.Union[str, int]], -) -> "t.Union[V, Undefined]": +def _min_or_max( + environment: "Environment", + value: "t.Iterable[V]", + func: "t.Callable[..., V]", + case_sensitive: bool, + attribute: t.Optional[t.Union[str, int]], +) -> "t.Union[V, Undefined]": it = iter(value) try: first = next(it) except StopIteration: - return environment.undefined("No aggregated item, sequence was empty.") + return environment.undefined("No aggregated item, sequence was empty.") key_func = make_attrgetter( - environment, attribute, postprocess=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) -@pass_environment -def do_min( - environment: "Environment", - value: "t.Iterable[V]", - case_sensitive: bool = False, - attribute: t.Optional[t.Union[str, int]] = None, -) -> "t.Union[V, Undefined]": +@pass_environment +def do_min( + environment: "Environment", + value: "t.Iterable[V]", + case_sensitive: bool = False, + attribute: t.Optional[t.Union[str, int]] = None, +) -> "t.Union[V, Undefined]": """Return the smallest item from the sequence. .. sourcecode:: jinja @@ -500,18 +500,18 @@ def do_min( -> 1 :param case_sensitive: Treat upper and lower case strings as distinct. - :param attribute: Get the object with the min value of this attribute. + :param attribute: Get the object with the min value of this attribute. """ return _min_or_max(environment, value, min, case_sensitive, attribute) -@pass_environment -def do_max( - environment: "Environment", - value: "t.Iterable[V]", - case_sensitive: bool = False, - attribute: t.Optional[t.Union[str, int]] = None, -) -> "t.Union[V, Undefined]": +@pass_environment +def do_max( + environment: "Environment", + value: "t.Iterable[V]", + case_sensitive: bool = False, + attribute: t.Optional[t.Union[str, int]] = None, +) -> "t.Union[V, Undefined]": """Return the largest item from the sequence. .. sourcecode:: jinja @@ -525,11 +525,11 @@ def do_max( return _min_or_max(environment, value, max, case_sensitive, attribute) -def do_default( - value: V, - default_value: V = "", # type: ignore - boolean: bool = False, -) -> V: +def do_default( + value: V, + default_value: V = "", # type: ignore + boolean: bool = False, +) -> V: """If the value is undefined it will return the passed default value, otherwise the value of the variable: @@ -545,26 +545,26 @@ def do_default( .. 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`. + + .. 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 - + return value -@pass_eval_context -def sync_do_join( - eval_ctx: "EvalContext", - value: t.Iterable, - d: str = "", - attribute: t.Optional[t.Union[str, int]] = None, -) -> str: +@pass_eval_context +def sync_do_join( + eval_ctx: "EvalContext", + value: t.Iterable, + d: str = "", + attribute: t.Optional[t.Union[str, int]] = None, +) -> str: """Return a string which is the concatenation of the strings in the sequence. The separator between elements is an empty string per default, you can define it with the optional parameter: @@ -587,262 +587,262 @@ def sync_do_join( The `attribute` parameter was added. """ if attribute is not None: - value = map(make_attrgetter(eval_ctx.environment, attribute), value) + value = map(make_attrgetter(eval_ctx.environment, attribute), value) - # no automatic escaping? joining is a lot easier then + # no automatic escaping? joining is a lot easier then if not eval_ctx.autoescape: - return str(d).join(map(str, value)) + return str(d).join(map(str, value)) # if the delimiter doesn't have an html representation we check # if any of the items has. If yes we do a coercion to Markup - if not hasattr(d, "__html__"): + if not hasattr(d, "__html__"): value = list(value) do_escape = False - + for idx, item in enumerate(value): - if hasattr(item, "__html__"): + if hasattr(item, "__html__"): do_escape = True else: - value[idx] = str(item) - + value[idx] = str(item) + if do_escape: d = escape(d) else: - d = str(d) - + d = str(d) + return d.join(value) # no html involved, to normal joining - return soft_str(d).join(map(soft_str, value)) - - -@async_variant(sync_do_join) # type: ignore -async def do_join( - eval_ctx: "EvalContext", - value: t.Union[t.AsyncIterable, t.Iterable], - d: str = "", - attribute: t.Optional[t.Union[str, int]] = None, -) -> str: - return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute) - - -def do_center(value: str, width: int = 80) -> str: + return soft_str(d).join(map(soft_str, value)) + + +@async_variant(sync_do_join) # type: ignore +async def do_join( + eval_ctx: "EvalContext", + value: t.Union[t.AsyncIterable, t.Iterable], + d: str = "", + attribute: t.Optional[t.Union[str, int]] = None, +) -> str: + return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute) + + +def do_center(value: str, width: int = 80) -> str: """Centers the value in a field of a given width.""" - return soft_str(value).center(width) + return soft_str(value).center(width) -@pass_environment -def sync_do_first( - environment: "Environment", seq: "t.Iterable[V]" -) -> "t.Union[V, Undefined]": +@pass_environment +def sync_do_first( + environment: "Environment", seq: "t.Iterable[V]" +) -> "t.Union[V, Undefined]": """Return the first item of a sequence.""" try: return next(iter(seq)) except StopIteration: - return environment.undefined("No first item, sequence was empty.") - - -@async_variant(sync_do_first) # type: ignore -async def do_first( - environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]" -) -> "t.Union[V, Undefined]": - try: - return await auto_aiter(seq).__anext__() - except StopAsyncIteration: - return environment.undefined("No first item, sequence was empty.") - - -@pass_environment -def do_last( - environment: "Environment", seq: "t.Reversible[V]" -) -> "t.Union[V, Undefined]": - """Return the last item of a sequence. - - Note: Does not work with generators. You may want to explicitly - convert it to a list: - - .. sourcecode:: jinja - - {{ data | selectattr('name', '==', 'Jinja') | list | last }} - """ + return environment.undefined("No first item, sequence was empty.") + + +@async_variant(sync_do_first) # type: ignore +async def do_first( + environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]" +) -> "t.Union[V, Undefined]": + try: + return await auto_aiter(seq).__anext__() + except StopAsyncIteration: + return environment.undefined("No first item, sequence was empty.") + + +@pass_environment +def do_last( + environment: "Environment", seq: "t.Reversible[V]" +) -> "t.Union[V, Undefined]": + """Return the last item of a sequence. + + Note: Does not work with generators. You may want to explicitly + convert it to a list: + + .. sourcecode:: jinja + + {{ data | selectattr('name', '==', 'Jinja') | list | last }} + """ try: return next(iter(reversed(seq))) except StopIteration: - return environment.undefined("No last item, sequence was empty.") + return environment.undefined("No last item, sequence was empty.") -# No async do_last, it may not be safe in async mode. - - -@pass_context -def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]": +# No async do_last, it may not be safe in async mode. + + +@pass_context +def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]": """Return a random item from the sequence.""" try: return random.choice(seq) except IndexError: - return context.environment.undefined("No random item, sequence was empty.") + return context.environment.undefined("No random item, sequence was empty.") -def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str: +def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str: """Format the value like a 'human-readable' file size (i.e. 13 kB, 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, Giga, etc.), if the second parameter is set to `True` the binary prefixes are used (Mebi, Gibi). """ bytes = float(value) - base = 1024 if binary else 1000 + base = 1024 if binary else 1000 prefixes = [ - ("KiB" if binary else "kB"), - ("MiB" if binary else "MB"), - ("GiB" if binary else "GB"), - ("TiB" if binary else "TB"), - ("PiB" if binary else "PB"), - ("EiB" if binary else "EB"), - ("ZiB" if binary else "ZB"), - ("YiB" if binary else "YB"), + ("KiB" if binary else "kB"), + ("MiB" if binary else "MB"), + ("GiB" if binary else "GB"), + ("TiB" if binary else "TB"), + ("PiB" if binary else "PB"), + ("EiB" if binary else "EB"), + ("ZiB" if binary else "ZB"), + ("YiB" if binary else "YB"), ] - + if bytes == 1: - return "1 Byte" + return "1 Byte" elif bytes < base: - return f"{int(bytes)} Bytes" + return f"{int(bytes)} Bytes" else: for i, prefix in enumerate(prefixes): unit = base ** (i + 2) - + if bytes < unit: - return f"{base * bytes / unit:.1f} {prefix}" - - return f"{base * bytes / unit:.1f} {prefix}" - - -def do_pprint(value: t.Any) -> str: - """Pretty print a variable. Useful for debugging.""" - return pformat(value) - - -_uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$") - - -@pass_eval_context -def do_urlize( - eval_ctx: "EvalContext", - value: str, - trim_url_limit: t.Optional[int] = None, - nofollow: bool = False, - target: t.Optional[str] = None, - rel: t.Optional[str] = None, - extra_schemes: t.Optional[t.Iterable[str]] = None, -) -> str: - """Convert URLs in text into clickable links. - - This may not recognize links in some situations. Usually, a more - comprehensive formatter, such as a Markdown library, is a better - choice. - - Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email - addresses. Links with trailing punctuation (periods, commas, closing - parentheses) and leading punctuation (opening parentheses) are - recognized excluding the punctuation. Email addresses that include - header fields are not recognized (for example, - ``mailto:address@example.com?cc=copy@example.com``). - - :param value: Original text containing URLs to link. - :param trim_url_limit: Shorten displayed URL values to this length. - :param nofollow: Add the ``rel=nofollow`` attribute to links. - :param target: Add the ``target`` attribute to links. - :param rel: Add the ``rel`` attribute to links. - :param extra_schemes: Recognize URLs that start with these schemes - in addition to the default behavior. Defaults to - ``env.policies["urlize.extra_schemes"]``, which defaults to no - extra schemes. - - .. versionchanged:: 3.0 - The ``extra_schemes`` parameter was added. - - .. versionchanged:: 3.0 - Generate ``https://`` links for URLs without a scheme. - - .. versionchanged:: 3.0 - The parsing rules were updated. Recognize email addresses with - or without the ``mailto:`` scheme. Validate IP addresses. Ignore - parentheses and brackets in more cases. - - .. versionchanged:: 2.8 - The ``target`` parameter was added. + return f"{base * bytes / unit:.1f} {prefix}" + + return f"{base * bytes / unit:.1f} {prefix}" + + +def do_pprint(value: t.Any) -> str: + """Pretty print a variable. Useful for debugging.""" + return pformat(value) + + +_uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$") + + +@pass_eval_context +def do_urlize( + eval_ctx: "EvalContext", + value: str, + trim_url_limit: t.Optional[int] = None, + nofollow: bool = False, + target: t.Optional[str] = None, + rel: t.Optional[str] = None, + extra_schemes: t.Optional[t.Iterable[str]] = None, +) -> str: + """Convert URLs in text into clickable links. + + This may not recognize links in some situations. Usually, a more + comprehensive formatter, such as a Markdown library, is a better + choice. + + Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email + addresses. Links with trailing punctuation (periods, commas, closing + parentheses) and leading punctuation (opening parentheses) are + recognized excluding the punctuation. Email addresses that include + header fields are not recognized (for example, + ``mailto:address@example.com?cc=copy@example.com``). + + :param value: Original text containing URLs to link. + :param trim_url_limit: Shorten displayed URL values to this length. + :param nofollow: Add the ``rel=nofollow`` attribute to links. + :param target: Add the ``target`` attribute to links. + :param rel: Add the ``rel`` attribute to links. + :param extra_schemes: Recognize URLs that start with these schemes + in addition to the default behavior. Defaults to + ``env.policies["urlize.extra_schemes"]``, which defaults to no + extra schemes. + + .. versionchanged:: 3.0 + The ``extra_schemes`` parameter was added. + + .. versionchanged:: 3.0 + Generate ``https://`` links for URLs without a scheme. + + .. versionchanged:: 3.0 + The parsing rules were updated. Recognize email addresses with + or without the ``mailto:`` scheme. Validate IP addresses. Ignore + parentheses and brackets in more cases. + + .. versionchanged:: 2.8 + The ``target`` parameter was added. """ policies = eval_ctx.environment.policies - rel_parts = set((rel or "").split()) - + rel_parts = set((rel or "").split()) + if nofollow: - rel_parts.add("nofollow") - - rel_parts.update((policies["urlize.rel"] or "").split()) - rel = " ".join(sorted(rel_parts)) or None - + rel_parts.add("nofollow") + + rel_parts.update((policies["urlize.rel"] or "").split()) + rel = " ".join(sorted(rel_parts)) or None + if target is None: - target = policies["urlize.target"] - - if extra_schemes is None: - extra_schemes = policies["urlize.extra_schemes"] or () - - for scheme in extra_schemes: - if _uri_scheme_re.fullmatch(scheme) is None: - raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.") - - rv = urlize( - value, - trim_url_limit=trim_url_limit, - rel=rel, - target=target, - extra_schemes=extra_schemes, - ) - + target = policies["urlize.target"] + + if extra_schemes is None: + extra_schemes = policies["urlize.extra_schemes"] or () + + for scheme in extra_schemes: + if _uri_scheme_re.fullmatch(scheme) is None: + raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.") + + rv = urlize( + value, + trim_url_limit=trim_url_limit, + rel=rel, + target=target, + extra_schemes=extra_schemes, + ) + if eval_ctx.autoescape: rv = Markup(rv) - + return rv -def do_indent( - s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False -) -> str: +def do_indent( + s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False +) -> str: """Return a copy of the string with each line indented by 4 spaces. The first line and blank lines are not indented by default. - :param width: Number of spaces, or a string, to indent by. + :param width: Number of spaces, or a string, to indent by. :param first: Don't skip indenting the first line. :param blank: Don't skip indenting empty lines. - .. versionchanged:: 3.0 - ``width`` can be a string. - + .. versionchanged:: 3.0 + ``width`` can be a string. + .. versionchanged:: 2.10 Blank lines are not indented by default. Rename the ``indentfirst`` argument to ``first``. """ - if isinstance(width, str): - indention = width - else: - indention = " " * width - - newline = "\n" - - if isinstance(s, Markup): - indention = Markup(indention) - newline = Markup(newline) - - s += newline # this quirk is necessary for splitlines method - + if isinstance(width, str): + indention = width + else: + indention = " " * width + + newline = "\n" + + if isinstance(s, Markup): + indention = Markup(indention) + newline = Markup(newline) + + s += newline # this quirk is necessary for splitlines method + if blank: - rv = (newline + indention).join(s.splitlines()) + rv = (newline + indention).join(s.splitlines()) else: lines = s.splitlines() rv = lines.pop(0) if lines: - rv += newline + newline.join( + rv += newline + newline.join( indention + line if line else line for line in lines ) @@ -852,15 +852,15 @@ def do_indent( return rv -@pass_environment -def do_truncate( - env: "Environment", - s: str, - length: int = 255, - killwords: bool = False, - end: str = "...", - leeway: t.Optional[int] = None, -) -> str: +@pass_environment +def do_truncate( + env: "Environment", + s: str, + length: int = 255, + killwords: bool = False, + end: str = "...", + leeway: t.Optional[int] = None, +) -> str: """Return a truncated copy of the string. The length is specified with the first parameter which defaults to ``255``. If the second parameter is ``true`` the filter will cut the text at length. Otherwise @@ -881,90 +881,90 @@ def do_truncate( {{ "foo bar baz qux"|truncate(11, False, '...', 0) }} -> "foo bar..." - The default leeway on newer Jinja versions is 5 and was 0 before but + The default leeway on newer Jinja versions is 5 and was 0 before but can be reconfigured globally. """ if leeway is None: - leeway = env.policies["truncate.leeway"] - - assert length >= len(end), f"expected length >= {len(end)}, got {length}" - assert leeway >= 0, f"expected leeway >= 0, got {leeway}" - + leeway = env.policies["truncate.leeway"] + + assert length >= len(end), f"expected length >= {len(end)}, got {length}" + assert leeway >= 0, f"expected leeway >= 0, got {leeway}" + if len(s) <= length + leeway: return s - + if killwords: - return s[: length - len(end)] + end - - result = s[: length - len(end)].rsplit(" ", 1)[0] + return s[: length - len(end)] + end + + result = s[: length - len(end)].rsplit(" ", 1)[0] return result + end -@pass_environment -def do_wordwrap( - environment: "Environment", - s: str, - width: int = 79, - break_long_words: bool = True, - wrapstring: t.Optional[str] = None, - break_on_hyphens: bool = True, -) -> str: - """Wrap a string to the given width. Existing newlines are treated - as paragraphs to be wrapped separately. - - :param s: Original text to wrap. - :param width: Maximum length of wrapped lines. - :param break_long_words: If a word is longer than ``width``, break - it across lines. - :param break_on_hyphens: If a word contains hyphens, it may be split - across lines. - :param wrapstring: String to join each wrapped line. Defaults to - :attr:`Environment.newline_sequence`. - - .. versionchanged:: 2.11 - Existing newlines are treated as paragraphs wrapped separately. - - .. versionchanged:: 2.11 - Added the ``break_on_hyphens`` parameter. - - .. versionchanged:: 2.7 - Added the ``wrapstring`` parameter. +@pass_environment +def do_wordwrap( + environment: "Environment", + s: str, + width: int = 79, + break_long_words: bool = True, + wrapstring: t.Optional[str] = None, + break_on_hyphens: bool = True, +) -> str: + """Wrap a string to the given width. Existing newlines are treated + as paragraphs to be wrapped separately. + + :param s: Original text to wrap. + :param width: Maximum length of wrapped lines. + :param break_long_words: If a word is longer than ``width``, break + it across lines. + :param break_on_hyphens: If a word contains hyphens, it may be split + across lines. + :param wrapstring: String to join each wrapped line. Defaults to + :attr:`Environment.newline_sequence`. + + .. versionchanged:: 2.11 + Existing newlines are treated as paragraphs wrapped separately. + + .. versionchanged:: 2.11 + Added the ``break_on_hyphens`` parameter. + + .. versionchanged:: 2.7 + Added the ``wrapstring`` parameter. """ - import textwrap - - if wrapstring is None: + import textwrap + + if wrapstring is None: wrapstring = environment.newline_sequence - # textwrap.wrap doesn't consider existing newlines when wrapping. - # If the string has a newline before width, wrap will still insert - # a newline at width, resulting in a short line. Instead, split and - # wrap each paragraph individually. - return wrapstring.join( - [ - wrapstring.join( - textwrap.wrap( - line, - width=width, - expand_tabs=False, - replace_whitespace=False, - break_long_words=break_long_words, - break_on_hyphens=break_on_hyphens, - ) - ) - for line in s.splitlines() - ] - ) - - -_word_re = re.compile(r"\w+") - - -def do_wordcount(s: str) -> int: + # textwrap.wrap doesn't consider existing newlines when wrapping. + # If the string has a newline before width, wrap will still insert + # a newline at width, resulting in a short line. Instead, split and + # wrap each paragraph individually. + return wrapstring.join( + [ + wrapstring.join( + textwrap.wrap( + line, + width=width, + expand_tabs=False, + replace_whitespace=False, + break_long_words=break_long_words, + break_on_hyphens=break_on_hyphens, + ) + ) + for line in s.splitlines() + ] + ) + + +_word_re = re.compile(r"\w+") + + +def do_wordcount(s: str) -> int: """Count the words in that string.""" - return len(_word_re.findall(soft_str(s))) + return len(_word_re.findall(soft_str(s))) -def do_int(value: t.Any, default: int = 0, base: int = 10) -> int: +def do_int(value: t.Any, default: int = 0, base: int = 10) -> int: """Convert the value into an integer. If the conversion doesn't work it will return ``0``. You can override this default using the first parameter. You @@ -974,9 +974,9 @@ def do_int(value: t.Any, default: int = 0, base: int = 10) -> int: The base is ignored for decimal numbers and non-string values. """ try: - if isinstance(value, str): + if isinstance(value, str): return int(value, base) - + return int(value) except (TypeError, ValueError): # this quirk is necessary so that "42.23"|int gives 42. @@ -986,7 +986,7 @@ def do_int(value: t.Any, default: int = 0, base: int = 10) -> int: return default -def do_float(value: t.Any, default: float = 0.0) -> float: +def do_float(value: t.Any, default: float = 0.0) -> float: """Convert the value into a floating point number. If the conversion doesn't work it will return ``0.0``. You can override this default using the first parameter. @@ -997,57 +997,57 @@ def do_float(value: t.Any, default: float = 0.0) -> float: return default -def do_format(value: str, *args: t.Any, **kwargs: t.Any) -> str: - """Apply the given values to a `printf-style`_ format string, like - ``string % values``. +def do_format(value: str, *args: t.Any, **kwargs: t.Any) -> str: + """Apply the given values to a `printf-style`_ format string, like + ``string % values``. .. sourcecode:: jinja - {{ "%s, %s!"|format(greeting, name) }} - Hello, World! - - In most cases it should be more convenient and efficient to use the - ``%`` operator or :meth:`str.format`. - - .. code-block:: text - - {{ "%s, %s!" % (greeting, name) }} - {{ "{}, {}!".format(greeting, name) }} - - .. _printf-style: https://docs.python.org/library/stdtypes.html - #printf-style-string-formatting + {{ "%s, %s!"|format(greeting, name) }} + Hello, World! + + In most cases it should be more convenient and efficient to use the + ``%`` operator or :meth:`str.format`. + + .. code-block:: text + + {{ "%s, %s!" % (greeting, name) }} + {{ "{}, {}!".format(greeting, name) }} + + .. _printf-style: https://docs.python.org/library/stdtypes.html + #printf-style-string-formatting """ if args and kwargs: - raise FilterArgumentError( - "can't handle positional and keyword arguments at the same time" - ) - - return soft_str(value) % (kwargs or args) + raise FilterArgumentError( + "can't handle positional and keyword arguments at the same time" + ) + return soft_str(value) % (kwargs or args) -def do_trim(value: str, chars: t.Optional[str] = None) -> str: - """Strip leading and trailing characters, by default whitespace.""" - return soft_str(value).strip(chars) + +def do_trim(value: str, chars: t.Optional[str] = None) -> str: + """Strip leading and trailing characters, by default whitespace.""" + return soft_str(value).strip(chars) -def do_striptags(value: "t.Union[str, HasHTML]") -> str: - """Strip SGML/XML tags and replace adjacent whitespace by one space.""" - if hasattr(value, "__html__"): - value = t.cast("HasHTML", value).__html__() +def do_striptags(value: "t.Union[str, HasHTML]") -> str: + """Strip SGML/XML tags and replace adjacent whitespace by one space.""" + if hasattr(value, "__html__"): + value = t.cast("HasHTML", value).__html__() - return Markup(str(value)).striptags() + return Markup(str(value)).striptags() - -def sync_do_slice( - value: "t.Collection[V]", slices: int, fill_with: "t.Optional[V]" = None -) -> "t.Iterator[t.List[V]]": + +def sync_do_slice( + value: "t.Collection[V]", slices: int, fill_with: "t.Optional[V]" = None +) -> "t.Iterator[t.List[V]]": """Slice an iterator and return a list of lists containing those items. Useful if you want to create a div containing three ul tags that represent columns: .. sourcecode:: html+jinja - <div class="columnwrapper"> + <div class="columnwrapper"> {%- for column in items|slice(3) %} <ul class="column-{{ loop.index }}"> {%- for item in column %} @@ -1065,34 +1065,34 @@ def sync_do_slice( items_per_slice = length // slices slices_with_extra = length % slices offset = 0 - + for slice_number in range(slices): start = offset + slice_number * items_per_slice - + if slice_number < slices_with_extra: offset += 1 - + end = offset + (slice_number + 1) * items_per_slice tmp = seq[start:end] - + if fill_with is not None and slice_number >= slices_with_extra: tmp.append(fill_with) - + yield tmp -@async_variant(sync_do_slice) # type: ignore -async def do_slice( - value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - slices: int, - fill_with: t.Optional[t.Any] = None, -) -> "t.Iterator[t.List[V]]": - return sync_do_slice(await auto_to_list(value), slices, fill_with) - - -def do_batch( - value: "t.Iterable[V]", linecount: int, fill_with: "t.Optional[V]" = None -) -> "t.Iterator[t.List[V]]": +@async_variant(sync_do_slice) # type: ignore +async def do_slice( + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", + slices: int, + fill_with: t.Optional[t.Any] = None, +) -> "t.Iterator[t.List[V]]": + return sync_do_slice(await auto_to_list(value), slices, fill_with) + + +def do_batch( + value: "t.Iterable[V]", linecount: int, fill_with: "t.Optional[V]" = None +) -> "t.Iterator[t.List[V]]": """ A filter that batches items. It works pretty much like `slice` just the other way round. It returns a list of lists with the @@ -1111,27 +1111,27 @@ def do_batch( {%- endfor %} </table> """ - tmp: "t.List[V]" = [] - + tmp: "t.List[V]" = [] + for item in value: if len(tmp) == linecount: yield tmp tmp = [] - + tmp.append(item) - + if tmp: if fill_with is not None and len(tmp) < linecount: tmp += [fill_with] * (linecount - len(tmp)) - + yield tmp -def do_round( - value: float, - precision: int = 0, - method: 'te.Literal["common", "ceil", "floor"]' = "common", -) -> float: +def do_round( + value: float, + precision: int = 0, + method: 'te.Literal["common", "ceil", "floor"]' = "common", +) -> float: """Round the number to a given precision. The first parameter specifies the precision (default is ``0``), the second the rounding method: @@ -1157,109 +1157,109 @@ def do_round( {{ 42.55|round|int }} -> 43 """ - if method not in {"common", "ceil", "floor"}: - raise FilterArgumentError("method must be common, ceil or floor") - - if method == "common": + if method not in {"common", "ceil", "floor"}: + raise FilterArgumentError("method must be common, ceil or floor") + + if method == "common": return round(value, precision) - + func = getattr(math, method) - return t.cast(float, func(value * (10 ** precision)) / (10 ** precision)) - - -class _GroupTuple(t.NamedTuple): - grouper: t.Any - list: t.List - - # Use the regular tuple repr to hide this subclass if users print - # out the value during debugging. - def __repr__(self) -> str: - return tuple.__repr__(self) - - def __str__(self) -> str: - return tuple.__str__(self) - - -@pass_environment -def sync_do_groupby( - environment: "Environment", - value: "t.Iterable[V]", - attribute: t.Union[str, int], - default: t.Optional[t.Any] = None, -) -> "t.List[t.Tuple[t.Any, t.List[V]]]": - """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``, - the values are sorted first so only one group is returned for each - unique value. - - For example, a list of ``User`` objects with a ``city`` attribute - can be rendered in groups. In this example, ``grouper`` refers to - the ``city`` value of the group. + return t.cast(float, func(value * (10 ** precision)) / (10 ** precision)) + + +class _GroupTuple(t.NamedTuple): + grouper: t.Any + list: t.List + + # Use the regular tuple repr to hide this subclass if users print + # out the value during debugging. + def __repr__(self) -> str: + return tuple.__repr__(self) + + def __str__(self) -> str: + return tuple.__str__(self) + + +@pass_environment +def sync_do_groupby( + environment: "Environment", + value: "t.Iterable[V]", + attribute: t.Union[str, int], + default: t.Optional[t.Any] = None, +) -> "t.List[t.Tuple[t.Any, t.List[V]]]": + """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``, + the values are sorted first so only one group is returned for each + unique value. + + For example, a list of ``User`` objects with a ``city`` attribute + can be rendered in groups. In this example, ``grouper`` refers to + the ``city`` value of the group. .. sourcecode:: html+jinja - <ul>{% for city, items in users|groupby("city") %} - <li>{{ city }} - <ul>{% for user in items %} - <li>{{ user.name }} - {% endfor %}</ul> - </li> - {% endfor %}</ul> + <ul>{% for city, items in users|groupby("city") %} + <li>{{ city }} + <ul>{% for user in items %} + <li>{{ user.name }} + {% endfor %}</ul> + </li> + {% endfor %}</ul> - ``groupby`` yields namedtuples of ``(grouper, list)``, which - can be used instead of the tuple unpacking above. ``grouper`` is the - value of the attribute, and ``list`` is the items with that value. + ``groupby`` yields namedtuples of ``(grouper, list)``, which + can be used instead of the tuple unpacking above. ``grouper`` is the + value of the attribute, and ``list`` is the items with that value. .. sourcecode:: html+jinja - <ul>{% for group in users|groupby("city") %} - <li>{{ group.grouper }}: {{ group.list|join(", ") }} - {% endfor %}</ul> - - You can specify a ``default`` value to use if an object in the list - does not have the given attribute. - - .. sourcecode:: jinja - - <ul>{% for city, items in users|groupby("city", default="NY") %} - <li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li> - {% endfor %}</ul> - - .. versionchanged:: 3.0 - Added the ``default`` parameter. - + <ul>{% for group in users|groupby("city") %} + <li>{{ group.grouper }}: {{ group.list|join(", ") }} + {% endfor %}</ul> + + You can specify a ``default`` value to use if an object in the list + does not have the given attribute. + + .. sourcecode:: jinja + + <ul>{% for city, items in users|groupby("city", default="NY") %} + <li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li> + {% endfor %}</ul> + + .. versionchanged:: 3.0 + Added the ``default`` parameter. + .. versionchanged:: 2.6 - The attribute supports dot notation for nested access. + The attribute supports dot notation for nested access. """ - expr = make_attrgetter(environment, attribute, default=default) - return [ - _GroupTuple(key, list(values)) - for key, values in groupby(sorted(value, key=expr), expr) - ] - - -@async_variant(sync_do_groupby) # type: ignore -async def do_groupby( - environment: "Environment", - 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 [ - _GroupTuple(key, await auto_to_list(values)) - for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr) - ] - - -@pass_environment -def sync_do_sum( - environment: "Environment", - iterable: "t.Iterable[V]", - attribute: t.Optional[t.Union[str, int]] = None, - start: V = 0, # type: ignore -) -> V: + expr = make_attrgetter(environment, attribute, default=default) + return [ + _GroupTuple(key, list(values)) + for key, values in groupby(sorted(value, key=expr), expr) + ] + + +@async_variant(sync_do_groupby) # type: ignore +async def do_groupby( + environment: "Environment", + 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 [ + _GroupTuple(key, await auto_to_list(values)) + for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr) + ] + + +@pass_environment +def sync_do_sum( + environment: "Environment", + iterable: "t.Iterable[V]", + attribute: t.Optional[t.Union[str, int]] = None, + start: V = 0, # type: ignore +) -> V: """Returns the sum of a sequence of numbers plus the value of parameter 'start' (which defaults to 0). When the sequence is empty it returns start. @@ -1275,89 +1275,89 @@ def sync_do_sum( attributes. Also the `start` parameter was moved on to the right. """ if attribute is not None: - iterable = map(make_attrgetter(environment, attribute), iterable) - + iterable = map(make_attrgetter(environment, attribute), iterable) + return sum(iterable, start) -@async_variant(sync_do_sum) # type: ignore -async def do_sum( - environment: "Environment", - iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - attribute: t.Optional[t.Union[str, int]] = None, - start: V = 0, # type: ignore -) -> V: - rv = start - - if attribute is not None: - func = make_attrgetter(environment, attribute) - else: - - def func(x: V) -> V: - return x - - async for item in auto_aiter(iterable): - rv += func(item) - - return rv - - -def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]": +@async_variant(sync_do_sum) # type: ignore +async def do_sum( + environment: "Environment", + iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", + attribute: t.Optional[t.Union[str, int]] = None, + start: V = 0, # type: ignore +) -> V: + rv = start + + if attribute is not None: + func = make_attrgetter(environment, attribute) + else: + + def func(x: V) -> V: + return x + + async for item in auto_aiter(iterable): + rv += func(item) + + return rv + + +def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]": """Convert the value into a list. If it was a string the returned list will be a list of characters. """ return list(value) -@async_variant(sync_do_list) # type: ignore -async def do_list(value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]") -> "t.List[V]": - return await auto_to_list(value) - - -def do_mark_safe(value: str) -> Markup: +@async_variant(sync_do_list) # type: ignore +async def do_list(value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]") -> "t.List[V]": + return await auto_to_list(value) + + +def do_mark_safe(value: str) -> Markup: """Mark the value as safe which means that in an environment with automatic escaping enabled this variable will not be escaped. """ return Markup(value) -def do_mark_unsafe(value: str) -> str: +def do_mark_unsafe(value: str) -> str: """Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" - return str(value) - - -@typing.overload -def do_reverse(value: str) -> str: - ... - - -@typing.overload -def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]": - ... - - -def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]]: + return str(value) + + +@typing.overload +def do_reverse(value: str) -> str: + ... + + +@typing.overload +def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]": + ... + + +def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]]: """Reverse the object or return an iterator that iterates over it the other way round. """ - if isinstance(value, str): + if isinstance(value, str): return value[::-1] - + try: - return reversed(value) # type: ignore + return reversed(value) # type: ignore except TypeError: try: rv = list(value) rv.reverse() return rv - except TypeError as e: - raise FilterArgumentError("argument must be iterable") from e + except TypeError as e: + raise FilterArgumentError("argument must be iterable") from e -@pass_environment -def do_attr( - environment: "Environment", obj: t.Any, name: str -) -> t.Union[Undefined, t.Any]: +@pass_environment +def do_attr( + environment: "Environment", obj: t.Any, name: str +) -> t.Union[Undefined, t.Any]: """Get an attribute of an object. ``foo|attr("bar")`` works like ``foo.bar`` just that always an attribute is returned and items are not looked up. @@ -1374,39 +1374,39 @@ def do_attr( except AttributeError: pass else: - if environment.sandboxed: - environment = t.cast("SandboxedEnvironment", environment) - - if not environment.is_safe_attribute(obj, name, value): - return environment.unsafe_undefined(obj, name) - + if environment.sandboxed: + environment = t.cast("SandboxedEnvironment", environment) + + if not environment.is_safe_attribute(obj, name, value): + return environment.unsafe_undefined(obj, name) + return value - + return environment.undefined(obj=obj, name=name) -@typing.overload -def sync_do_map( - context: "Context", value: t.Iterable, name: str, *args: t.Any, **kwargs: t.Any -) -> t.Iterable: - ... - - -@typing.overload -def sync_do_map( - context: "Context", - value: t.Iterable, - *, - attribute: str = ..., - default: t.Optional[t.Any] = None, -) -> t.Iterable: - ... - - -@pass_context -def sync_do_map( - context: "Context", value: t.Iterable, *args: t.Any, **kwargs: t.Any -) -> t.Iterable: +@typing.overload +def sync_do_map( + context: "Context", value: t.Iterable, name: str, *args: t.Any, **kwargs: t.Any +) -> t.Iterable: + ... + + +@typing.overload +def sync_do_map( + context: "Context", + value: t.Iterable, + *, + attribute: str = ..., + default: t.Optional[t.Any] = None, +) -> t.Iterable: + ... + + +@pass_context +def sync_do_map( + context: "Context", value: t.Iterable, *args: t.Any, **kwargs: t.Any +) -> t.Iterable: """Applies a filter on a sequence of objects or looks up an attribute. This is useful when dealing with lists of objects but you are really only interested in a certain value of it. @@ -1418,13 +1418,13 @@ def sync_do_map( Users on this page: {{ users|map(attribute='username')|join(', ') }} - 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(", ") }} - + 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(", ") }} + 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 text conversion filter on a sequence: @@ -1433,66 +1433,66 @@ def sync_do_map( Users on this page: {{ titles|map('lower')|join(', ') }} - Similar to a generator comprehension such as: - - .. code-block:: python - - (u.username for u in users) - (getattr(u, "username", "Anonymous") for u in users) - (do_lower(x) for x in titles) - - .. versionchanged:: 2.11.0 - Added the ``default`` parameter. - + Similar to a generator comprehension such as: + + .. code-block:: python + + (u.username for u in users) + (getattr(u, "username", "Anonymous") for u in users) + (do_lower(x) for x in titles) + + .. versionchanged:: 2.11.0 + Added the ``default`` parameter. + .. versionadded:: 2.7 """ - if value: - func = prepare_map(context, args, kwargs) - - for item in value: + if value: + func = prepare_map(context, args, kwargs) + + for item in value: yield func(item) -@typing.overload -def do_map( - context: "Context", - value: t.Union[t.AsyncIterable, t.Iterable], - name: str, - *args: t.Any, - **kwargs: t.Any, -) -> t.Iterable: - ... - - -@typing.overload -def do_map( - context: "Context", - value: t.Union[t.AsyncIterable, t.Iterable], - *, - attribute: str = ..., - default: t.Optional[t.Any] = None, -) -> t.Iterable: - ... - - -@async_variant(sync_do_map) # type: ignore -async def do_map( - context: "Context", - value: t.Union[t.AsyncIterable, t.Iterable], - *args: t.Any, - **kwargs: t.Any, -) -> t.AsyncIterable: - if value: - func = prepare_map(context, args, kwargs) - - async for item in auto_aiter(value): - yield await auto_await(func(item)) - - -@pass_context -def sync_do_select( - context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any -) -> "t.Iterator[V]": +@typing.overload +def do_map( + context: "Context", + value: t.Union[t.AsyncIterable, t.Iterable], + name: str, + *args: t.Any, + **kwargs: t.Any, +) -> t.Iterable: + ... + + +@typing.overload +def do_map( + context: "Context", + value: t.Union[t.AsyncIterable, t.Iterable], + *, + attribute: str = ..., + default: t.Optional[t.Any] = None, +) -> t.Iterable: + ... + + +@async_variant(sync_do_map) # type: ignore +async def do_map( + context: "Context", + value: t.Union[t.AsyncIterable, t.Iterable], + *args: t.Any, + **kwargs: t.Any, +) -> t.AsyncIterable: + if value: + func = prepare_map(context, args, kwargs) + + async for item in auto_aiter(value): + yield await auto_await(func(item)) + + +@pass_context +def sync_do_select( + context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any +) -> "t.Iterator[V]": """Filters a sequence of objects by applying a test to each object, and only selecting the objects with the test succeeding. @@ -1508,32 +1508,32 @@ def sync_do_select( {{ numbers|select("lessthan", 42) }} {{ strings|select("equalto", "mystring") }} - Similar to a generator comprehension such as: - - .. code-block:: python - - (n for n in numbers if test_odd(n)) - (n for n in numbers if test_divisibleby(n, 3)) - + Similar to a generator comprehension such as: + + .. code-block:: python + + (n for n in numbers if test_odd(n)) + (n for n in numbers if test_divisibleby(n, 3)) + .. versionadded:: 2.7 """ - return select_or_reject(context, value, args, kwargs, lambda x: x, False) - - -@async_variant(sync_do_select) # type: ignore -async def do_select( - context: "Context", - value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - *args: t.Any, - **kwargs: t.Any, -) -> "t.AsyncIterator[V]": - return async_select_or_reject(context, value, args, kwargs, lambda x: x, False) - - -@pass_context -def sync_do_reject( - context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any -) -> "t.Iterator[V]": + return select_or_reject(context, value, args, kwargs, lambda x: x, False) + + +@async_variant(sync_do_select) # type: ignore +async def do_select( + context: "Context", + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", + *args: t.Any, + **kwargs: t.Any, +) -> "t.AsyncIterator[V]": + return async_select_or_reject(context, value, args, kwargs, lambda x: x, False) + + +@pass_context +def sync_do_reject( + context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any +) -> "t.Iterator[V]": """Filters a sequence of objects by applying a test to each object, and rejecting the objects with the test succeeding. @@ -1545,31 +1545,31 @@ def sync_do_reject( {{ numbers|reject("odd") }} - Similar to a generator comprehension such as: - - .. code-block:: python - - (n for n in numbers if not test_odd(n)) - + Similar to a generator comprehension such as: + + .. code-block:: python + + (n for n in numbers if not test_odd(n)) + .. versionadded:: 2.7 """ - return select_or_reject(context, value, args, kwargs, lambda x: not x, False) - - -@async_variant(sync_do_reject) # type: ignore -async def do_reject( - context: "Context", - value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - *args: t.Any, - **kwargs: t.Any, -) -> "t.AsyncIterator[V]": - return async_select_or_reject(context, value, args, kwargs, lambda x: not x, False) - - -@pass_context -def sync_do_selectattr( - context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any -) -> "t.Iterator[V]": + return select_or_reject(context, value, args, kwargs, lambda x: not x, False) + + +@async_variant(sync_do_reject) # type: ignore +async def do_reject( + context: "Context", + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", + *args: t.Any, + **kwargs: t.Any, +) -> "t.AsyncIterator[V]": + return async_select_or_reject(context, value, args, kwargs, lambda x: not x, False) + + +@pass_context +def sync_do_selectattr( + context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any +) -> "t.Iterator[V]": """Filters a sequence of objects by applying a test to the specified attribute of each object, and only selecting the objects with the test succeeding. @@ -1584,32 +1584,32 @@ def sync_do_selectattr( {{ users|selectattr("is_active") }} {{ users|selectattr("email", "none") }} - Similar to a generator comprehension such as: - - .. code-block:: python - - (u for user in users if user.is_active) - (u for user in users if test_none(user.email)) - + Similar to a generator comprehension such as: + + .. code-block:: python + + (u for user in users if user.is_active) + (u for user in users if test_none(user.email)) + .. versionadded:: 2.7 """ - return select_or_reject(context, value, args, kwargs, lambda x: x, True) - - -@async_variant(sync_do_selectattr) # type: ignore -async def do_selectattr( - context: "Context", - value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - *args: t.Any, - **kwargs: t.Any, -) -> "t.AsyncIterator[V]": - return async_select_or_reject(context, value, args, kwargs, lambda x: x, True) - - -@pass_context -def sync_do_rejectattr( - context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any -) -> "t.Iterator[V]": + return select_or_reject(context, value, args, kwargs, lambda x: x, True) + + +@async_variant(sync_do_selectattr) # type: ignore +async def do_selectattr( + context: "Context", + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", + *args: t.Any, + **kwargs: t.Any, +) -> "t.AsyncIterator[V]": + return async_select_or_reject(context, value, args, kwargs, lambda x: x, True) + + +@pass_context +def sync_do_rejectattr( + context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any +) -> "t.Iterator[V]": """Filters a sequence of objects by applying a test to the specified attribute of each object, and rejecting the objects with the test succeeding. @@ -1622,203 +1622,203 @@ def sync_do_rejectattr( {{ users|rejectattr("is_active") }} {{ users|rejectattr("email", "none") }} - Similar to a generator comprehension such as: - - .. code-block:: python - - (u for user in users if not user.is_active) - (u for user in users if not test_none(user.email)) - + Similar to a generator comprehension such as: + + .. code-block:: python + + (u for user in users if not user.is_active) + (u for user in users if not test_none(user.email)) + .. versionadded:: 2.7 """ - return select_or_reject(context, value, args, kwargs, lambda x: not x, True) + return select_or_reject(context, value, args, kwargs, lambda x: not x, True) -@async_variant(sync_do_rejectattr) # type: ignore -async def do_rejectattr( - context: "Context", - value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - *args: t.Any, - **kwargs: t.Any, -) -> "t.AsyncIterator[V]": - return async_select_or_reject(context, value, args, kwargs, lambda x: not x, True) +@async_variant(sync_do_rejectattr) # type: ignore +async def do_rejectattr( + context: "Context", + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", + *args: t.Any, + **kwargs: t.Any, +) -> "t.AsyncIterator[V]": + return async_select_or_reject(context, value, args, kwargs, lambda x: not x, True) -@pass_eval_context -def do_tojson( - eval_ctx: "EvalContext", value: t.Any, indent: t.Optional[int] = None -) -> Markup: - """Serialize an object to a string of JSON, and mark it safe to - render in HTML. This filter is only for use in HTML documents. +@pass_eval_context +def do_tojson( + eval_ctx: "EvalContext", value: t.Any, indent: t.Optional[int] = None +) -> Markup: + """Serialize an object to a string of JSON, and mark it safe to + render in HTML. This filter is only for use in HTML documents. - The returned string is safe to render in HTML documents and - ``<script>`` tags. The exception is in HTML attributes that are - double quoted; either use single quotes or the ``|forceescape`` - filter. + The returned string is safe to render in HTML documents and + ``<script>`` tags. The exception is in HTML attributes that are + double quoted; either use single quotes or the ``|forceescape`` + filter. - :param value: The object to serialize to JSON. - :param indent: The ``indent`` parameter passed to ``dumps``, for - pretty-printing the value. + :param value: The object to serialize to JSON. + :param indent: The ``indent`` parameter passed to ``dumps``, for + pretty-printing the value. .. versionadded:: 2.9 """ policies = eval_ctx.environment.policies - dumps = policies["json.dumps_function"] - kwargs = policies["json.dumps_kwargs"] - + dumps = policies["json.dumps_function"] + kwargs = policies["json.dumps_kwargs"] + if indent is not None: - kwargs = kwargs.copy() - kwargs["indent"] = indent - - return htmlsafe_json_dumps(value, dumps=dumps, **kwargs) + kwargs = kwargs.copy() + kwargs["indent"] = indent + return htmlsafe_json_dumps(value, dumps=dumps, **kwargs) -def prepare_map( - context: "Context", args: t.Tuple, kwargs: t.Dict[str, t.Any] -) -> t.Callable[[t.Any], t.Any]: - if not args and "attribute" in kwargs: - attribute = kwargs.pop("attribute") - default = kwargs.pop("default", None) +def prepare_map( + context: "Context", args: t.Tuple, kwargs: t.Dict[str, t.Any] +) -> t.Callable[[t.Any], t.Any]: + if not args and "attribute" in kwargs: + attribute = kwargs.pop("attribute") + default = kwargs.pop("default", None) + if kwargs: - raise FilterArgumentError( - f"Unexpected keyword argument {next(iter(kwargs))!r}" - ) - - func = make_attrgetter(context.environment, attribute, default=default) + raise FilterArgumentError( + f"Unexpected keyword argument {next(iter(kwargs))!r}" + ) + + func = make_attrgetter(context.environment, attribute, default=default) else: try: - name = args[0] - args = args[1:] + name = args[0] + args = args[1:] except LookupError: - raise FilterArgumentError("map requires a filter argument") from None - - def func(item: t.Any) -> t.Any: - return context.environment.call_filter( - name, item, args, kwargs, context=context - ) - - return func - - -def prepare_select_or_reject( - context: "Context", - args: t.Tuple, - kwargs: t.Dict[str, t.Any], - modfunc: t.Callable[[t.Any], t.Any], - lookup_attr: bool, -) -> t.Callable[[t.Any], t.Any]: + raise FilterArgumentError("map requires a filter argument") from None + + def func(item: t.Any) -> t.Any: + return context.environment.call_filter( + name, item, args, kwargs, context=context + ) + + return func + + +def prepare_select_or_reject( + context: "Context", + args: t.Tuple, + kwargs: t.Dict[str, t.Any], + modfunc: t.Callable[[t.Any], t.Any], + lookup_attr: bool, +) -> t.Callable[[t.Any], t.Any]: if lookup_attr: try: - attr = args[0] + attr = args[0] except LookupError: - raise FilterArgumentError("Missing parameter for attribute name") from None - + raise FilterArgumentError("Missing parameter for attribute name") from None + transfunc = make_attrgetter(context.environment, attr) off = 1 else: off = 0 - def transfunc(x: V) -> V: - return x - + def transfunc(x: V) -> V: + return x + try: - name = args[off] - args = args[1 + off :] - - def func(item: t.Any) -> t.Any: - return context.environment.call_test(name, item, args, kwargs) - + name = args[off] + args = args[1 + off :] + + def func(item: t.Any) -> t.Any: + return context.environment.call_test(name, item, args, kwargs) + except LookupError: - func = bool # type: ignore - - return lambda item: modfunc(func(transfunc(item))) - - -def select_or_reject( - context: "Context", - value: "t.Iterable[V]", - args: t.Tuple, - kwargs: t.Dict[str, t.Any], - modfunc: t.Callable[[t.Any], t.Any], - lookup_attr: bool, -) -> "t.Iterator[V]": - if value: - func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) - - for item in value: - if func(item): - yield item - - -async def async_select_or_reject( - context: "Context", - value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - args: t.Tuple, - kwargs: t.Dict[str, t.Any], - modfunc: t.Callable[[t.Any], t.Any], - lookup_attr: bool, -) -> "t.AsyncIterator[V]": - if value: - func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) - - async for item in auto_aiter(value): + func = bool # type: ignore + + return lambda item: modfunc(func(transfunc(item))) + + +def select_or_reject( + context: "Context", + value: "t.Iterable[V]", + args: t.Tuple, + kwargs: t.Dict[str, t.Any], + modfunc: t.Callable[[t.Any], t.Any], + lookup_attr: bool, +) -> "t.Iterator[V]": + if value: + func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) + + for item in value: if func(item): yield item +async def async_select_or_reject( + context: "Context", + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", + args: t.Tuple, + kwargs: t.Dict[str, t.Any], + modfunc: t.Callable[[t.Any], t.Any], + lookup_attr: bool, +) -> "t.AsyncIterator[V]": + if value: + func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) + + async for item in auto_aiter(value): + if func(item): + yield item + + FILTERS = { - "abs": abs, - "attr": do_attr, - "batch": do_batch, - "capitalize": do_capitalize, - "center": do_center, - "count": len, - "d": do_default, - "default": do_default, - "dictsort": do_dictsort, - "e": escape, - "escape": escape, - "filesizeformat": do_filesizeformat, - "first": do_first, - "float": do_float, - "forceescape": do_forceescape, - "format": do_format, - "groupby": do_groupby, - "indent": do_indent, - "int": do_int, - "join": do_join, - "last": do_last, - "length": len, - "list": do_list, - "lower": do_lower, - "map": do_map, - "min": do_min, - "max": do_max, - "pprint": do_pprint, - "random": do_random, - "reject": do_reject, - "rejectattr": do_rejectattr, - "replace": do_replace, - "reverse": do_reverse, - "round": do_round, - "safe": do_mark_safe, - "select": do_select, - "selectattr": do_selectattr, - "slice": do_slice, - "sort": do_sort, - "string": soft_str, - "striptags": do_striptags, - "sum": do_sum, - "title": do_title, - "trim": do_trim, - "truncate": do_truncate, - "unique": do_unique, - "upper": do_upper, - "urlencode": do_urlencode, - "urlize": do_urlize, - "wordcount": do_wordcount, - "wordwrap": do_wordwrap, - "xmlattr": do_xmlattr, - "tojson": do_tojson, + "abs": abs, + "attr": do_attr, + "batch": do_batch, + "capitalize": do_capitalize, + "center": do_center, + "count": len, + "d": do_default, + "default": do_default, + "dictsort": do_dictsort, + "e": escape, + "escape": escape, + "filesizeformat": do_filesizeformat, + "first": do_first, + "float": do_float, + "forceescape": do_forceescape, + "format": do_format, + "groupby": do_groupby, + "indent": do_indent, + "int": do_int, + "join": do_join, + "last": do_last, + "length": len, + "list": do_list, + "lower": do_lower, + "map": do_map, + "min": do_min, + "max": do_max, + "pprint": do_pprint, + "random": do_random, + "reject": do_reject, + "rejectattr": do_rejectattr, + "replace": do_replace, + "reverse": do_reverse, + "round": do_round, + "safe": do_mark_safe, + "select": do_select, + "selectattr": do_selectattr, + "slice": do_slice, + "sort": do_sort, + "string": soft_str, + "striptags": do_striptags, + "sum": do_sum, + "title": do_title, + "trim": do_trim, + "truncate": do_truncate, + "unique": do_unique, + "upper": do_upper, + "urlencode": do_urlencode, + "urlize": do_urlize, + "wordcount": do_wordcount, + "wordwrap": do_wordwrap, + "xmlattr": do_xmlattr, + "tojson": do_tojson, } diff --git a/contrib/python/Jinja2/py3/jinja2/idtracking.py b/contrib/python/Jinja2/py3/jinja2/idtracking.py index 38c525ee31..aefcd02738 100644 --- a/contrib/python/Jinja2/py3/jinja2/idtracking.py +++ b/contrib/python/Jinja2/py3/jinja2/idtracking.py @@ -1,17 +1,17 @@ -import typing as t +import typing as t + +from . import nodes +from .visitor import NodeVisitor -from . import nodes -from .visitor import NodeVisitor +VAR_LOAD_PARAMETER = "param" +VAR_LOAD_RESOLVE = "resolve" +VAR_LOAD_ALIAS = "alias" +VAR_LOAD_UNDEFINED = "undefined" -VAR_LOAD_PARAMETER = "param" -VAR_LOAD_RESOLVE = "resolve" -VAR_LOAD_ALIAS = "alias" -VAR_LOAD_UNDEFINED = "undefined" - -def find_symbols( - nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None -) -> "Symbols": +def find_symbols( + nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None +) -> "Symbols": sym = Symbols(parent=parent_symbols) visitor = FrameSymbolVisitor(sym) for node in nodes: @@ -19,79 +19,79 @@ def find_symbols( return sym -def symbols_for_node( - node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None -) -> "Symbols": +def symbols_for_node( + node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None +) -> "Symbols": sym = Symbols(parent=parent_symbols) sym.analyze_node(node) return sym -class Symbols: - def __init__( - self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None - ) -> None: +class Symbols: + def __init__( + self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None + ) -> None: if level is None: if parent is None: level = 0 else: level = parent.level + 1 - - self.level: int = level + + self.level: int = level self.parent = parent - self.refs: t.Dict[str, str] = {} - self.loads: t.Dict[str, t.Any] = {} - self.stores: t.Set[str] = set() + self.refs: t.Dict[str, str] = {} + self.loads: t.Dict[str, t.Any] = {} + self.stores: t.Set[str] = set() - def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None: + def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None: visitor = RootVisitor(self) visitor.visit(node, **kwargs) - def _define_ref( - self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None - ) -> str: - ident = f"l_{self.level}_{name}" + def _define_ref( + self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None + ) -> str: + ident = f"l_{self.level}_{name}" self.refs[name] = ident if load is not None: self.loads[ident] = load return ident - def find_load(self, target: str) -> t.Optional[t.Any]: + def find_load(self, target: str) -> t.Optional[t.Any]: if target in self.loads: return self.loads[target] - + if self.parent is not None: return self.parent.find_load(target) - return None - - def find_ref(self, name: str) -> t.Optional[str]: + return None + + def find_ref(self, name: str) -> t.Optional[str]: if name in self.refs: return self.refs[name] - + if self.parent is not None: return self.parent.find_ref(name) - return None - - def ref(self, name: str) -> str: + return None + + def ref(self, name: str) -> str: rv = self.find_ref(name) if rv is None: - raise AssertionError( - "Tried to resolve a name to a reference that was" - f" unknown to the frame ({name!r})" - ) + raise AssertionError( + "Tried to resolve a name to a reference that was" + f" unknown to the frame ({name!r})" + ) return rv - def copy(self) -> "Symbols": - rv = t.cast(Symbols, object.__new__(self.__class__)) + def copy(self) -> "Symbols": + rv = t.cast(Symbols, object.__new__(self.__class__)) rv.__dict__.update(self.__dict__) rv.refs = self.refs.copy() rv.loads = self.loads.copy() rv.stores = self.stores.copy() return rv - def store(self, name: str) -> None: + def store(self, name: str) -> None: self.stores.add(name) # If we have not see the name referenced yet, we need to figure @@ -109,16 +109,16 @@ class Symbols: # Otherwise we can just set it to undefined. self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None)) - def declare_parameter(self, name: str) -> str: + def declare_parameter(self, name: str) -> str: self.stores.add(name) return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None)) - def load(self, name: str) -> None: - if self.find_ref(name) is None: + def load(self, name: str) -> None: + if self.find_ref(name) is None: self._define_ref(name, load=(VAR_LOAD_RESOLVE, name)) - def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None: - stores: t.Dict[str, int] = {} + def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None: + stores: t.Dict[str, int] = {} for branch in branch_symbols: for target in branch.stores: if target in self.stores: @@ -130,12 +130,12 @@ class Symbols: self.loads.update(sym.loads) self.stores.update(sym.stores) - for name, branch_count in stores.items(): + for name, branch_count in stores.items(): if branch_count == len(branch_symbols): continue - - target = self.find_ref(name) # type: ignore - assert target is not None, "should not happen" + + target = self.find_ref(name) # type: ignore + assert target is not None, "should not happen" if self.parent is not None: outer_target = self.parent.find_ref(name) @@ -144,121 +144,121 @@ class Symbols: continue self.loads[target] = (VAR_LOAD_RESOLVE, name) - def dump_stores(self) -> t.Dict[str, str]: - rv: t.Dict[str, str] = {} - node: t.Optional["Symbols"] = self - + def dump_stores(self) -> t.Dict[str, str]: + rv: t.Dict[str, str] = {} + node: t.Optional["Symbols"] = self + while node is not None: - for name in sorted(node.stores): + for name in sorted(node.stores): if name not in rv: - rv[name] = self.find_ref(name) # type: ignore - + rv[name] = self.find_ref(name) # type: ignore + node = node.parent - + return rv - def dump_param_targets(self) -> t.Set[str]: + def dump_param_targets(self) -> t.Set[str]: rv = set() - node: t.Optional["Symbols"] = self - + node: t.Optional["Symbols"] = self + while node is not None: - for target, (instr, _) in self.loads.items(): + for target, (instr, _) in self.loads.items(): if instr == VAR_LOAD_PARAMETER: rv.add(target) - + node = node.parent - + return rv class RootVisitor(NodeVisitor): - def __init__(self, symbols: "Symbols") -> None: + def __init__(self, symbols: "Symbols") -> None: self.sym_visitor = FrameSymbolVisitor(symbols) - def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None: + def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None: for child in node.iter_child_nodes(): self.sym_visitor.visit(child) - visit_Template = _simple_visit - visit_Block = _simple_visit - visit_Macro = _simple_visit - visit_FilterBlock = _simple_visit - visit_Scope = _simple_visit - visit_If = _simple_visit - visit_ScopedEvalContextModifier = _simple_visit + visit_Template = _simple_visit + visit_Block = _simple_visit + visit_Macro = _simple_visit + visit_FilterBlock = _simple_visit + visit_Scope = _simple_visit + visit_If = _simple_visit + visit_ScopedEvalContextModifier = _simple_visit - def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None: + def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None: for child in node.body: self.sym_visitor.visit(child) - def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None: - for child in node.iter_child_nodes(exclude=("call",)): + def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None: + for child in node.iter_child_nodes(exclude=("call",)): self.sym_visitor.visit(child) - def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None: + def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None: for child in node.body: self.sym_visitor.visit(child) - def visit_For( - self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any - ) -> None: - if for_branch == "body": + def visit_For( + self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any + ) -> None: + if for_branch == "body": self.sym_visitor.visit(node.target, store_as_param=True) branch = node.body - elif for_branch == "else": + elif for_branch == "else": branch = node.else_ - elif for_branch == "test": + elif for_branch == "test": self.sym_visitor.visit(node.target, store_as_param=True) if node.test is not None: self.sym_visitor.visit(node.test) return else: - raise RuntimeError("Unknown for branch") + raise RuntimeError("Unknown for branch") - if branch: - for item in branch: - self.sym_visitor.visit(item) - - def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None: + if branch: + for item in branch: + self.sym_visitor.visit(item) + + def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None: for target in node.targets: self.sym_visitor.visit(target) for child in node.body: self.sym_visitor.visit(child) - def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None: - raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}") + def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None: + raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}") class FrameSymbolVisitor(NodeVisitor): """A visitor for `Frame.inspect`.""" - def __init__(self, symbols: "Symbols") -> None: + def __init__(self, symbols: "Symbols") -> None: self.symbols = symbols - def visit_Name( - self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any - ) -> None: + def visit_Name( + self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any + ) -> None: """All assignments to names go through this function.""" - if store_as_param or node.ctx == "param": + if store_as_param or node.ctx == "param": self.symbols.declare_parameter(node.name) - elif node.ctx == "store": + elif node.ctx == "store": self.symbols.store(node.name) - elif node.ctx == "load": + elif node.ctx == "load": self.symbols.load(node.name) - def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None: + def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None: self.symbols.load(node.name) - def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None: + def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None: self.visit(node.test, **kwargs) original_symbols = self.symbols - def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols": + def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols": self.symbols = rv = original_symbols.copy() - + for subnode in nodes: self.visit(subnode, **kwargs) - + self.symbols = original_symbols return rv @@ -267,52 +267,52 @@ class FrameSymbolVisitor(NodeVisitor): else_symbols = inner_visit(node.else_ or ()) self.symbols.branch_update([body_symbols, elif_symbols, else_symbols]) - def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None: + def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None: self.symbols.store(node.name) - def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None: + def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None: self.generic_visit(node, **kwargs) self.symbols.store(node.target) - def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None: + def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None: self.generic_visit(node, **kwargs) - + for name in node.names: if isinstance(name, tuple): self.symbols.store(name[1]) else: self.symbols.store(name) - def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None: + def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None: """Visit assignments in the correct order.""" self.visit(node.node, **kwargs) self.visit(node.target, **kwargs) - def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None: + def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None: """Visiting stops at for blocks. However the block sequence is visited as part of the outer scope. """ self.visit(node.iter, **kwargs) - def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None: + def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None: self.visit(node.call, **kwargs) - def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None: + def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None: self.visit(node.filter, **kwargs) - def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None: + def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None: for target in node.values: self.visit(target) - def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None: + def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None: """Stop visiting at block assigns.""" self.visit(node.target, **kwargs) - def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None: + def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None: """Stop visiting at scopes.""" - def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None: + def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None: """Stop visiting at blocks.""" - def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None: + def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None: """Do not visit into overlay scopes.""" diff --git a/contrib/python/Jinja2/py3/jinja2/lexer.py b/contrib/python/Jinja2/py3/jinja2/lexer.py index c25ab0f690..5f2c443371 100644 --- a/contrib/python/Jinja2/py3/jinja2/lexer.py +++ b/contrib/python/Jinja2/py3/jinja2/lexer.py @@ -1,278 +1,278 @@ -"""Implements a Jinja / Python combination lexer. The ``Lexer`` class -is used to do some preprocessing. It filters out invalid operators like -the bitshift operators we don't allow in templates. It separates -template code and python code in expressions. +"""Implements a Jinja / Python combination lexer. The ``Lexer`` class +is used to do some preprocessing. It filters out invalid operators like +the bitshift operators we don't allow in templates. It separates +template code and python code in expressions. """ import re -import typing as t -from ast import literal_eval +import typing as t +from ast import literal_eval from collections import deque -from sys import intern +from sys import intern -from ._identifier import pattern as name_re -from .exceptions import TemplateSyntaxError -from .utils import LRUCache - -if t.TYPE_CHECKING: - import typing_extensions as te - from .environment import Environment +from ._identifier import pattern as name_re +from .exceptions import TemplateSyntaxError +from .utils import LRUCache +if t.TYPE_CHECKING: + import typing_extensions as te + from .environment import Environment + # cache for the lexers. Exists in order to be able to have multiple # environments with the same lexer -_lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore +_lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore # static regular expressions -whitespace_re = re.compile(r"\s+") -newline_re = re.compile(r"(\r\n|\r|\n)") -string_re = re.compile( - r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S -) -integer_re = re.compile( - r""" - ( - 0b(_?[0-1])+ # binary - | - 0o(_?[0-7])+ # octal - | - 0x(_?[\da-f])+ # hex - | - [1-9](_?\d)* # decimal - | - 0(_?0)* # decimal zero - ) - """, - re.IGNORECASE | re.VERBOSE, -) -float_re = re.compile( - r""" - (?<!\.) # doesn't start with a . - (\d+_)*\d+ # digits, possibly _ separated - ( - (\.(\d+_)*\d+)? # optional fractional part - e[+\-]?(\d+_)*\d+ # exponent part - | - \.(\d+_)*\d+ # required fractional part - ) - """, - re.IGNORECASE | re.VERBOSE, -) +whitespace_re = re.compile(r"\s+") +newline_re = re.compile(r"(\r\n|\r|\n)") +string_re = re.compile( + r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S +) +integer_re = re.compile( + r""" + ( + 0b(_?[0-1])+ # binary + | + 0o(_?[0-7])+ # octal + | + 0x(_?[\da-f])+ # hex + | + [1-9](_?\d)* # decimal + | + 0(_?0)* # decimal zero + ) + """, + re.IGNORECASE | re.VERBOSE, +) +float_re = re.compile( + r""" + (?<!\.) # doesn't start with a . + (\d+_)*\d+ # digits, possibly _ separated + ( + (\.(\d+_)*\d+)? # optional fractional part + e[+\-]?(\d+_)*\d+ # exponent part + | + \.(\d+_)*\d+ # required fractional part + ) + """, + re.IGNORECASE | re.VERBOSE, +) # internal the tokens and keep references to them -TOKEN_ADD = intern("add") -TOKEN_ASSIGN = intern("assign") -TOKEN_COLON = intern("colon") -TOKEN_COMMA = intern("comma") -TOKEN_DIV = intern("div") -TOKEN_DOT = intern("dot") -TOKEN_EQ = intern("eq") -TOKEN_FLOORDIV = intern("floordiv") -TOKEN_GT = intern("gt") -TOKEN_GTEQ = intern("gteq") -TOKEN_LBRACE = intern("lbrace") -TOKEN_LBRACKET = intern("lbracket") -TOKEN_LPAREN = intern("lparen") -TOKEN_LT = intern("lt") -TOKEN_LTEQ = intern("lteq") -TOKEN_MOD = intern("mod") -TOKEN_MUL = intern("mul") -TOKEN_NE = intern("ne") -TOKEN_PIPE = intern("pipe") -TOKEN_POW = intern("pow") -TOKEN_RBRACE = intern("rbrace") -TOKEN_RBRACKET = intern("rbracket") -TOKEN_RPAREN = intern("rparen") -TOKEN_SEMICOLON = intern("semicolon") -TOKEN_SUB = intern("sub") -TOKEN_TILDE = intern("tilde") -TOKEN_WHITESPACE = intern("whitespace") -TOKEN_FLOAT = intern("float") -TOKEN_INTEGER = intern("integer") -TOKEN_NAME = intern("name") -TOKEN_STRING = intern("string") -TOKEN_OPERATOR = intern("operator") -TOKEN_BLOCK_BEGIN = intern("block_begin") -TOKEN_BLOCK_END = intern("block_end") -TOKEN_VARIABLE_BEGIN = intern("variable_begin") -TOKEN_VARIABLE_END = intern("variable_end") -TOKEN_RAW_BEGIN = intern("raw_begin") -TOKEN_RAW_END = intern("raw_end") -TOKEN_COMMENT_BEGIN = intern("comment_begin") -TOKEN_COMMENT_END = intern("comment_end") -TOKEN_COMMENT = intern("comment") -TOKEN_LINESTATEMENT_BEGIN = intern("linestatement_begin") -TOKEN_LINESTATEMENT_END = intern("linestatement_end") -TOKEN_LINECOMMENT_BEGIN = intern("linecomment_begin") -TOKEN_LINECOMMENT_END = intern("linecomment_end") -TOKEN_LINECOMMENT = intern("linecomment") -TOKEN_DATA = intern("data") -TOKEN_INITIAL = intern("initial") -TOKEN_EOF = intern("eof") +TOKEN_ADD = intern("add") +TOKEN_ASSIGN = intern("assign") +TOKEN_COLON = intern("colon") +TOKEN_COMMA = intern("comma") +TOKEN_DIV = intern("div") +TOKEN_DOT = intern("dot") +TOKEN_EQ = intern("eq") +TOKEN_FLOORDIV = intern("floordiv") +TOKEN_GT = intern("gt") +TOKEN_GTEQ = intern("gteq") +TOKEN_LBRACE = intern("lbrace") +TOKEN_LBRACKET = intern("lbracket") +TOKEN_LPAREN = intern("lparen") +TOKEN_LT = intern("lt") +TOKEN_LTEQ = intern("lteq") +TOKEN_MOD = intern("mod") +TOKEN_MUL = intern("mul") +TOKEN_NE = intern("ne") +TOKEN_PIPE = intern("pipe") +TOKEN_POW = intern("pow") +TOKEN_RBRACE = intern("rbrace") +TOKEN_RBRACKET = intern("rbracket") +TOKEN_RPAREN = intern("rparen") +TOKEN_SEMICOLON = intern("semicolon") +TOKEN_SUB = intern("sub") +TOKEN_TILDE = intern("tilde") +TOKEN_WHITESPACE = intern("whitespace") +TOKEN_FLOAT = intern("float") +TOKEN_INTEGER = intern("integer") +TOKEN_NAME = intern("name") +TOKEN_STRING = intern("string") +TOKEN_OPERATOR = intern("operator") +TOKEN_BLOCK_BEGIN = intern("block_begin") +TOKEN_BLOCK_END = intern("block_end") +TOKEN_VARIABLE_BEGIN = intern("variable_begin") +TOKEN_VARIABLE_END = intern("variable_end") +TOKEN_RAW_BEGIN = intern("raw_begin") +TOKEN_RAW_END = intern("raw_end") +TOKEN_COMMENT_BEGIN = intern("comment_begin") +TOKEN_COMMENT_END = intern("comment_end") +TOKEN_COMMENT = intern("comment") +TOKEN_LINESTATEMENT_BEGIN = intern("linestatement_begin") +TOKEN_LINESTATEMENT_END = intern("linestatement_end") +TOKEN_LINECOMMENT_BEGIN = intern("linecomment_begin") +TOKEN_LINECOMMENT_END = intern("linecomment_end") +TOKEN_LINECOMMENT = intern("linecomment") +TOKEN_DATA = intern("data") +TOKEN_INITIAL = intern("initial") +TOKEN_EOF = intern("eof") # bind operators to token types operators = { - "+": TOKEN_ADD, - "-": TOKEN_SUB, - "/": TOKEN_DIV, - "//": TOKEN_FLOORDIV, - "*": TOKEN_MUL, - "%": TOKEN_MOD, - "**": TOKEN_POW, - "~": TOKEN_TILDE, - "[": TOKEN_LBRACKET, - "]": TOKEN_RBRACKET, - "(": TOKEN_LPAREN, - ")": TOKEN_RPAREN, - "{": TOKEN_LBRACE, - "}": TOKEN_RBRACE, - "==": TOKEN_EQ, - "!=": TOKEN_NE, - ">": TOKEN_GT, - ">=": TOKEN_GTEQ, - "<": TOKEN_LT, - "<=": TOKEN_LTEQ, - "=": TOKEN_ASSIGN, - ".": TOKEN_DOT, - ":": TOKEN_COLON, - "|": TOKEN_PIPE, - ",": TOKEN_COMMA, - ";": TOKEN_SEMICOLON, + "+": TOKEN_ADD, + "-": TOKEN_SUB, + "/": TOKEN_DIV, + "//": TOKEN_FLOORDIV, + "*": TOKEN_MUL, + "%": TOKEN_MOD, + "**": TOKEN_POW, + "~": TOKEN_TILDE, + "[": TOKEN_LBRACKET, + "]": TOKEN_RBRACKET, + "(": TOKEN_LPAREN, + ")": TOKEN_RPAREN, + "{": TOKEN_LBRACE, + "}": TOKEN_RBRACE, + "==": TOKEN_EQ, + "!=": TOKEN_NE, + ">": TOKEN_GT, + ">=": TOKEN_GTEQ, + "<": TOKEN_LT, + "<=": TOKEN_LTEQ, + "=": TOKEN_ASSIGN, + ".": TOKEN_DOT, + ":": TOKEN_COLON, + "|": TOKEN_PIPE, + ",": TOKEN_COMMA, + ";": TOKEN_SEMICOLON, } -reverse_operators = {v: k for k, v in operators.items()} -assert len(operators) == len(reverse_operators), "operators dropped" -operator_re = re.compile( - f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})" -) - -ignored_tokens = frozenset( - [ - TOKEN_COMMENT_BEGIN, - TOKEN_COMMENT, - TOKEN_COMMENT_END, - TOKEN_WHITESPACE, - TOKEN_LINECOMMENT_BEGIN, - TOKEN_LINECOMMENT_END, - TOKEN_LINECOMMENT, - ] -) -ignore_if_empty = frozenset( - [TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT, TOKEN_LINECOMMENT] -) - - -def _describe_token_type(token_type: str) -> str: +reverse_operators = {v: k for k, v in operators.items()} +assert len(operators) == len(reverse_operators), "operators dropped" +operator_re = re.compile( + f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})" +) + +ignored_tokens = frozenset( + [ + TOKEN_COMMENT_BEGIN, + TOKEN_COMMENT, + TOKEN_COMMENT_END, + TOKEN_WHITESPACE, + TOKEN_LINECOMMENT_BEGIN, + TOKEN_LINECOMMENT_END, + TOKEN_LINECOMMENT, + ] +) +ignore_if_empty = frozenset( + [TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT, TOKEN_LINECOMMENT] +) + + +def _describe_token_type(token_type: str) -> str: if token_type in reverse_operators: return reverse_operators[token_type] - + return { - TOKEN_COMMENT_BEGIN: "begin of comment", - TOKEN_COMMENT_END: "end of comment", - TOKEN_COMMENT: "comment", - TOKEN_LINECOMMENT: "comment", - TOKEN_BLOCK_BEGIN: "begin of statement block", - TOKEN_BLOCK_END: "end of statement block", - TOKEN_VARIABLE_BEGIN: "begin of print statement", - TOKEN_VARIABLE_END: "end of print statement", - TOKEN_LINESTATEMENT_BEGIN: "begin of line statement", - TOKEN_LINESTATEMENT_END: "end of line statement", - TOKEN_DATA: "template data / text", - TOKEN_EOF: "end of template", + TOKEN_COMMENT_BEGIN: "begin of comment", + TOKEN_COMMENT_END: "end of comment", + TOKEN_COMMENT: "comment", + TOKEN_LINECOMMENT: "comment", + TOKEN_BLOCK_BEGIN: "begin of statement block", + TOKEN_BLOCK_END: "end of statement block", + TOKEN_VARIABLE_BEGIN: "begin of print statement", + TOKEN_VARIABLE_END: "end of print statement", + TOKEN_LINESTATEMENT_BEGIN: "begin of line statement", + TOKEN_LINESTATEMENT_END: "end of line statement", + TOKEN_DATA: "template data / text", + TOKEN_EOF: "end of template", }.get(token_type, token_type) -def describe_token(token: "Token") -> str: +def describe_token(token: "Token") -> str: """Returns a description of the token.""" - if token.type == TOKEN_NAME: + if token.type == TOKEN_NAME: return token.value - + return _describe_token_type(token.type) -def describe_token_expr(expr: str) -> str: +def describe_token_expr(expr: str) -> str: """Like `describe_token` but for token expressions.""" - if ":" in expr: - type, value = expr.split(":", 1) - - if type == TOKEN_NAME: + if ":" in expr: + type, value = expr.split(":", 1) + + if type == TOKEN_NAME: return value else: type = expr - + return _describe_token_type(type) -def count_newlines(value: str) -> int: +def count_newlines(value: str) -> int: """Count the number of newline characters in the string. This is useful for extensions that filter a stream. """ return len(newline_re.findall(value)) -def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]: +def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]: """Compiles all the rules from the environment into a list of rules.""" e = re.escape rules = [ - ( - len(environment.comment_start_string), - TOKEN_COMMENT_BEGIN, - e(environment.comment_start_string), - ), - ( - len(environment.block_start_string), - TOKEN_BLOCK_BEGIN, - e(environment.block_start_string), - ), - ( - len(environment.variable_start_string), - TOKEN_VARIABLE_BEGIN, - e(environment.variable_start_string), - ), + ( + len(environment.comment_start_string), + TOKEN_COMMENT_BEGIN, + e(environment.comment_start_string), + ), + ( + len(environment.block_start_string), + TOKEN_BLOCK_BEGIN, + e(environment.block_start_string), + ), + ( + len(environment.variable_start_string), + TOKEN_VARIABLE_BEGIN, + e(environment.variable_start_string), + ), ] if environment.line_statement_prefix is not None: - rules.append( - ( - len(environment.line_statement_prefix), - TOKEN_LINESTATEMENT_BEGIN, - r"^[ \t\v]*" + e(environment.line_statement_prefix), - ) - ) + rules.append( + ( + len(environment.line_statement_prefix), + TOKEN_LINESTATEMENT_BEGIN, + r"^[ \t\v]*" + e(environment.line_statement_prefix), + ) + ) if environment.line_comment_prefix is not None: - rules.append( - ( - len(environment.line_comment_prefix), - TOKEN_LINECOMMENT_BEGIN, - r"(?:^|(?<=\S))[^\S\r\n]*" + e(environment.line_comment_prefix), - ) - ) + rules.append( + ( + len(environment.line_comment_prefix), + TOKEN_LINECOMMENT_BEGIN, + r"(?:^|(?<=\S))[^\S\r\n]*" + e(environment.line_comment_prefix), + ) + ) return [x[1:] for x in sorted(rules, reverse=True)] -class Failure: +class Failure: """Class that raises a `TemplateSyntaxError` if called. Used by the `Lexer` to specify known errors. """ - def __init__( - self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError - ) -> None: + def __init__( + self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError + ) -> None: self.message = message self.error_class = cls - def __call__(self, lineno: int, filename: str) -> "te.NoReturn": + def __call__(self, lineno: int, filename: str) -> "te.NoReturn": raise self.error_class(self.message, lineno, filename) -class Token(t.NamedTuple): - lineno: int - type: str - value: str - - def __str__(self) -> str: - return describe_token(self) +class Token(t.NamedTuple): + lineno: int + type: str + value: str + + def __str__(self) -> str: + return describe_token(self) - def test(self, expr: str) -> bool: + def test(self, expr: str) -> bool: """Test a token against a token expression. This can either be a token type or ``'token_type:token_value'``. This can only test against string values and types. @@ -281,75 +281,75 @@ class Token(t.NamedTuple): # passed an iterable of not interned strings. if self.type == expr: return True - - if ":" in expr: - return expr.split(":", 1) == [self.type, self.value] - + + if ":" in expr: + return expr.split(":", 1) == [self.type, self.value] + return False - def test_any(self, *iterable: str) -> bool: + def test_any(self, *iterable: str) -> bool: """Test against multiple token expressions.""" - return any(self.test(expr) for expr in iterable) + return any(self.test(expr) for expr in iterable) -class TokenStreamIterator: +class TokenStreamIterator: """The iterator for tokenstreams. Iterate over the stream until the eof token is reached. """ - def __init__(self, stream: "TokenStream") -> None: + def __init__(self, stream: "TokenStream") -> None: self.stream = stream - def __iter__(self) -> "TokenStreamIterator": + def __iter__(self) -> "TokenStreamIterator": return self - def __next__(self) -> Token: + def __next__(self) -> Token: token = self.stream.current - + if token.type is TOKEN_EOF: self.stream.close() - raise StopIteration - + raise StopIteration + next(self.stream) return token -class TokenStream: +class TokenStream: """A token stream is an iterable that yields :class:`Token`\\s. The parser however does not iterate over it but calls :meth:`next` to go one token ahead. The current active token is stored as :attr:`current`. """ - def __init__( - self, - generator: t.Iterable[Token], - name: t.Optional[str], - filename: t.Optional[str], - ): + def __init__( + self, + generator: t.Iterable[Token], + name: t.Optional[str], + filename: t.Optional[str], + ): self._iter = iter(generator) - self._pushed: "te.Deque[Token]" = deque() + self._pushed: "te.Deque[Token]" = deque() self.name = name self.filename = filename self.closed = False - self.current = Token(1, TOKEN_INITIAL, "") + self.current = Token(1, TOKEN_INITIAL, "") next(self) - def __iter__(self) -> TokenStreamIterator: + def __iter__(self) -> TokenStreamIterator: return TokenStreamIterator(self) - def __bool__(self) -> bool: + def __bool__(self) -> bool: return bool(self._pushed) or self.current.type is not TOKEN_EOF + + @property + def eos(self) -> bool: + """Are we at the end of the stream?""" + return not self - @property - def eos(self) -> bool: - """Are we at the end of the stream?""" - return not self - - def push(self, token: Token) -> None: + def push(self, token: Token) -> None: """Push a token back to the stream.""" self._pushed.append(token) - def look(self) -> Token: + def look(self) -> Token: """Look at the next token.""" old_token = next(self) result = self.current @@ -357,31 +357,31 @@ class TokenStream: self.current = old_token return result - def skip(self, n: int = 1) -> None: + def skip(self, n: int = 1) -> None: """Got n tokens ahead.""" - for _ in range(n): + for _ in range(n): next(self) - def next_if(self, expr: str) -> t.Optional[Token]: + def next_if(self, expr: str) -> t.Optional[Token]: """Perform the token test and return the token if it matched. Otherwise the return value is `None`. """ if self.current.test(expr): return next(self) - return None - - def skip_if(self, expr: str) -> bool: + return None + + def skip_if(self, expr: str) -> bool: """Like :meth:`next_if` but only returns `True` or `False`.""" return self.next_if(expr) is not None - def __next__(self) -> Token: + def __next__(self) -> Token: """Go one token ahead and return the old one. Use the built-in :func:`next` instead of calling this directly. """ rv = self.current - + if self._pushed: self.current = self._pushed.popleft() elif self.current.type is not TOKEN_EOF: @@ -389,84 +389,84 @@ class TokenStream: self.current = next(self._iter) except StopIteration: self.close() - + return rv - def close(self) -> None: + def close(self) -> None: """Close the stream.""" - self.current = Token(self.current.lineno, TOKEN_EOF, "") - self._iter = iter(()) + self.current = Token(self.current.lineno, TOKEN_EOF, "") + self._iter = iter(()) self.closed = True - def expect(self, expr: str) -> Token: + def expect(self, expr: str) -> Token: """Expect a given token type and return it. This accepts the same argument as :meth:`jinja2.lexer.Token.test`. """ if not self.current.test(expr): expr = describe_token_expr(expr) - + if self.current.type is TOKEN_EOF: - raise TemplateSyntaxError( - f"unexpected end of template, expected {expr!r}.", - self.current.lineno, - self.name, - self.filename, - ) - - raise TemplateSyntaxError( - f"expected token {expr!r}, got {describe_token(self.current)!r}", - self.current.lineno, - self.name, - self.filename, - ) - - return next(self) - - -def get_lexer(environment: "Environment") -> "Lexer": + raise TemplateSyntaxError( + f"unexpected end of template, expected {expr!r}.", + self.current.lineno, + self.name, + self.filename, + ) + + raise TemplateSyntaxError( + f"expected token {expr!r}, got {describe_token(self.current)!r}", + self.current.lineno, + self.name, + self.filename, + ) + + return next(self) + + +def get_lexer(environment: "Environment") -> "Lexer": """Return a lexer which is probably cached.""" - key = ( - environment.block_start_string, - environment.block_end_string, - environment.variable_start_string, - environment.variable_end_string, - environment.comment_start_string, - environment.comment_end_string, - environment.line_statement_prefix, - environment.line_comment_prefix, - environment.trim_blocks, - environment.lstrip_blocks, - environment.newline_sequence, - environment.keep_trailing_newline, - ) + key = ( + environment.block_start_string, + environment.block_end_string, + environment.variable_start_string, + environment.variable_end_string, + environment.comment_start_string, + environment.comment_end_string, + environment.line_statement_prefix, + environment.line_comment_prefix, + environment.trim_blocks, + environment.lstrip_blocks, + environment.newline_sequence, + environment.keep_trailing_newline, + ) lexer = _lexer_cache.get(key) - + if lexer is None: - _lexer_cache[key] = lexer = Lexer(environment) - + _lexer_cache[key] = lexer = Lexer(environment) + return lexer -class OptionalLStrip(tuple): - """A special tuple for marking a point in the state that can have - lstrip applied. - """ - - __slots__ = () - - # Even though it looks like a no-op, creating instances fails - # without this. - def __new__(cls, *members, **kwargs): # type: ignore - return super().__new__(cls, members) - - -class _Rule(t.NamedTuple): - pattern: t.Pattern[str] - tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]] - command: t.Optional[str] - - -class Lexer: +class OptionalLStrip(tuple): + """A special tuple for marking a point in the state that can have + lstrip applied. + """ + + __slots__ = () + + # Even though it looks like a no-op, creating instances fails + # without this. + def __new__(cls, *members, **kwargs): # type: ignore + return super().__new__(cls, members) + + +class _Rule(t.NamedTuple): + pattern: t.Pattern[str] + tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]] + command: t.Optional[str] + + +class Lexer: """Class that implements a lexer for a given environment. Automatically created by the environment class, usually you don't have to do that. @@ -474,21 +474,21 @@ class Lexer: Multiple environments can share the same lexer. """ - def __init__(self, environment: "Environment") -> None: + def __init__(self, environment: "Environment") -> None: # shortcuts e = re.escape - def c(x: str) -> t.Pattern[str]: - return re.compile(x, re.M | re.S) - + def c(x: str) -> t.Pattern[str]: + return re.compile(x, re.M | re.S) + # lexing rules for tags - tag_rules: t.List[_Rule] = [ - _Rule(whitespace_re, TOKEN_WHITESPACE, None), - _Rule(float_re, TOKEN_FLOAT, None), - _Rule(integer_re, TOKEN_INTEGER, None), - _Rule(name_re, TOKEN_NAME, None), - _Rule(string_re, TOKEN_STRING, None), - _Rule(operator_re, TOKEN_OPERATOR, None), + tag_rules: t.List[_Rule] = [ + _Rule(whitespace_re, TOKEN_WHITESPACE, None), + _Rule(float_re, TOKEN_FLOAT, None), + _Rule(integer_re, TOKEN_INTEGER, None), + _Rule(name_re, TOKEN_NAME, None), + _Rule(string_re, TOKEN_STRING, None), + _Rule(operator_re, TOKEN_OPERATOR, None), ] # assemble the root lexing rule. because "|" is ungreedy @@ -499,213 +499,213 @@ class Lexer: # is required. root_tag_rules = compile_rules(environment) - block_start_re = e(environment.block_start_string) - block_end_re = e(environment.block_end_string) - comment_end_re = e(environment.comment_end_string) - variable_end_re = e(environment.variable_end_string) - + block_start_re = e(environment.block_start_string) + block_end_re = e(environment.block_end_string) + comment_end_re = e(environment.comment_end_string) + variable_end_re = e(environment.variable_end_string) + # block suffix if trimming is enabled - block_suffix_re = "\\n?" if environment.trim_blocks else "" + block_suffix_re = "\\n?" if environment.trim_blocks else "" - # If lstrip is enabled, it should not be applied if there is any - # non-whitespace between the newline and block. - self.lstrip_unless_re = c(r"[^ \t]") if environment.lstrip_blocks else None + # 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.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}))" - ) - root_parts_re = "|".join( - [root_raw_re] + [fr"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules] - ) - + root_raw_re = ( + fr"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*" + fr"(?:\-{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] + ) + # global lexing rules - self.rules: t.Dict[str, t.List[_Rule]] = { - "root": [ + self.rules: t.Dict[str, t.List[_Rule]] = { + "root": [ # directives - _Rule( - c(fr"(.*?)(?:{root_parts_re})"), - OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore - "#bygroup", - ), + _Rule( + c(fr"(.*?)(?:{root_parts_re})"), + OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore + "#bygroup", + ), # data - _Rule(c(".+"), TOKEN_DATA, None), + _Rule(c(".+"), TOKEN_DATA, None), ], # comments TOKEN_COMMENT_BEGIN: [ - _Rule( - c( - fr"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*" - fr"|{comment_end_re}{block_suffix_re}))" - ), - (TOKEN_COMMENT, TOKEN_COMMENT_END), - "#pop", - ), - _Rule(c(r"(.)"), (Failure("Missing end of comment tag"),), None), + _Rule( + c( + fr"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*" + fr"|{comment_end_re}{block_suffix_re}))" + ), + (TOKEN_COMMENT, TOKEN_COMMENT_END), + "#pop", + ), + _Rule(c(r"(.)"), (Failure("Missing end of comment tag"),), None), ], # blocks TOKEN_BLOCK_BEGIN: [ - _Rule( - c( - fr"(?:\+{block_end_re}|\-{block_end_re}\s*" - fr"|{block_end_re}{block_suffix_re})" - ), - TOKEN_BLOCK_END, - "#pop", - ), - ] - + tag_rules, + _Rule( + c( + fr"(?:\+{block_end_re}|\-{block_end_re}\s*" + fr"|{block_end_re}{block_suffix_re})" + ), + TOKEN_BLOCK_END, + "#pop", + ), + ] + + tag_rules, # variables TOKEN_VARIABLE_BEGIN: [ - _Rule( - c(fr"\-{variable_end_re}\s*|{variable_end_re}"), - TOKEN_VARIABLE_END, - "#pop", - ) - ] - + tag_rules, + _Rule( + c(fr"\-{variable_end_re}\s*|{variable_end_re}"), + TOKEN_VARIABLE_END, + "#pop", + ) + ] + + tag_rules, # raw block 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}))" - ), - OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore - "#pop", - ), - _Rule(c(r"(.)"), (Failure("Missing end of raw directive"),), None), + _Rule( + c( + fr"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*" + fr"(?:\+{block_end_re}|\-{block_end_re}\s*" + fr"|{block_end_re}{block_suffix_re}))" + ), + OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore + "#pop", + ), + _Rule(c(r"(.)"), (Failure("Missing end of raw directive"),), None), ], # line statements TOKEN_LINESTATEMENT_BEGIN: [ - _Rule(c(r"\s*(\n|$)"), TOKEN_LINESTATEMENT_END, "#pop") - ] - + tag_rules, + _Rule(c(r"\s*(\n|$)"), TOKEN_LINESTATEMENT_END, "#pop") + ] + + tag_rules, # line comments TOKEN_LINECOMMENT_BEGIN: [ - _Rule( - c(r"(.*?)()(?=\n|$)"), - (TOKEN_LINECOMMENT, TOKEN_LINECOMMENT_END), - "#pop", - ) - ], + _Rule( + c(r"(.*?)()(?=\n|$)"), + (TOKEN_LINECOMMENT, TOKEN_LINECOMMENT_END), + "#pop", + ) + ], } - def _normalize_newlines(self, value: str) -> str: - """Replace all newlines with the configured sequence in strings - and template data. - """ + def _normalize_newlines(self, value: str) -> str: + """Replace all newlines with the configured sequence in strings + and template data. + """ return newline_re.sub(self.newline_sequence, value) - def tokenize( - self, - source: str, - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - state: t.Optional[str] = None, - ) -> TokenStream: - """Calls tokeniter + tokenize and wraps it in a token stream.""" + def tokenize( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + state: t.Optional[str] = None, + ) -> TokenStream: + """Calls tokeniter + tokenize and wraps it in a token stream.""" stream = self.tokeniter(source, name, filename, state) return TokenStream(self.wrap(stream, name, filename), name, filename) - def wrap( - self, - stream: t.Iterable[t.Tuple[int, str, str]], - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - ) -> t.Iterator[Token]: + def wrap( + self, + stream: t.Iterable[t.Tuple[int, str, str]], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> t.Iterator[Token]: """This is called with the stream as returned by `tokenize` and wraps every token in a :class:`Token` and converts the value. """ - for lineno, token, value_str in stream: + for lineno, token, value_str in stream: if token in ignored_tokens: continue - - value: t.Any = value_str - - if token == TOKEN_LINESTATEMENT_BEGIN: - token = TOKEN_BLOCK_BEGIN - elif token == TOKEN_LINESTATEMENT_END: - token = TOKEN_BLOCK_END + + value: t.Any = value_str + + if token == TOKEN_LINESTATEMENT_BEGIN: + token = TOKEN_BLOCK_BEGIN + elif token == TOKEN_LINESTATEMENT_END: + token = TOKEN_BLOCK_END # we are not interested in those tokens in the parser - elif token in (TOKEN_RAW_BEGIN, TOKEN_RAW_END): + elif token in (TOKEN_RAW_BEGIN, TOKEN_RAW_END): continue - elif token == TOKEN_DATA: - value = self._normalize_newlines(value_str) - elif token == "keyword": - token = value_str - elif token == TOKEN_NAME: - value = value_str - - if not value.isidentifier(): + elif token == TOKEN_DATA: + value = self._normalize_newlines(value_str) + elif token == "keyword": + token = value_str + elif token == TOKEN_NAME: + value = value_str + + if not value.isidentifier(): raise TemplateSyntaxError( - "Invalid character in identifier", lineno, name, filename - ) - elif token == TOKEN_STRING: + "Invalid character in identifier", lineno, name, filename + ) + elif token == TOKEN_STRING: # try to unescape string try: - value = ( - self._normalize_newlines(value_str[1:-1]) - .encode("ascii", "backslashreplace") - .decode("unicode-escape") - ) + value = ( + self._normalize_newlines(value_str[1:-1]) + .encode("ascii", "backslashreplace") + .decode("unicode-escape") + ) except Exception as e: - msg = str(e).split(":")[-1].strip() - raise TemplateSyntaxError(msg, lineno, name, filename) from e - elif token == TOKEN_INTEGER: - value = int(value_str.replace("_", ""), 0) - elif token == TOKEN_FLOAT: - # remove all "_" first to support more Python versions - value = literal_eval(value_str.replace("_", "")) - elif token == TOKEN_OPERATOR: - token = operators[value_str] - + msg = str(e).split(":")[-1].strip() + raise TemplateSyntaxError(msg, lineno, name, filename) from e + elif token == TOKEN_INTEGER: + value = int(value_str.replace("_", ""), 0) + elif token == TOKEN_FLOAT: + # remove all "_" first to support more Python versions + value = literal_eval(value_str.replace("_", "")) + elif token == TOKEN_OPERATOR: + token = operators[value_str] + yield Token(lineno, token, value) - def tokeniter( - self, - source: str, - name: t.Optional[str], - filename: t.Optional[str] = None, - state: t.Optional[str] = None, - ) -> t.Iterator[t.Tuple[int, str, str]]: + def tokeniter( + self, + source: str, + name: t.Optional[str], + filename: t.Optional[str] = None, + state: t.Optional[str] = None, + ) -> t.Iterator[t.Tuple[int, str, str]]: """This method tokenizes the text and returns the tokens in a - generator. Use this method if you just want to tokenize a template. - - .. versionchanged:: 3.0 - Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line - breaks. + generator. Use this method if you just want to tokenize a template. + + .. versionchanged:: 3.0 + Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line + breaks. """ - lines = newline_re.split(source)[::2] - - if not self.keep_trailing_newline and lines[-1] == "": - del lines[-1] - - source = "\n".join(lines) + lines = newline_re.split(source)[::2] + + if not self.keep_trailing_newline and lines[-1] == "": + del lines[-1] + + source = "\n".join(lines) pos = 0 lineno = 1 - stack = ["root"] - - if state is not None and state != "root": - assert state in ("variable", "block"), "invalid state" - stack.append(state + "_begin") - + stack = ["root"] + + if state is not None and state != "root": + assert state in ("variable", "block"), "invalid state" + stack.append(state + "_begin") + 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 + balancing_stack: t.List[str] = [] + lstrip_unless_re = self.lstrip_unless_re + newlines_stripped = 0 + line_starting = True - while True: + while True: # tokenizer loop for regex, tokens, new_state in statetokens: m = regex.match(source, pos) - + # if no match we try again with the next rule if m is None: continue @@ -714,48 +714,48 @@ class Lexer: # are balanced. continue parsing with the lower rule which # is the operator rule. do this only if the end tags look # like operators - if balancing_stack and tokens in ( - TOKEN_VARIABLE_END, - TOKEN_BLOCK_END, - TOKEN_LINESTATEMENT_END, - ): + if balancing_stack and tokens in ( + TOKEN_VARIABLE_END, + TOKEN_BLOCK_END, + TOKEN_LINESTATEMENT_END, + ): continue # tuples support more options if isinstance(tokens, tuple): - groups = m.groups() - - if isinstance(tokens, OptionalLStrip): - # Rule supports lstrip. Match will look like - # text, block type, whitespace control, type, control, ... - text = groups[0] - # Skipping the text and first type, every other group is the - # whitespace control for each type. One of the groups will be - # -, +, or empty string instead of None. - strip_sign = next(g for g in groups[2::2] if g is not None) - - if strip_sign == "-": - # Strip all whitespace between the text and the tag. - stripped = text.rstrip() - newlines_stripped = text[len(stripped) :].count("\n") - groups = [stripped, *groups[1:]] - elif ( - # Not marked for preserving whitespace. - strip_sign != "+" - # lstrip is enabled. - and lstrip_unless_re is not None - # Not a variable expression. - and not m.groupdict().get(TOKEN_VARIABLE_BEGIN) - ): - # The start of text between the last newline and the tag. - l_pos = text.rfind("\n") + 1 - - 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): - groups = [text[:l_pos], *groups[1:]] - + groups = m.groups() + + if isinstance(tokens, OptionalLStrip): + # Rule supports lstrip. Match will look like + # text, block type, whitespace control, type, control, ... + text = groups[0] + # Skipping the text and first type, every other group is the + # whitespace control for each type. One of the groups will be + # -, +, or empty string instead of None. + strip_sign = next(g for g in groups[2::2] if g is not None) + + if strip_sign == "-": + # Strip all whitespace between the text and the tag. + stripped = text.rstrip() + newlines_stripped = text[len(stripped) :].count("\n") + groups = [stripped, *groups[1:]] + elif ( + # Not marked for preserving whitespace. + strip_sign != "+" + # lstrip is enabled. + and lstrip_unless_re is not None + # Not a variable expression. + and not m.groupdict().get(TOKEN_VARIABLE_BEGIN) + ): + # The start of text between the last newline and the tag. + l_pos = text.rfind("\n") + 1 + + 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): + groups = [text[:l_pos], *groups[1:]] + for idx, token in enumerate(tokens): # failure group if token.__class__ is Failure: @@ -763,62 +763,62 @@ class Lexer: # bygroup is a bit more complex, in that case we # yield for the current token the first named # group that matched - elif token == "#bygroup": - for key, value in m.groupdict().items(): + elif token == "#bygroup": + for key, value in m.groupdict().items(): if value is not None: yield lineno, key, value - lineno += value.count("\n") + lineno += value.count("\n") break else: - raise RuntimeError( - f"{regex!r} wanted to resolve the token dynamically" - " but no group matched" - ) + raise RuntimeError( + f"{regex!r} wanted to resolve the token dynamically" + " but no group matched" + ) # normal group else: - data = groups[idx] - + data = groups[idx] + if data or token not in ignore_if_empty: yield lineno, token, data - - lineno += data.count("\n") + newlines_stripped - newlines_stripped = 0 + + lineno += data.count("\n") + newlines_stripped + newlines_stripped = 0 # strings as token just are yielded as it. else: data = m.group() - + # update brace/parentheses balance - if tokens == TOKEN_OPERATOR: - if data == "{": - balancing_stack.append("}") - elif data == "(": - balancing_stack.append(")") - elif data == "[": - balancing_stack.append("]") - elif data in ("}", ")", "]"): + if tokens == TOKEN_OPERATOR: + if data == "{": + balancing_stack.append("}") + elif data == "(": + balancing_stack.append(")") + elif data == "[": + balancing_stack.append("]") + elif data in ("}", ")", "]"): if not balancing_stack: - raise TemplateSyntaxError( - f"unexpected '{data}'", lineno, name, filename - ) - + raise TemplateSyntaxError( + f"unexpected '{data}'", lineno, name, filename + ) + expected_op = balancing_stack.pop() - + if expected_op != data: - raise TemplateSyntaxError( - f"unexpected '{data}', expected '{expected_op}'", - lineno, - name, - filename, - ) - + raise TemplateSyntaxError( + f"unexpected '{data}', expected '{expected_op}'", + lineno, + name, + filename, + ) + # yield items if data or tokens not in ignore_if_empty: yield lineno, tokens, data + + lineno += data.count("\n") - lineno += data.count("\n") - - line_starting = m.group()[-1:] == "\n" + line_starting = m.group()[-1:] == "\n" # fetch new position into new variable so that we can check # if there is a internal parsing error which would result # in an infinite loop @@ -827,32 +827,32 @@ class Lexer: # handle state changes if new_state is not None: # remove the uppermost state - if new_state == "#pop": + if new_state == "#pop": stack.pop() # resolve the new state by group checking - elif new_state == "#bygroup": - for key, value in m.groupdict().items(): + elif new_state == "#bygroup": + for key, value in m.groupdict().items(): if value is not None: stack.append(key) break else: - raise RuntimeError( - f"{regex!r} wanted to resolve the new state dynamically" - f" but no group matched" - ) + raise RuntimeError( + f"{regex!r} wanted to resolve the new state dynamically" + f" but no group matched" + ) # direct state name given else: stack.append(new_state) - + statetokens = self.rules[stack[-1]] # we are still at the same position and no stack change. # this means a loop without break condition, avoid that and # raise error elif pos2 == pos: - raise RuntimeError( - f"{regex!r} yielded empty string without stack change" - ) - + raise RuntimeError( + f"{regex!r} yielded empty string without stack change" + ) + # publish new function and start again pos = pos2 break @@ -862,8 +862,8 @@ class Lexer: # end of text if pos >= source_length: return - + # something went wrong - raise TemplateSyntaxError( - f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename - ) + raise TemplateSyntaxError( + f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename + ) diff --git a/contrib/python/Jinja2/py3/jinja2/loaders.py b/contrib/python/Jinja2/py3/jinja2/loaders.py index cc58eca221..f7e2cadc3e 100644 --- a/contrib/python/Jinja2/py3/jinja2/loaders.py +++ b/contrib/python/Jinja2/py3/jinja2/loaders.py @@ -1,47 +1,47 @@ -"""API and implementations for loading templates from different data -sources. +"""API and implementations for loading templates from different data +sources. """ -import importlib.util +import importlib.util import os import sys -import pkgutil -import typing as t +import pkgutil +import typing as t import weakref -import zipimport -from collections import abc -from hashlib import sha1 -from importlib import import_module +import zipimport +from collections import abc +from hashlib import sha1 +from importlib import import_module from types import ModuleType -from .exceptions import TemplateNotFound -from .utils import internalcode -from .utils import open_if_exists - -if t.TYPE_CHECKING: - from .environment import Environment - from .environment import Template - -import __res as arcadia_res - - -def split_template_path(template: str) -> t.List[str]: +from .exceptions import TemplateNotFound +from .utils import internalcode +from .utils import open_if_exists + +if t.TYPE_CHECKING: + from .environment import Environment + from .environment import Template + +import __res as arcadia_res + + +def split_template_path(template: str) -> t.List[str]: """Split a path into segments and perform a sanity check. If it detects '..' in the path it will raise a `TemplateNotFound` error. """ pieces = [] - for piece in template.split("/"): - if ( - os.path.sep in piece - or (os.path.altsep and os.path.altsep in piece) - or piece == os.path.pardir - ): + for piece in template.split("/"): + if ( + os.path.sep in piece + or (os.path.altsep and os.path.altsep in piece) + or piece == os.path.pardir + ): raise TemplateNotFound(template) - elif piece and piece != ".": + elif piece and piece != ".": pieces.append(piece) return pieces -class BaseLoader: +class BaseLoader: """Baseclass for all loaders. Subclass this and override `get_source` to implement a custom loading mechanism. The environment provides a `get_template` method that calls the loader's `load` method to get the @@ -63,8 +63,8 @@ class BaseLoader: if not exists(path): raise TemplateNotFound(template) mtime = getmtime(path) - with open(path) as f: - source = f.read() + with open(path) as f: + source = f.read() return source, path, lambda: mtime == getmtime(path) """ @@ -74,18 +74,18 @@ class BaseLoader: #: .. versionadded:: 2.4 has_source_access = True - def get_source( - self, environment: "Environment", template: str - ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: + def get_source( + self, environment: "Environment", template: str + ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: """Get the template source, filename and reload helper for a template. It's passed the environment and template name and has to return a tuple in the form ``(source, filename, uptodate)`` or raise a `TemplateNotFound` error if it can't locate the template. The source part of the returned tuple must be the source of the - template as a string. The filename should be the name of the - file on the filesystem if it was loaded from there, otherwise - ``None``. The filename is used by Python for the tracebacks + template as a string. The filename should be the name of the + file on the filesystem if it was loaded from there, otherwise + ``None``. The filename is used by Python for the tracebacks if no loader extension is used. The last item in the tuple is the `uptodate` function. If auto @@ -95,24 +95,24 @@ class BaseLoader: the template will be reloaded. """ if not self.has_source_access: - raise RuntimeError( - f"{type(self).__name__} cannot provide access to the source" - ) + raise RuntimeError( + f"{type(self).__name__} cannot provide access to the source" + ) raise TemplateNotFound(template) - def list_templates(self) -> t.List[str]: + def list_templates(self) -> t.List[str]: """Iterates over all templates. If the loader does not support that it should raise a :exc:`TypeError` which is the default behavior. """ - raise TypeError("this loader cannot iterate over all templates") + raise TypeError("this loader cannot iterate over all templates") @internalcode - def load( - self, - environment: "Environment", - name: str, - globals: t.Optional[t.MutableMapping[str, t.Any]] = None, - ) -> "Template": + def load( + self, + environment: "Environment", + name: str, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, + ) -> "Template": """Loads a template. This method looks up the template in the cache or loads one by calling :meth:`get_source`. Subclasses should not override this method as loaders working on collections of other @@ -146,57 +146,57 @@ class BaseLoader: bucket.code = code bcc.set_bucket(bucket) - return environment.template_class.from_code( - environment, code, globals, uptodate - ) + return environment.template_class.from_code( + environment, code, globals, uptodate + ) class FileSystemLoader(BaseLoader): - """Load templates from a directory in the file system. - - The path can be relative or absolute. Relative paths are relative to - the current working directory. - - .. code-block:: python + """Load templates from a directory in the file system. - loader = FileSystemLoader("templates") + The path can be relative or absolute. Relative paths are relative to + the current working directory. - A list of paths can be given. The directories will be searched in - order, stopping at the first matching template. + .. code-block:: python - .. code-block:: python + loader = FileSystemLoader("templates") - loader = FileSystemLoader(["/override/templates", "/default/templates"]) + A list of paths can be given. The directories will be searched in + order, stopping at the first matching template. - :param searchpath: A path, or list of paths, to the directory that - contains the templates. - :param encoding: Use this encoding to read the text from template - files. - :param followlinks: Follow symbolic links in the path. + .. code-block:: python - .. versionchanged:: 2.8 - Added the ``followlinks`` parameter. + loader = FileSystemLoader(["/override/templates", "/default/templates"]) + + :param searchpath: A path, or list of paths, to the directory that + contains the templates. + :param encoding: Use this encoding to read the text from template + files. + :param followlinks: Follow symbolic links in the path. + + .. versionchanged:: 2.8 + Added the ``followlinks`` parameter. """ - def __init__( - self, - searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]], - encoding: str = "utf-8", - followlinks: bool = False, - ) -> None: - if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str): + def __init__( + self, + searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]], + encoding: str = "utf-8", + followlinks: bool = False, + ) -> None: + if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str): searchpath = [searchpath] - - self.searchpath = [os.fspath(p) for p in searchpath] + + self.searchpath = [os.fspath(p) for p in searchpath] self.encoding = encoding self.followlinks = followlinks - def get_source( - self, environment: "Environment", template: str - ) -> t.Tuple[str, str, t.Callable[[], bool]]: + def get_source( + self, environment: "Environment", template: str + ) -> t.Tuple[str, str, t.Callable[[], bool]]: pieces = split_template_path(template) for searchpath in self.searchpath: - filename = os.path.join(searchpath, *pieces) + filename = os.path.join(searchpath, *pieces) f = open_if_exists(filename) if f is None: continue @@ -205,29 +205,29 @@ class FileSystemLoader(BaseLoader): finally: f.close() - mtime = os.path.getmtime(filename) + mtime = os.path.getmtime(filename) - def uptodate() -> bool: + def uptodate() -> bool: try: - return os.path.getmtime(filename) == mtime + return os.path.getmtime(filename) == mtime except OSError: return False - + return contents, filename, uptodate raise TemplateNotFound(template) - def list_templates(self) -> t.List[str]: + def list_templates(self) -> t.List[str]: found = set() for searchpath in self.searchpath: walk_dir = os.walk(searchpath, followlinks=self.followlinks) - for dirpath, _, filenames in walk_dir: + for dirpath, _, filenames in walk_dir: for filename in filenames: - template = ( - os.path.join(dirpath, filename)[len(searchpath) :] - .strip(os.path.sep) - .replace(os.path.sep, "/") - ) - if template[:2] == "./": + template = ( + os.path.join(dirpath, filename)[len(searchpath) :] + .strip(os.path.sep) + .replace(os.path.sep, "/") + ) + if template[:2] == "./": template = template[2:] if template not in found: found.add(template) @@ -235,74 +235,74 @@ class FileSystemLoader(BaseLoader): class PackageLoader(BaseLoader): - """Load templates from a directory in a Python package. - - :param package_name: Import name of the package that contains the - template directory. - :param package_path: Directory within the imported package that - contains the templates. - :param encoding: Encoding of template files. - - The following example looks up templates in the ``pages`` directory - within the ``project.ui`` package. - - .. code-block:: python - - loader = PackageLoader("project.ui", "pages") - - Only packages installed as directories (standard pip behavior) or - zip/egg files (less common) are supported. The Python API for - introspecting data in packages is too limited to support other - installation methods the way this loader requires. - - There is limited support for :pep:`420` namespace packages. The - template directory is assumed to only be in one namespace - contributor. Zip files contributing to a namespace are not - supported. - - .. versionchanged:: 3.0 - No longer uses ``setuptools`` as a dependency. - - .. versionchanged:: 3.0 - Limited PEP 420 namespace package support. + """Load templates from a directory in a Python package. + + :param package_name: Import name of the package that contains the + template directory. + :param package_path: Directory within the imported package that + contains the templates. + :param encoding: Encoding of template files. + + The following example looks up templates in the ``pages`` directory + within the ``project.ui`` package. + + .. code-block:: python + + loader = PackageLoader("project.ui", "pages") + + Only packages installed as directories (standard pip behavior) or + zip/egg files (less common) are supported. The Python API for + introspecting data in packages is too limited to support other + installation methods the way this loader requires. + + There is limited support for :pep:`420` namespace packages. The + template directory is assumed to only be in one namespace + contributor. Zip files contributing to a namespace are not + supported. + + .. versionchanged:: 3.0 + No longer uses ``setuptools`` as a dependency. + + .. versionchanged:: 3.0 + Limited PEP 420 namespace package support. """ - def __init__( - self, - package_name: str, - package_path: "str" = "templates", - encoding: str = "utf-8", - ) -> None: - package_path = os.path.normpath(package_path).rstrip(os.path.sep) - - # normpath preserves ".", which isn't valid in zip paths. - if package_path == os.path.curdir: - package_path = "" - elif package_path[:2] == os.path.curdir + os.path.sep: - package_path = package_path[2:] - - self.package_path = package_path - self.package_name = package_name + def __init__( + self, + package_name: str, + package_path: "str" = "templates", + encoding: str = "utf-8", + ) -> None: + package_path = os.path.normpath(package_path).rstrip(os.path.sep) + + # normpath preserves ".", which isn't valid in zip paths. + if package_path == os.path.curdir: + package_path = "" + elif package_path[:2] == os.path.curdir + os.path.sep: + package_path = package_path[2:] + + self.package_path = package_path + self.package_name = package_name self.encoding = encoding - # Make sure the package exists. This also makes namespace - # packages work, otherwise get_loader returns None. - package = import_module(package_name) - spec = importlib.util.find_spec(package_name) - assert spec is not None, "An import spec was not found for the package." - loader = spec.loader - assert loader is not None, "A loader was not found for the package." - self._loader = loader - self._archive = None - self._package = package - template_root = None - - 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) - elif hasattr(loader, "arcadia_source_finder"): - template_root = os.path.dirname(package.__file__) + # Make sure the package exists. This also makes namespace + # packages work, otherwise get_loader returns None. + package = import_module(package_name) + spec = importlib.util.find_spec(package_name) + assert spec is not None, "An import spec was not found for the package." + loader = spec.loader + assert loader is not None, "A loader was not found for the package." + self._loader = loader + self._archive = None + self._package = package + template_root = None + + 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) + elif hasattr(loader, "arcadia_source_finder"): + template_root = os.path.dirname(package.__file__) else: roots: t.List[str] = [] @@ -315,131 +315,131 @@ class PackageLoader(BaseLoader): roots.append(os.path.dirname(spec.origin)) for root in roots: - root = os.path.join(root, package_path) - - if os.path.isdir(root): - template_root = root - break - - if template_root is None: - raise ValueError( - f"The {package_name!r} package was not installed in a" - " way that PackageLoader understands." - ) - - self._template_root = template_root - - 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)) - up_to_date: t.Optional[t.Callable[[], bool]] - - if self._archive is None and hasattr(self, "_package"): - try: - source = pkgutil.get_data(self.package_name, os.path.join(self.package_path, *split_template_path(template))) - except OSError: - raise TemplateNotFound(template) - - def up_to_date() -> bool: - return True - - elif self._archive is None: - # Package is a directory. - if not os.path.isfile(p): - raise TemplateNotFound(template) - - with open(p, "rb") as f: - source = f.read() - - mtime = os.path.getmtime(p) - - def up_to_date() -> bool: - return os.path.isfile(p) and os.path.getmtime(p) == mtime - - else: - # Package is a zip file. - try: - source = self._loader.get_data(p) # type: ignore - except OSError as e: - raise TemplateNotFound(template) from e - - # Could use the zip's mtime for all template mtimes, but - # would need to safely reload the module if it's out of - # date, so just report it as always current. - up_to_date = None - - return source.decode(self.encoding), p, up_to_date - - def list_templates(self) -> t.List[str]: - results: t.List[str] = [] - - if self._archive is None and hasattr(self, "_package"): - prefix = os.path.join(self._template_root, self.package_path).encode() + b"/" - for name in arcadia_res.resfs_files(prefix): - results.append(name.removeprefix(prefix).decode()) - - elif self._archive is None: - # Package is a directory. - offset = len(self._template_root) - - for dirpath, _, filenames in os.walk(self._template_root): - dirpath = dirpath[offset:].lstrip(os.path.sep) - results.extend( - os.path.join(dirpath, name).replace(os.path.sep, "/") - for name in filenames - ) - else: - if not hasattr(self._loader, "_files"): - raise TypeError( - "This zip import does not have the required" - " metadata to list templates." - ) - - # Package is a zip file. - prefix = ( - self._template_root[len(self._archive) :].lstrip(os.path.sep) - + os.path.sep - ) - offset = len(prefix) - - for name in self._loader._files.keys(): # type: ignore - # Find names under the templates directory that aren't directories. - if name.startswith(prefix) and name[-1] != os.path.sep: - results.append(name[offset:].replace(os.path.sep, "/")) - + root = os.path.join(root, package_path) + + if os.path.isdir(root): + template_root = root + break + + if template_root is None: + raise ValueError( + f"The {package_name!r} package was not installed in a" + " way that PackageLoader understands." + ) + + self._template_root = template_root + + 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)) + up_to_date: t.Optional[t.Callable[[], bool]] + + if self._archive is None and hasattr(self, "_package"): + try: + source = pkgutil.get_data(self.package_name, os.path.join(self.package_path, *split_template_path(template))) + except OSError: + raise TemplateNotFound(template) + + def up_to_date() -> bool: + return True + + elif self._archive is None: + # Package is a directory. + if not os.path.isfile(p): + raise TemplateNotFound(template) + + with open(p, "rb") as f: + source = f.read() + + mtime = os.path.getmtime(p) + + def up_to_date() -> bool: + return os.path.isfile(p) and os.path.getmtime(p) == mtime + + else: + # Package is a zip file. + try: + source = self._loader.get_data(p) # type: ignore + except OSError as e: + raise TemplateNotFound(template) from e + + # Could use the zip's mtime for all template mtimes, but + # would need to safely reload the module if it's out of + # date, so just report it as always current. + up_to_date = None + + return source.decode(self.encoding), p, up_to_date + + def list_templates(self) -> t.List[str]: + results: t.List[str] = [] + + if self._archive is None and hasattr(self, "_package"): + prefix = os.path.join(self._template_root, self.package_path).encode() + b"/" + for name in arcadia_res.resfs_files(prefix): + results.append(name.removeprefix(prefix).decode()) + + elif self._archive is None: + # Package is a directory. + offset = len(self._template_root) + + for dirpath, _, filenames in os.walk(self._template_root): + dirpath = dirpath[offset:].lstrip(os.path.sep) + results.extend( + os.path.join(dirpath, name).replace(os.path.sep, "/") + for name in filenames + ) + else: + if not hasattr(self._loader, "_files"): + raise TypeError( + "This zip import does not have the required" + " metadata to list templates." + ) + + # Package is a zip file. + prefix = ( + self._template_root[len(self._archive) :].lstrip(os.path.sep) + + os.path.sep + ) + offset = len(prefix) + + for name in self._loader._files.keys(): # type: ignore + # Find names under the templates directory that aren't directories. + if name.startswith(prefix) and name[-1] != os.path.sep: + results.append(name[offset:].replace(os.path.sep, "/")) + results.sort() return results class DictLoader(BaseLoader): - """Loads a template from a Python dict mapping template names to - template source. This loader is useful for unittesting: + """Loads a template from a Python dict mapping template names to + template source. This loader is useful for unittesting: >>> loader = DictLoader({'index.html': 'source here'}) Because auto reloading is rarely useful this is disabled per default. """ - def __init__(self, mapping: t.Mapping[str, str]) -> None: + def __init__(self, mapping: t.Mapping[str, str]) -> None: self.mapping = mapping - def get_source( - self, environment: "Environment", template: str - ) -> t.Tuple[str, None, t.Callable[[], bool]]: + def get_source( + self, environment: "Environment", template: str + ) -> t.Tuple[str, None, t.Callable[[], bool]]: if template in self.mapping: source = self.mapping[template] return source, None, lambda: source == self.mapping.get(template) raise TemplateNotFound(template) - def list_templates(self) -> t.List[str]: + def list_templates(self) -> t.List[str]: return sorted(self.mapping) class FunctionLoader(BaseLoader): """A loader that is passed a function which does the loading. The function receives the name of the template and has to return either - a string with the template source, a tuple in the form ``(source, + a string with the template source, a tuple in the form ``(source, filename, uptodatefunc)`` or `None` if the template does not exist. >>> def load_template(name): @@ -454,30 +454,30 @@ class FunctionLoader(BaseLoader): return value. """ - def __init__( - self, - load_func: t.Callable[ - [str], - t.Optional[ - t.Union[ - str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]] - ] - ], - ], - ) -> None: + def __init__( + self, + load_func: t.Callable[ + [str], + t.Optional[ + t.Union[ + str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]] + ] + ], + ], + ) -> None: self.load_func = load_func - def get_source( - self, environment: "Environment", template: str - ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: + def get_source( + self, environment: "Environment", template: str + ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: rv = self.load_func(template) - + if rv is None: raise TemplateNotFound(template) - - if isinstance(rv, str): + + if isinstance(rv, str): return rv, None, None - + return rv @@ -496,49 +496,49 @@ class PrefixLoader(BaseLoader): by loading ``'app2/index.html'`` the file from the second. """ - def __init__( - self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/" - ) -> None: + def __init__( + self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/" + ) -> None: self.mapping = mapping self.delimiter = delimiter - def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]: + def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]: try: prefix, name = template.split(self.delimiter, 1) loader = self.mapping[prefix] - except (ValueError, KeyError) as e: - raise TemplateNotFound(template) from e + except (ValueError, KeyError) as e: + raise TemplateNotFound(template) from e return loader, name - def get_source( - self, environment: "Environment", template: str - ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: + def get_source( + self, environment: "Environment", template: str + ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: loader, name = self.get_loader(template) try: return loader.get_source(environment, name) - except TemplateNotFound as e: + except TemplateNotFound as e: # re-raise the exception with the correct filename here. # (the one that includes the prefix) - raise TemplateNotFound(template) from e + raise TemplateNotFound(template) from e @internalcode - def load( - self, - environment: "Environment", - name: str, - globals: t.Optional[t.MutableMapping[str, t.Any]] = None, - ) -> "Template": + def load( + self, + environment: "Environment", + name: str, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, + ) -> "Template": loader, local_name = self.get_loader(name) try: return loader.load(environment, local_name, globals) - except TemplateNotFound as e: + except TemplateNotFound as e: # re-raise the exception with the correct filename here. # (the one that includes the prefix) - raise TemplateNotFound(name) from e + raise TemplateNotFound(name) from e - def list_templates(self) -> t.List[str]: + def list_templates(self) -> t.List[str]: result = [] - for prefix, loader in self.mapping.items(): + for prefix, loader in self.mapping.items(): for template in loader.list_templates(): result.append(prefix + self.delimiter + template) return result @@ -558,12 +558,12 @@ class ChoiceLoader(BaseLoader): from a different location. """ - def __init__(self, loaders: t.Sequence[BaseLoader]) -> None: + def __init__(self, loaders: t.Sequence[BaseLoader]) -> None: self.loaders = loaders - def get_source( - self, environment: "Environment", template: str - ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: + def get_source( + self, environment: "Environment", template: str + ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: for loader in self.loaders: try: return loader.get_source(environment, template) @@ -572,12 +572,12 @@ class ChoiceLoader(BaseLoader): raise TemplateNotFound(template) @internalcode - def load( - self, - environment: "Environment", - name: str, - globals: t.Optional[t.MutableMapping[str, t.Any]] = None, - ) -> "Template": + def load( + self, + environment: "Environment", + name: str, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, + ) -> "Template": for loader in self.loaders: try: return loader.load(environment, name, globals) @@ -585,7 +585,7 @@ class ChoiceLoader(BaseLoader): pass raise TemplateNotFound(name) - def list_templates(self) -> t.List[str]: + def list_templates(self) -> t.List[str]: found = set() for loader in self.loaders: found.update(loader.list_templates()) @@ -611,24 +611,24 @@ class ModuleLoader(BaseLoader): has_source_access = False - def __init__( - self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]] - ) -> None: - package_name = f"_jinja2_module_templates_{id(self):x}" + def __init__( + self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]] + ) -> None: + package_name = f"_jinja2_module_templates_{id(self):x}" # create a fake module that looks for the templates in the # path given. mod = _TemplateModule(package_name) - - if not isinstance(path, abc.Iterable) or isinstance(path, str): + + if not isinstance(path, abc.Iterable) or isinstance(path, str): path = [path] - mod.__path__ = [os.fspath(p) for p in path] # type: ignore - - sys.modules[package_name] = weakref.proxy( - mod, lambda x: sys.modules.pop(package_name, None) - ) + mod.__path__ = [os.fspath(p) for p in path] # type: ignore + sys.modules[package_name] = weakref.proxy( + mod, lambda x: sys.modules.pop(package_name, None) + ) + # the only strong reference, the sys.modules entry is weak # so that the garbage collector can remove it once the # loader that created it goes out of business. @@ -636,40 +636,40 @@ class ModuleLoader(BaseLoader): self.package_name = package_name @staticmethod - def get_template_key(name: str) -> str: - return "tmpl_" + sha1(name.encode("utf-8")).hexdigest() + def get_template_key(name: str) -> str: + return "tmpl_" + sha1(name.encode("utf-8")).hexdigest() @staticmethod - def get_module_filename(name: str) -> str: - return ModuleLoader.get_template_key(name) + ".py" + def get_module_filename(name: str) -> str: + return ModuleLoader.get_template_key(name) + ".py" @internalcode - def load( - self, - environment: "Environment", - name: str, - globals: t.Optional[t.MutableMapping[str, t.Any]] = None, - ) -> "Template": + def load( + self, + environment: "Environment", + name: str, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, + ) -> "Template": key = self.get_template_key(name) - module = f"{self.package_name}.{key}" + module = f"{self.package_name}.{key}" mod = getattr(self.module, module, None) - + if mod is None: try: - mod = __import__(module, None, None, ["root"]) - except ImportError as e: - raise TemplateNotFound(name) from e + mod = __import__(module, None, None, ["root"]) + except ImportError as e: + raise TemplateNotFound(name) from e # remove the entry from sys.modules, we only want the attribute # on the module object we have stored on the loader. sys.modules.pop(module, None) - if globals is None: - globals = {} - + if globals is None: + globals = {} + return environment.template_class.from_module_dict( - environment, mod.__dict__, globals - ) + environment, mod.__dict__, globals + ) class ResourceLoader(BaseLoader): @@ -683,4 +683,4 @@ class ResourceLoader(BaseLoader): try: return self.module_loader.get_data(os.path.join(self.prefix, template)).decode('utf-8'), None, None except IOError: - raise TemplateNotFound(template) + raise TemplateNotFound(template) diff --git a/contrib/python/Jinja2/py3/jinja2/meta.py b/contrib/python/Jinja2/py3/jinja2/meta.py index 0057d6eaba..67a032b6c2 100644 --- a/contrib/python/Jinja2/py3/jinja2/meta.py +++ b/contrib/python/Jinja2/py3/jinja2/meta.py @@ -1,36 +1,36 @@ -"""Functions that expose information about templates that might be -interesting for introspection. +"""Functions that expose information about templates that might be +interesting for introspection. """ -import typing as t - -from . import nodes -from .compiler import CodeGenerator -from .compiler import Frame - -if t.TYPE_CHECKING: - from .environment import Environment +import typing as t + +from . import nodes +from .compiler import CodeGenerator +from .compiler import Frame +if t.TYPE_CHECKING: + from .environment import Environment + class TrackingCodeGenerator(CodeGenerator): """We abuse the code generator for introspection.""" - def __init__(self, environment: "Environment") -> None: - super().__init__(environment, "<introspection>", "<introspection>") - self.undeclared_identifiers: t.Set[str] = set() + def __init__(self, environment: "Environment") -> None: + super().__init__(environment, "<introspection>", "<introspection>") + self.undeclared_identifiers: t.Set[str] = set() - def write(self, x: str) -> None: + def write(self, x: str) -> None: """Don't write.""" - def enter_frame(self, frame: Frame) -> None: + def enter_frame(self, frame: Frame) -> None: """Remember all undeclared identifiers.""" - super().enter_frame(frame) - - for _, (action, param) in frame.symbols.loads.items(): - if action == "resolve" and param not in self.environment.globals: + super().enter_frame(frame) + + for _, (action, param) in frame.symbols.loads.items(): + if action == "resolve" and param not in self.environment.globals: self.undeclared_identifiers.add(param) -def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]: +def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]: """Returns a set of all variables in the AST that will be looked up from the context at runtime. Because at compile time it's not known which variables will be used depending on the path the execution takes at @@ -39,7 +39,7 @@ def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]: >>> from jinja2 import Environment, meta >>> env = Environment() >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}') - >>> meta.find_undeclared_variables(ast) == {'bar'} + >>> meta.find_undeclared_variables(ast) == {'bar'} True .. admonition:: Implementation @@ -49,16 +49,16 @@ def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]: :exc:`TemplateAssertionError` during compilation and as a matter of fact this function can currently raise that exception as well. """ - codegen = TrackingCodeGenerator(ast.environment) # type: ignore + codegen = TrackingCodeGenerator(ast.environment) # type: ignore codegen.visit(ast) return codegen.undeclared_identifiers -_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include) -_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include] - - -def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]: +_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include) +_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include] + + +def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]: """Finds all the referenced templates from the AST. This will return an iterator over all the hardcoded template extensions, inclusions and imports. If dynamic inheritance or inclusion is used, `None` will be @@ -73,19 +73,19 @@ def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str] This function is useful for dependency tracking. For example if you want to rebuild parts of the website after a layout template has changed. """ - template_name: t.Any - - for node in ast.find_all(_ref_types): - template: nodes.Expr = node.template # type: ignore - - if not isinstance(template, nodes.Const): + template_name: t.Any + + for node in ast.find_all(_ref_types): + template: nodes.Expr = node.template # type: ignore + + if not isinstance(template, nodes.Const): # a tuple with some non consts in there - if isinstance(template, (nodes.Tuple, nodes.List)): - for template_name in template.items: + if isinstance(template, (nodes.Tuple, nodes.List)): + for template_name in template.items: # something const, only yield the strings and ignore # non-string consts that really just make no sense if isinstance(template_name, nodes.Const): - if isinstance(template_name.value, str): + if isinstance(template_name.value, str): yield template_name.value # something dynamic in there else: @@ -95,16 +95,16 @@ def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str] yield None continue # constant is a basestring, direct template name - if isinstance(template.value, str): - yield template.value + if isinstance(template.value, str): + yield template.value # a tuple or list (latter *should* not happen) made of consts, # yield the consts that are strings. We could warn here for # non string values - elif isinstance(node, nodes.Include) and isinstance( - template.value, (tuple, list) - ): - for template_name in template.value: - if isinstance(template_name, str): + elif isinstance(node, nodes.Include) and isinstance( + template.value, (tuple, list) + ): + for template_name in template.value: + if isinstance(template_name, str): yield template_name # something else we don't care about, we could warn here else: diff --git a/contrib/python/Jinja2/py3/jinja2/nativetypes.py b/contrib/python/Jinja2/py3/jinja2/nativetypes.py index 20597d50ab..44b09635b2 100644 --- a/contrib/python/Jinja2/py3/jinja2/nativetypes.py +++ b/contrib/python/Jinja2/py3/jinja2/nativetypes.py @@ -1,37 +1,37 @@ -import typing as t +import typing as t from ast import literal_eval from ast import parse -from itertools import chain -from itertools import islice - -from . import nodes -from .compiler import CodeGenerator -from .compiler import Frame -from .compiler import has_safe_repr -from .environment import Environment -from .environment import Template - - -def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]: - """Return a native Python type from the list of compiled nodes. If - the result is a single node, its value is returned. Otherwise, the - nodes are concatenated as strings. If the result can be parsed with - :func:`ast.literal_eval`, the parsed value is returned. Otherwise, - the string is returned. - - :param values: Iterable of outputs to concatenate. +from itertools import chain +from itertools import islice + +from . import nodes +from .compiler import CodeGenerator +from .compiler import Frame +from .compiler import has_safe_repr +from .environment import Environment +from .environment import Template + + +def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]: + """Return a native Python type from the list of compiled nodes. If + the result is a single node, its value is returned. Otherwise, the + nodes are concatenated as strings. If the result can be parsed with + :func:`ast.literal_eval`, the parsed value is returned. Otherwise, + the string is returned. + + :param values: Iterable of outputs to concatenate. """ - head = list(islice(values, 2)) + head = list(islice(values, 2)) if not head: return None if len(head) == 1: - raw = head[0] - if not isinstance(raw, str): - return raw + raw = head[0] + if not isinstance(raw, str): + return raw else: - raw = "".join([str(v) for v in chain(head, values)]) + raw = "".join([str(v) for v in chain(head, values)]) try: return literal_eval( @@ -41,84 +41,84 @@ def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]: parse(raw, mode="eval") ) except (ValueError, SyntaxError, MemoryError): - return raw + return raw class NativeCodeGenerator(CodeGenerator): - """A code generator which renders Python types by not adding - ``str()`` around output nodes. + """A code generator which renders Python types by not adding + ``str()`` around output nodes. """ - @staticmethod - def _default_finalize(value: t.Any) -> t.Any: - return value + @staticmethod + def _default_finalize(value: t.Any) -> t.Any: + return value - def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: - return repr("".join([str(v) for v in group])) + def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: + return repr("".join([str(v) for v in group])) - def _output_child_to_const( - self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo - ) -> t.Any: - const = node.as_const(frame.eval_ctx) + def _output_child_to_const( + self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo + ) -> t.Any: + const = node.as_const(frame.eval_ctx) - if not has_safe_repr(const): - raise nodes.Impossible() + if not has_safe_repr(const): + raise nodes.Impossible() - if isinstance(node, nodes.TemplateData): - return const + if isinstance(node, nodes.TemplateData): + return const - return finalize.const(const) # type: ignore + return finalize.const(const) # type: ignore - def _output_child_pre( - self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo - ) -> None: - if finalize.src is not None: - self.write(finalize.src) + def _output_child_pre( + self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo + ) -> None: + if finalize.src is not None: + self.write(finalize.src) - def _output_child_post( - self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo - ) -> None: - if finalize.src is not None: - self.write(")") + def _output_child_post( + self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo + ) -> None: + if finalize.src is not None: + self.write(")") -class NativeEnvironment(Environment): - """An environment that renders templates to native Python types.""" +class NativeEnvironment(Environment): + """An environment that renders templates to native Python types.""" - code_generator_class = NativeCodeGenerator + code_generator_class = NativeCodeGenerator -class NativeTemplate(Template): - environment_class = NativeEnvironment +class NativeTemplate(Template): + environment_class = NativeEnvironment - def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any: - """Render the template to produce a native Python type. If the - result is a single node, its value is returned. Otherwise, the - nodes are concatenated as strings. If the result can be parsed - with :func:`ast.literal_eval`, the parsed value is returned. - Otherwise, the string is returned. + def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Render the template to produce a native Python type. If the + result is a single node, its value is returned. Otherwise, the + nodes are concatenated as strings. If the result can be parsed + with :func:`ast.literal_eval`, the parsed value is returned. + Otherwise, the string is returned. """ - ctx = self.new_context(dict(*args, **kwargs)) + ctx = self.new_context(dict(*args, **kwargs)) try: - return native_concat(self.root_render_func(ctx)) # type: ignore + return native_concat(self.root_render_func(ctx)) # type: ignore except Exception: - return self.environment.handle_exception() - - async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any: - if not self.environment.is_async: - raise RuntimeError( - "The environment was not created with async mode enabled." - ) - - ctx = self.new_context(dict(*args, **kwargs)) - - try: - return native_concat( - [n async for n in self.root_render_func(ctx)] # type: ignore - ) - except Exception: - return self.environment.handle_exception() - - -NativeEnvironment.template_class = NativeTemplate + return self.environment.handle_exception() + + async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + if not self.environment.is_async: + raise RuntimeError( + "The environment was not created with async mode enabled." + ) + + ctx = self.new_context(dict(*args, **kwargs)) + + try: + return native_concat( + [n async for n in self.root_render_func(ctx)] # type: ignore + ) + except Exception: + return self.environment.handle_exception() + + +NativeEnvironment.template_class = NativeTemplate diff --git a/contrib/python/Jinja2/py3/jinja2/nodes.py b/contrib/python/Jinja2/py3/jinja2/nodes.py index b2f88d9d9c..fcb5acfc78 100644 --- a/contrib/python/Jinja2/py3/jinja2/nodes.py +++ b/contrib/python/Jinja2/py3/jinja2/nodes.py @@ -1,47 +1,47 @@ -"""AST nodes generated by the parser for the compiler. Also provides -some node tree helper functions used by the parser and compiler in order -to normalize nodes. +"""AST nodes generated by the parser for the compiler. Also provides +some node tree helper functions used by the parser and compiler in order +to normalize nodes. """ -import inspect +import inspect import operator -import typing as t +import typing as t from collections import deque -from markupsafe import Markup - -from .utils import _PassArg - -if t.TYPE_CHECKING: - import typing_extensions as te - from .environment import Environment - -_NodeBound = t.TypeVar("_NodeBound", bound="Node") - -_binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = { - "*": operator.mul, - "/": operator.truediv, - "//": operator.floordiv, - "**": operator.pow, - "%": operator.mod, - "+": operator.add, - "-": operator.sub, +from markupsafe import Markup + +from .utils import _PassArg + +if t.TYPE_CHECKING: + import typing_extensions as te + from .environment import Environment + +_NodeBound = t.TypeVar("_NodeBound", bound="Node") + +_binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = { + "*": operator.mul, + "/": operator.truediv, + "//": operator.floordiv, + "**": operator.pow, + "%": operator.mod, + "+": operator.add, + "-": operator.sub, } -_uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = { - "not": operator.not_, - "+": operator.pos, - "-": operator.neg, -} - -_cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = { - "eq": operator.eq, - "ne": operator.ne, - "gt": operator.gt, - "gteq": operator.ge, - "lt": operator.lt, - "lteq": operator.le, - "in": lambda a, b: a in b, - "notin": lambda a, b: a not in b, +_uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = { + "not": operator.not_, + "+": operator.pos, + "-": operator.neg, +} + +_cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = { + "eq": operator.eq, + "ne": operator.ne, + "gt": operator.gt, + "gteq": operator.ge, + "lt": operator.lt, + "lteq": operator.le, + "in": lambda a, b: a in b, + "notin": lambda a, b: a not in b, } @@ -54,26 +54,26 @@ class NodeType(type): inheritance. fields and attributes from the parent class are automatically forwarded to the child.""" - def __new__(mcs, name, bases, d): # type: ignore - for attr in "fields", "attributes": + def __new__(mcs, name, bases, d): # type: ignore + for attr in "fields", "attributes": storage = [] - storage.extend(getattr(bases[0] if bases else object, attr, ())) + storage.extend(getattr(bases[0] if bases else object, attr, ())) storage.extend(d.get(attr, ())) - assert len(bases) <= 1, "multiple inheritance not allowed" - assert len(storage) == len(set(storage)), "layout conflict" + assert len(bases) <= 1, "multiple inheritance not allowed" + assert len(storage) == len(set(storage)), "layout conflict" d[attr] = tuple(storage) - d.setdefault("abstract", False) - return type.__new__(mcs, name, bases, d) + d.setdefault("abstract", False) + return type.__new__(mcs, name, bases, d) -class EvalContext: +class EvalContext: """Holds evaluation time information. Custom attributes can be attached to it in extensions. """ - def __init__( - self, environment: "Environment", template_name: t.Optional[str] = None - ) -> None: + def __init__( + self, environment: "Environment", template_name: t.Optional[str] = None + ) -> None: self.environment = environment if callable(environment.autoescape): self.autoescape = environment.autoescape(template_name) @@ -81,27 +81,27 @@ class EvalContext: self.autoescape = environment.autoescape self.volatile = False - def save(self) -> t.Mapping[str, t.Any]: + def save(self) -> t.Mapping[str, t.Any]: return self.__dict__.copy() - def revert(self, old: t.Mapping[str, t.Any]) -> None: + def revert(self, old: t.Mapping[str, t.Any]) -> None: self.__dict__.clear() self.__dict__.update(old) -def get_eval_context(node: "Node", ctx: t.Optional[EvalContext]) -> EvalContext: +def get_eval_context(node: "Node", ctx: t.Optional[EvalContext]) -> EvalContext: if ctx is None: if node.environment is None: - raise RuntimeError( - "if no eval context is passed, the node must have an" - " attached environment." - ) + raise RuntimeError( + "if no eval context is passed, the node must have an" + " attached environment." + ) return EvalContext(node.environment) return ctx -class Node(metaclass=NodeType): - """Baseclass for all Jinja nodes. There are a number of nodes available +class Node(metaclass=NodeType): + """Baseclass for all Jinja nodes. There are a number of nodes available of different types. There are four major types: - :class:`Stmt`: statements @@ -116,37 +116,37 @@ class Node(metaclass=NodeType): The `environment` attribute is set at the end of the parsing process for all nodes automatically. """ - - fields: t.Tuple[str, ...] = () - attributes: t.Tuple[str, ...] = ("lineno", "environment") + + fields: t.Tuple[str, ...] = () + attributes: t.Tuple[str, ...] = ("lineno", "environment") abstract = True - lineno: int - environment: t.Optional["Environment"] - - def __init__(self, *fields: t.Any, **attributes: t.Any) -> None: + lineno: int + environment: t.Optional["Environment"] + + def __init__(self, *fields: t.Any, **attributes: t.Any) -> None: if self.abstract: - raise TypeError("abstract nodes are not instantiable") + raise TypeError("abstract nodes are not instantiable") if fields: if len(fields) != len(self.fields): if not self.fields: - raise TypeError(f"{type(self).__name__!r} takes 0 arguments") - raise TypeError( - f"{type(self).__name__!r} takes 0 or {len(self.fields)}" - f" argument{'s' if len(self.fields) != 1 else ''}" - ) - for name, arg in zip(self.fields, fields): + raise TypeError(f"{type(self).__name__!r} takes 0 arguments") + raise TypeError( + f"{type(self).__name__!r} takes 0 or {len(self.fields)}" + f" argument{'s' if len(self.fields) != 1 else ''}" + ) + for name, arg in zip(self.fields, fields): setattr(self, name, arg) for attr in self.attributes: setattr(self, attr, attributes.pop(attr, None)) if attributes: - raise TypeError(f"unknown attribute {next(iter(attributes))!r}") + raise TypeError(f"unknown attribute {next(iter(attributes))!r}") - def iter_fields( - self, - exclude: t.Optional[t.Container[str]] = None, - only: t.Optional[t.Container[str]] = None, - ) -> t.Iterator[t.Tuple[str, t.Any]]: + def iter_fields( + self, + exclude: t.Optional[t.Container[str]] = None, + only: t.Optional[t.Container[str]] = None, + ) -> t.Iterator[t.Tuple[str, t.Any]]: """This method iterates over all fields that are defined and yields ``(key, value)`` tuples. Per default all fields are returned, but it's possible to limit that to some fields by providing the `only` @@ -154,26 +154,26 @@ class Node(metaclass=NodeType): should be sets or tuples of field names. """ for name in self.fields: - if ( - (exclude is None and only is None) - or (exclude is not None and name not in exclude) - or (only is not None and name in only) - ): + if ( + (exclude is None and only is None) + or (exclude is not None and name not in exclude) + or (only is not None and name in only) + ): try: yield name, getattr(self, name) except AttributeError: pass - def iter_child_nodes( - self, - exclude: t.Optional[t.Container[str]] = None, - only: t.Optional[t.Container[str]] = None, - ) -> t.Iterator["Node"]: + def iter_child_nodes( + self, + exclude: t.Optional[t.Container[str]] = None, + only: t.Optional[t.Container[str]] = None, + ) -> t.Iterator["Node"]: """Iterates over all direct child nodes of the node. This iterates over all fields and yields the values of they are nodes. If the value of a field is a list all the nodes in that list are returned. """ - for _, item in self.iter_fields(exclude, only): + for _, item in self.iter_fields(exclude, only): if isinstance(item, list): for n in item: if isinstance(n, Node): @@ -181,27 +181,27 @@ class Node(metaclass=NodeType): elif isinstance(item, Node): yield item - def find(self, node_type: t.Type[_NodeBound]) -> t.Optional[_NodeBound]: + def find(self, node_type: t.Type[_NodeBound]) -> t.Optional[_NodeBound]: """Find the first node of a given type. If no such node exists the return value is `None`. """ for result in self.find_all(node_type): return result - return None - - def find_all( - self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]] - ) -> t.Iterator[_NodeBound]: + return None + + def find_all( + self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]] + ) -> t.Iterator[_NodeBound]: """Find all the nodes of a given type. If the type is a tuple, the check is performed for any of the tuple items. """ for child in self.iter_child_nodes(): if isinstance(child, node_type): - yield child # type: ignore - yield from child.find_all(node_type) + yield child # type: ignore + yield from child.find_all(node_type) - def set_ctx(self, ctx: str) -> "Node": + def set_ctx(self, ctx: str) -> "Node": """Reset the context of a node and all child nodes. Per default the parser will all generate nodes that have a 'load' context as it's the most common one. This method is used in the parser to set assignment @@ -210,23 +210,23 @@ class Node(metaclass=NodeType): todo = deque([self]) while todo: node = todo.popleft() - if "ctx" in node.fields: - node.ctx = ctx # type: ignore + if "ctx" in node.fields: + node.ctx = ctx # type: ignore todo.extend(node.iter_child_nodes()) return self - def set_lineno(self, lineno: int, override: bool = False) -> "Node": + def set_lineno(self, lineno: int, override: bool = False) -> "Node": """Set the line numbers of the node and children.""" todo = deque([self]) while todo: node = todo.popleft() - if "lineno" in node.attributes: + if "lineno" in node.attributes: if node.lineno is None or override: node.lineno = lineno todo.extend(node.iter_child_nodes()) return self - def set_environment(self, environment: "Environment") -> "Node": + def set_environment(self, environment: "Environment") -> "Node": """Set the environment for all nodes.""" todo = deque([self]) while todo: @@ -235,57 +235,57 @@ class Node(metaclass=NodeType): todo.extend(node.iter_child_nodes()) return self - def __eq__(self, other: t.Any) -> bool: - if type(self) is not type(other): - return NotImplemented + def __eq__(self, other: t.Any) -> bool: + if type(self) is not type(other): + return NotImplemented - return tuple(self.iter_fields()) == tuple(other.iter_fields()) + return tuple(self.iter_fields()) == tuple(other.iter_fields()) __hash__ = object.__hash__ - def __repr__(self) -> str: - args_str = ", ".join(f"{a}={getattr(self, a, None)!r}" for a in self.fields) - return f"{type(self).__name__}({args_str})" + def __repr__(self) -> str: + args_str = ", ".join(f"{a}={getattr(self, a, None)!r}" for a in self.fields) + return f"{type(self).__name__}({args_str})" - def dump(self) -> str: - def _dump(node: t.Union[Node, t.Any]) -> None: + def dump(self) -> str: + def _dump(node: t.Union[Node, t.Any]) -> None: if not isinstance(node, Node): buf.append(repr(node)) return - buf.append(f"nodes.{type(node).__name__}(") + buf.append(f"nodes.{type(node).__name__}(") if not node.fields: - buf.append(")") + buf.append(")") return for idx, field in enumerate(node.fields): if idx: - buf.append(", ") + buf.append(", ") value = getattr(node, field) if isinstance(value, list): - buf.append("[") + buf.append("[") for idx, item in enumerate(value): if idx: - buf.append(", ") + buf.append(", ") _dump(item) - buf.append("]") + buf.append("]") else: _dump(value) - buf.append(")") - - buf: t.List[str] = [] + buf.append(")") + + buf: t.List[str] = [] _dump(self) - return "".join(buf) + return "".join(buf) class Stmt(Node): """Base node for all statements.""" - + abstract = True class Helper(Node): """Nodes that exist in a specific context only.""" - + abstract = True @@ -294,26 +294,26 @@ class Template(Node): is passed to the compiler. """ - fields = ("body",) - body: t.List[Node] - + fields = ("body",) + body: t.List[Node] + class Output(Stmt): """A node that holds multiple expressions which are then printed out. This is used both for the `print` statement and the regular template data. """ - fields = ("nodes",) - nodes: t.List["Expr"] - + fields = ("nodes",) + nodes: t.List["Expr"] + class Extends(Stmt): """Represents an extends statement.""" - fields = ("template",) - template: "Expr" - + fields = ("template",) + template: "Expr" + class For(Stmt): """The for loop. `target` is the target for the iteration (usually a :class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list @@ -323,58 +323,58 @@ class For(Stmt): For filtered nodes an expression can be stored as `test`, otherwise `None`. """ - fields = ("target", "iter", "body", "else_", "test", "recursive") - target: Node - iter: Node - body: t.List[Node] - else_: t.List[Node] - test: t.Optional[Node] - recursive: bool - + fields = ("target", "iter", "body", "else_", "test", "recursive") + target: Node + iter: Node + body: t.List[Node] + else_: t.List[Node] + test: t.Optional[Node] + recursive: bool + class If(Stmt): """If `test` is true, `body` is rendered, else `else_`.""" - fields = ("test", "body", "elif_", "else_") - test: Node - body: t.List[Node] - elif_: t.List["If"] - else_: t.List[Node] - + fields = ("test", "body", "elif_", "else_") + test: Node + body: t.List[Node] + elif_: t.List["If"] + else_: t.List[Node] + class Macro(Stmt): """A macro definition. `name` is the name of the macro, `args` a list of arguments and `defaults` a list of defaults if there are any. `body` is a list of nodes for the macro body. """ - fields = ("name", "args", "defaults", "body") - name: str - args: t.List["Name"] - defaults: t.List["Expr"] - body: t.List[Node] - + fields = ("name", "args", "defaults", "body") + name: str + args: t.List["Name"] + defaults: t.List["Expr"] + body: t.List[Node] + class CallBlock(Stmt): """Like a macro without a name but a call instead. `call` is called with the unnamed macro as `caller` argument this node holds. """ - fields = ("call", "args", "defaults", "body") - call: "Call" - args: t.List["Name"] - defaults: t.List["Expr"] - body: t.List[Node] - + fields = ("call", "args", "defaults", "body") + call: "Call" + args: t.List["Name"] + defaults: t.List["Expr"] + body: t.List[Node] + class FilterBlock(Stmt): """Node for filter sections.""" - fields = ("body", "filter") - body: t.List[Node] - filter: "Filter" - + fields = ("body", "filter") + body: t.List[Node] + filter: "Filter" + class With(Stmt): """Specific node for with statements. In older versions of Jinja the with statement was implemented on the base of the `Scope` node instead. @@ -382,44 +382,44 @@ class With(Stmt): .. versionadded:: 2.9.3 """ - fields = ("targets", "values", "body") - targets: t.List["Expr"] - values: t.List["Expr"] - body: t.List[Node] - + fields = ("targets", "values", "body") + targets: t.List["Expr"] + values: t.List["Expr"] + body: t.List[Node] + class Block(Stmt): - """A node that represents a block. - - .. versionchanged:: 3.0.0 - the `required` field was added. - """ - - fields = ("name", "body", "scoped", "required") - name: str - body: t.List[Node] - scoped: bool - required: bool - - + """A node that represents a block. + + .. versionchanged:: 3.0.0 + the `required` field was added. + """ + + fields = ("name", "body", "scoped", "required") + name: str + body: t.List[Node] + scoped: bool + required: bool + + class Include(Stmt): """A node that represents the include tag.""" - fields = ("template", "with_context", "ignore_missing") - template: "Expr" - with_context: bool - ignore_missing: bool - + fields = ("template", "with_context", "ignore_missing") + template: "Expr" + with_context: bool + ignore_missing: bool + class Import(Stmt): """A node that represents the import tag.""" - fields = ("template", "target", "with_context") - template: "Expr" - target: str - with_context: bool - + fields = ("template", "target", "with_context") + template: "Expr" + target: str + with_context: bool + class FromImport(Stmt): """A node that represents the from import tag. It's important to not pass unsafe names to the name attribute. The compiler translates the @@ -432,42 +432,42 @@ class FromImport(Stmt): The list of names may contain tuples if aliases are wanted. """ - fields = ("template", "names", "with_context") - template: "Expr" - names: t.List[t.Union[str, t.Tuple[str, str]]] - with_context: bool - + fields = ("template", "names", "with_context") + template: "Expr" + names: t.List[t.Union[str, t.Tuple[str, str]]] + with_context: bool + class ExprStmt(Stmt): """A statement that evaluates an expression and discards the result.""" - fields = ("node",) - node: Node - + fields = ("node",) + node: Node + class Assign(Stmt): """Assigns an expression to a target.""" - fields = ("target", "node") - target: "Expr" - node: Node - + fields = ("target", "node") + target: "Expr" + node: Node + class AssignBlock(Stmt): """Assigns a block to a target.""" - fields = ("target", "filter", "body") - target: "Expr" - filter: t.Optional["Filter"] - body: t.List[Node] - + fields = ("target", "filter", "body") + target: "Expr" + filter: t.Optional["Filter"] + body: t.List[Node] + class Expr(Node): """Baseclass for all expressions.""" - + abstract = True - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: """Return the value of the expression as constant or raise :exc:`Impossible` if this was not possible. @@ -480,58 +480,58 @@ class Expr(Node): """ raise Impossible() - def can_assign(self) -> bool: + def can_assign(self) -> bool: """Check if it's possible to assign something to this node.""" return False class BinExpr(Expr): """Baseclass for all binary expressions.""" - - fields = ("left", "right") - left: Expr - right: Expr - operator: str + + fields = ("left", "right") + left: Expr + right: Expr + operator: str abstract = True - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: eval_ctx = get_eval_context(self, eval_ctx) - + # intercepted operators cannot be folded at compile time - if ( - eval_ctx.environment.sandboxed - and self.operator in eval_ctx.environment.intercepted_binops # type: ignore - ): + if ( + eval_ctx.environment.sandboxed + and self.operator in eval_ctx.environment.intercepted_binops # type: ignore + ): raise Impossible() f = _binop_to_func[self.operator] try: return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx)) - except Exception as e: - raise Impossible() from e + except Exception as e: + raise Impossible() from e class UnaryExpr(Expr): """Baseclass for all unary expressions.""" - - fields = ("node",) - node: Expr - operator: str + + fields = ("node",) + node: Expr + operator: str abstract = True - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: eval_ctx = get_eval_context(self, eval_ctx) - + # intercepted operators cannot be folded at compile time - if ( - eval_ctx.environment.sandboxed - and self.operator in eval_ctx.environment.intercepted_unops # type: ignore - ): + if ( + eval_ctx.environment.sandboxed + and self.operator in eval_ctx.environment.intercepted_unops # type: ignore + ): raise Impossible() f = _uaop_to_func[self.operator] try: return f(self.node.as_const(eval_ctx)) - except Exception as e: - raise Impossible() from e + except Exception as e: + raise Impossible() from e class Name(Expr): @@ -543,22 +543,22 @@ class Name(Expr): - `param`: like `store` but if the name was defined as function parameter. """ - fields = ("name", "ctx") - name: str - ctx: str - - def can_assign(self) -> bool: - return self.name not in {"true", "false", "none", "True", "False", "None"} + fields = ("name", "ctx") + name: str + ctx: str + + def can_assign(self) -> bool: + return self.name not in {"true", "false", "none", "True", "False", "None"} class NSRef(Expr): """Reference to a namespace value assignment""" - fields = ("name", "attr") - name: str - attr: str - - def can_assign(self) -> bool: + fields = ("name", "attr") + name: str + attr: str + + def can_assign(self) -> bool: # We don't need any special checks here; NSRef assignments have a # runtime check to ensure the target is a namespace object which will # have been checked already as it is created using a normal assignment @@ -568,7 +568,7 @@ class NSRef(Expr): class Literal(Expr): """Baseclass for literals.""" - + abstract = True @@ -579,25 +579,25 @@ class Const(Literal): representation (objects where ``eval(repr(x)) == x`` is true). """ - fields = ("value",) - value: t.Any - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: - return self.value + fields = ("value",) + value: t.Any + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + return self.value @classmethod - def from_untrusted( - cls, - value: t.Any, - lineno: t.Optional[int] = None, - environment: "t.Optional[Environment]" = None, - ) -> "Const": + def from_untrusted( + cls, + value: t.Any, + lineno: t.Optional[int] = None, + environment: "t.Optional[Environment]" = None, + ) -> "Const": """Return a const object if the value is representable as constant value in the generated code, otherwise it will raise an `Impossible` exception. """ from .compiler import has_safe_repr - + if not has_safe_repr(value): raise Impossible() return cls(value, lineno=lineno, environment=environment) @@ -606,10 +606,10 @@ class Const(Literal): class TemplateData(Literal): """A constant template string.""" - fields = ("data",) - data: str - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str: + fields = ("data",) + data: str + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str: eval_ctx = get_eval_context(self, eval_ctx) if eval_ctx.volatile: raise Impossible() @@ -624,15 +624,15 @@ class Tuple(Literal): is used for loading the names or storing. """ - fields = ("items", "ctx") - items: t.List[Expr] - ctx: str - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]: + fields = ("items", "ctx") + items: t.List[Expr] + ctx: str + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]: eval_ctx = get_eval_context(self, eval_ctx) return tuple(x.as_const(eval_ctx) for x in self.items) - def can_assign(self) -> bool: + def can_assign(self) -> bool: for item in self.items: if not item.can_assign(): return False @@ -642,10 +642,10 @@ class Tuple(Literal): class List(Literal): """Any list literal such as ``[1, 2, 3]``""" - fields = ("items",) - items: t.List[Expr] - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]: + fields = ("items",) + items: t.List[Expr] + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]: eval_ctx = get_eval_context(self, eval_ctx) return [x.as_const(eval_ctx) for x in self.items] @@ -655,12 +655,12 @@ class Dict(Literal): :class:`Pair` nodes. """ - fields = ("items",) - items: t.List["Pair"] - - def as_const( - self, eval_ctx: t.Optional[EvalContext] = None - ) -> t.Dict[t.Any, t.Any]: + fields = ("items",) + items: t.List["Pair"] + + def as_const( + self, eval_ctx: t.Optional[EvalContext] = None + ) -> t.Dict[t.Any, t.Any]: eval_ctx = get_eval_context(self, eval_ctx) return dict(x.as_const(eval_ctx) for x in self.items) @@ -668,13 +668,13 @@ class Dict(Literal): class Pair(Helper): """A key, value pair for dicts.""" - fields = ("key", "value") - key: Expr - value: Expr - - def as_const( - self, eval_ctx: t.Optional[EvalContext] = None - ) -> t.Tuple[t.Any, t.Any]: + fields = ("key", "value") + key: Expr + value: Expr + + def as_const( + self, eval_ctx: t.Optional[EvalContext] = None + ) -> t.Tuple[t.Any, t.Any]: eval_ctx = get_eval_context(self, eval_ctx) return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx) @@ -682,11 +682,11 @@ class Pair(Helper): class Keyword(Helper): """A key, value pair for keyword arguments where key is a string.""" - fields = ("key", "value") - key: str - value: Expr - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]: + fields = ("key", "value") + key: str + value: Expr + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]: eval_ctx = get_eval_context(self, eval_ctx) return self.key, self.value.as_const(eval_ctx) @@ -696,12 +696,12 @@ class CondExpr(Expr): foo if bar else baz }}``) """ - fields = ("test", "expr1", "expr2") - test: Expr - expr1: Expr - expr2: t.Optional[Expr] - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + fields = ("test", "expr1", "expr2") + test: Expr + expr1: Expr + expr2: t.Optional[Expr] + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: eval_ctx = get_eval_context(self, eval_ctx) if self.test.as_const(eval_ctx): return self.expr1.as_const(eval_ctx) @@ -713,105 +713,105 @@ class CondExpr(Expr): return self.expr2.as_const(eval_ctx) -def args_as_const( - node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext] -) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]: +def args_as_const( + node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext] +) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]: args = [x.as_const(eval_ctx) for x in node.args] kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs) if node.dyn_args is not None: try: args.extend(node.dyn_args.as_const(eval_ctx)) - except Exception as e: - raise Impossible() from e + except Exception as e: + raise Impossible() from e if node.dyn_kwargs is not None: try: kwargs.update(node.dyn_kwargs.as_const(eval_ctx)) - except Exception as e: - raise Impossible() from e + except Exception as e: + raise Impossible() from e return args, kwargs -class _FilterTestCommon(Expr): - fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs") - node: Expr - name: str - args: t.List[Expr] - kwargs: t.List[Pair] - dyn_args: t.Optional[Expr] - dyn_kwargs: t.Optional[Expr] - abstract = True - _is_filter = True +class _FilterTestCommon(Expr): + fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs") + node: Expr + name: str + args: t.List[Expr] + kwargs: t.List[Pair] + dyn_args: t.Optional[Expr] + dyn_kwargs: t.Optional[Expr] + abstract = True + _is_filter = True - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile: + if eval_ctx.volatile: raise Impossible() - if self._is_filter: - env_map = eval_ctx.environment.filters - else: - env_map = eval_ctx.environment.tests - - func = env_map.get(self.name) - pass_arg = _PassArg.from_obj(func) # type: ignore + if self._is_filter: + env_map = eval_ctx.environment.filters + else: + env_map = eval_ctx.environment.tests - if func is None or pass_arg is _PassArg.context: + func = env_map.get(self.name) + pass_arg = _PassArg.from_obj(func) # type: ignore + + if func is None or pass_arg is _PassArg.context: raise Impossible() - if eval_ctx.environment.is_async and ( - getattr(func, "jinja_async_variant", False) is True - or inspect.iscoroutinefunction(func) + if eval_ctx.environment.is_async and ( + getattr(func, "jinja_async_variant", False) is True + or inspect.iscoroutinefunction(func) ): raise Impossible() args, kwargs = args_as_const(self, eval_ctx) args.insert(0, self.node.as_const(eval_ctx)) - if pass_arg is _PassArg.eval_context: + if pass_arg is _PassArg.eval_context: args.insert(0, eval_ctx) - elif pass_arg is _PassArg.environment: - args.insert(0, eval_ctx.environment) + elif pass_arg is _PassArg.environment: + args.insert(0, eval_ctx.environment) try: - return func(*args, **kwargs) - except Exception as e: - raise Impossible() from e + return func(*args, **kwargs) + except Exception as e: + raise Impossible() from e -class Filter(_FilterTestCommon): - """Apply a filter to an expression. ``name`` is the name of the - filter, the other fields are the same as :class:`Call`. - - If ``node`` is ``None``, the filter is being used in a filter block - and is applied to the content of the block. +class Filter(_FilterTestCommon): + """Apply a filter to an expression. ``name`` is the name of the + filter, the other fields are the same as :class:`Call`. + + If ``node`` is ``None``, the filter is being used in a filter block + and is applied to the content of the block. """ - node: t.Optional[Expr] # type: ignore + node: t.Optional[Expr] # type: ignore - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: - if self.node is None: + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + if self.node is None: raise Impossible() - return super().as_const(eval_ctx=eval_ctx) - + return super().as_const(eval_ctx=eval_ctx) -class Test(_FilterTestCommon): - """Apply a test to an expression. ``name`` is the name of the test, - the other field are the same as :class:`Call`. - - .. versionchanged:: 3.0 - ``as_const`` shares the same logic for filters and tests. Tests - check for volatile, async, and ``@pass_context`` etc. - decorators. - """ - - _is_filter = False +class Test(_FilterTestCommon): + """Apply a test to an expression. ``name`` is the name of the test, + the other field are the same as :class:`Call`. + .. versionchanged:: 3.0 + ``as_const`` shares the same logic for filters and tests. Tests + check for volatile, async, and ``@pass_context`` etc. + decorators. + """ + + _is_filter = False + + class Call(Expr): """Calls an expression. `args` is a list of arguments, `kwargs` a list of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args` @@ -820,34 +820,34 @@ class Call(Expr): arguments. """ - fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs") - node: Expr - args: t.List[Expr] - kwargs: t.List[Keyword] - dyn_args: t.Optional[Expr] - dyn_kwargs: t.Optional[Expr] - + fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs") + node: Expr + args: t.List[Expr] + kwargs: t.List[Keyword] + dyn_args: t.Optional[Expr] + dyn_kwargs: t.Optional[Expr] + class Getitem(Expr): """Get an attribute or item from an expression and prefer the item.""" - fields = ("node", "arg", "ctx") - node: Expr - arg: Expr - ctx: str - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: - if self.ctx != "load": + fields = ("node", "arg", "ctx") + node: Expr + arg: Expr + ctx: str + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + if self.ctx != "load": raise Impossible() - - eval_ctx = get_eval_context(self, eval_ctx) - + + eval_ctx = get_eval_context(self, eval_ctx) + try: - return eval_ctx.environment.getitem( - self.node.as_const(eval_ctx), self.arg.as_const(eval_ctx) - ) - except Exception as e: - raise Impossible() from e + return eval_ctx.environment.getitem( + self.node.as_const(eval_ctx), self.arg.as_const(eval_ctx) + ) + except Exception as e: + raise Impossible() from e class Getattr(Expr): @@ -855,21 +855,21 @@ class Getattr(Expr): bytestring and prefer the attribute. """ - fields = ("node", "attr", "ctx") - node: Expr - attr: str - ctx: str - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: - if self.ctx != "load": + fields = ("node", "attr", "ctx") + node: Expr + attr: str + ctx: str + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + if self.ctx != "load": raise Impossible() - - eval_ctx = get_eval_context(self, eval_ctx) - + + eval_ctx = get_eval_context(self, eval_ctx) + try: - return eval_ctx.environment.getattr(self.node.as_const(eval_ctx), self.attr) - except Exception as e: - raise Impossible() from e + return eval_ctx.environment.getattr(self.node.as_const(eval_ctx), self.attr) + except Exception as e: + raise Impossible() from e class Slice(Expr): @@ -877,33 +877,33 @@ class Slice(Expr): :class:`Subscript`. """ - fields = ("start", "stop", "step") - start: t.Optional[Expr] - stop: t.Optional[Expr] - step: t.Optional[Expr] - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice: + fields = ("start", "stop", "step") + start: t.Optional[Expr] + stop: t.Optional[Expr] + step: t.Optional[Expr] + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice: eval_ctx = get_eval_context(self, eval_ctx) - - def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]: + + def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]: if obj is None: return None return obj.as_const(eval_ctx) - + return slice(const(self.start), const(self.stop), const(self.step)) class Concat(Expr): - """Concatenates the list of expressions provided after converting - them to strings. + """Concatenates the list of expressions provided after converting + them to strings. """ - fields = ("nodes",) - nodes: t.List[Expr] - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str: + fields = ("nodes",) + nodes: t.List[Expr] + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str: eval_ctx = get_eval_context(self, eval_ctx) - return "".join(str(x.as_const(eval_ctx)) for x in self.nodes) + return "".join(str(x.as_const(eval_ctx)) for x in self.nodes) class Compare(Expr): @@ -911,87 +911,87 @@ class Compare(Expr): list of :class:`Operand`\\s. """ - fields = ("expr", "ops") - expr: Expr - ops: t.List["Operand"] - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + fields = ("expr", "ops") + expr: Expr + ops: t.List["Operand"] + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: eval_ctx = get_eval_context(self, eval_ctx) result = value = self.expr.as_const(eval_ctx) - + try: for op in self.ops: new_value = op.expr.as_const(eval_ctx) result = _cmpop_to_func[op.op](value, new_value) - - if not result: - return False - + + if not result: + return False + value = new_value - except Exception as e: - raise Impossible() from e - + except Exception as e: + raise Impossible() from e + return result class Operand(Helper): """Holds an operator and an expression.""" - fields = ("op", "expr") - op: str - expr: Expr - - + fields = ("op", "expr") + op: str + expr: Expr + + class Mul(BinExpr): """Multiplies the left with the right node.""" - operator = "*" - + operator = "*" + class Div(BinExpr): """Divides the left by the right node.""" - operator = "/" - + operator = "/" + class FloorDiv(BinExpr): """Divides the left by the right node and converts the result into an integer by truncating. """ - operator = "//" - + operator = "//" + class Add(BinExpr): """Add the left to the right node.""" - operator = "+" - + operator = "+" + class Sub(BinExpr): """Subtract the right from the left node.""" - operator = "-" - + operator = "-" + class Mod(BinExpr): """Left modulo right.""" - operator = "%" - + operator = "%" + class Pow(BinExpr): """Left to the power of right.""" - operator = "**" - + operator = "**" + class And(BinExpr): """Short circuited AND.""" - operator = "and" - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + operator = "and" + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: eval_ctx = get_eval_context(self, eval_ctx) return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx) @@ -999,9 +999,9 @@ class And(BinExpr): class Or(BinExpr): """Short circuited OR.""" - operator = "or" - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: + operator = "or" + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any: eval_ctx = get_eval_context(self, eval_ctx) return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx) @@ -1009,21 +1009,21 @@ class Or(BinExpr): class Not(UnaryExpr): """Negate the expression.""" - operator = "not" - + operator = "not" + class Neg(UnaryExpr): """Make the expression negative.""" - operator = "-" - + operator = "-" + class Pos(UnaryExpr): """Make the expression positive (noop for most expressions)""" - operator = "+" - + operator = "+" + # Helpers for extensions @@ -1032,10 +1032,10 @@ class EnvironmentAttribute(Expr): extensions that want to call a callback stored on the environment. """ - fields = ("name",) - name: str - + fields = ("name",) + name: str + class ExtensionAttribute(Expr): """Returns the attribute of an extension bound to the environment. The identifier is the identifier of the :class:`Extension`. @@ -1044,11 +1044,11 @@ class ExtensionAttribute(Expr): :meth:`~jinja2.ext.Extension.attr` method on an extension. """ - fields = ("identifier", "name") - identifier: str - name: str - + fields = ("identifier", "name") + identifier: str + name: str + class ImportedName(Expr): """If created with an import name the import name is returned on node access. For example ``ImportedName('cgi.escape')`` returns the `escape` @@ -1056,35 +1056,35 @@ class ImportedName(Expr): compiler so there is no need to assign them to local variables. """ - fields = ("importname",) - importname: str - + fields = ("importname",) + importname: str + class InternalName(Expr): """An internal name in the compiler. You cannot create these nodes yourself but the parser provides a :meth:`~jinja2.parser.Parser.free_identifier` method that creates a new identifier for you. This identifier is not available from the - template and is not treated specially by the compiler. + template and is not treated specially by the compiler. """ - fields = ("name",) - name: str - - def __init__(self) -> None: - raise TypeError( - "Can't create internal names. Use the " - "`free_identifier` method on a parser." - ) + fields = ("name",) + name: str + + def __init__(self) -> None: + raise TypeError( + "Can't create internal names. Use the " + "`free_identifier` method on a parser." + ) class MarkSafe(Expr): """Mark the wrapped expression as safe (wrap it as `Markup`).""" - fields = ("expr",) - expr: Expr - - def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> Markup: + fields = ("expr",) + expr: Expr + + def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> Markup: eval_ctx = get_eval_context(self, eval_ctx) return Markup(self.expr.as_const(eval_ctx)) @@ -1096,12 +1096,12 @@ class MarkSafeIfAutoescape(Expr): .. versionadded:: 2.5 """ - fields = ("expr",) - expr: Expr - - def as_const( - self, eval_ctx: t.Optional[EvalContext] = None - ) -> t.Union[Markup, t.Any]: + fields = ("expr",) + expr: Expr + + def as_const( + self, eval_ctx: t.Optional[EvalContext] = None + ) -> t.Union[Markup, t.Any]: eval_ctx = get_eval_context(self, eval_ctx) if eval_ctx.volatile: raise Impossible() @@ -1121,23 +1121,23 @@ class ContextReference(Expr): Assign(Name('foo', ctx='store'), Getattr(ContextReference(), 'name')) - - This is basically equivalent to using the - :func:`~jinja2.pass_context` decorator when using the high-level - API, which causes a reference to the context to be passed as the - first argument to a function. - """ - - -class DerivedContextReference(Expr): - """Return the current template context including locals. Behaves - exactly like :class:`ContextReference`, but includes local - variables, such as from a ``for`` loop. - - .. versionadded:: 2.11 + + This is basically equivalent to using the + :func:`~jinja2.pass_context` decorator when using the high-level + API, which causes a reference to the context to be passed as the + first argument to a function. """ +class DerivedContextReference(Expr): + """Return the current template context including locals. Behaves + exactly like :class:`ContextReference`, but includes local + variables, such as from a ``for`` loop. + + .. versionadded:: 2.11 + """ + + class Continue(Stmt): """Continue a loop.""" @@ -1149,10 +1149,10 @@ class Break(Stmt): class Scope(Stmt): """An artificial scope.""" - fields = ("body",) - body: t.List[Node] - + fields = ("body",) + body: t.List[Node] + class OverlayScope(Stmt): """An overlay scope for extensions. This is a largely unoptimized scope that however can be used to introduce completely arbitrary variables into @@ -1167,11 +1167,11 @@ class OverlayScope(Stmt): .. versionadded:: 2.10 """ - fields = ("context", "body") - context: Expr - body: t.List[Node] - + fields = ("context", "body") + context: Expr + body: t.List[Node] + class EvalContextModifier(Stmt): """Modifies the eval context. For each option that should be modified, a :class:`Keyword` has to be added to the :attr:`options` list. @@ -1181,24 +1181,24 @@ class EvalContextModifier(Stmt): EvalContextModifier(options=[Keyword('autoescape', Const(True))]) """ - fields = ("options",) - options: t.List[Keyword] - + fields = ("options",) + options: t.List[Keyword] + class ScopedEvalContextModifier(EvalContextModifier): """Modifies the eval context and reverts it later. Works exactly like :class:`EvalContextModifier` but will only modify the :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`. """ - fields = ("body",) - body: t.List[Node] - + fields = ("body",) + body: t.List[Node] + # make sure nobody creates custom nodes -def _failing_new(*args: t.Any, **kwargs: t.Any) -> "te.NoReturn": - raise TypeError("can't create custom node types") - - -NodeType.__new__ = staticmethod(_failing_new) # type: ignore -del _failing_new +def _failing_new(*args: t.Any, **kwargs: t.Any) -> "te.NoReturn": + raise TypeError("can't create custom node types") + + +NodeType.__new__ = staticmethod(_failing_new) # type: ignore +del _failing_new diff --git a/contrib/python/Jinja2/py3/jinja2/optimizer.py b/contrib/python/Jinja2/py3/jinja2/optimizer.py index fe1010705e..597dece56d 100644 --- a/contrib/python/Jinja2/py3/jinja2/optimizer.py +++ b/contrib/python/Jinja2/py3/jinja2/optimizer.py @@ -1,47 +1,47 @@ -"""The optimizer tries to constant fold expressions and modify the AST -in place so that it should be faster to evaluate. - -Because the AST does not contain all the scoping information and the -compiler has to find that out, we cannot do all the optimizations we -want. For example, loop unrolling doesn't work because unrolled loops -would have a different scope. The solution would be a second syntax tree -that stored the scoping rules. +"""The optimizer tries to constant fold expressions and modify the AST +in place so that it should be faster to evaluate. + +Because the AST does not contain all the scoping information and the +compiler has to find that out, we cannot do all the optimizations we +want. For example, loop unrolling doesn't work because unrolled loops +would have a different scope. The solution would be a second syntax tree +that stored the scoping rules. """ -import typing as t - -from . import nodes -from .visitor import NodeTransformer - -if t.TYPE_CHECKING: - from .environment import Environment +import typing as t + +from . import nodes +from .visitor import NodeTransformer +if t.TYPE_CHECKING: + from .environment import Environment -def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node: + +def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node: """The context hint can be used to perform an static optimization based on the context given.""" optimizer = Optimizer(environment) - return t.cast(nodes.Node, optimizer.visit(node)) + return t.cast(nodes.Node, optimizer.visit(node)) class Optimizer(NodeTransformer): - def __init__(self, environment: "t.Optional[Environment]") -> None: + def __init__(self, environment: "t.Optional[Environment]") -> None: self.environment = environment - def generic_visit( - self, node: nodes.Node, *args: t.Any, **kwargs: t.Any - ) -> nodes.Node: - node = super().generic_visit(node, *args, **kwargs) - - # Do constant folding. Some other nodes besides Expr have - # as_const, but folding them causes errors later on. - if isinstance(node, nodes.Expr): - try: - return nodes.Const.from_untrusted( - node.as_const(args[0] if args else None), - lineno=node.lineno, - environment=self.environment, - ) - except nodes.Impossible: - pass - - return node + def generic_visit( + self, node: nodes.Node, *args: t.Any, **kwargs: t.Any + ) -> nodes.Node: + node = super().generic_visit(node, *args, **kwargs) + + # Do constant folding. Some other nodes besides Expr have + # as_const, but folding them causes errors later on. + if isinstance(node, nodes.Expr): + try: + return nodes.Const.from_untrusted( + node.as_const(args[0] if args else None), + lineno=node.lineno, + environment=self.environment, + ) + except nodes.Impossible: + pass + + return node diff --git a/contrib/python/Jinja2/py3/jinja2/parser.py b/contrib/python/Jinja2/py3/jinja2/parser.py index 7ad73fcc0f..e5f9698b96 100644 --- a/contrib/python/Jinja2/py3/jinja2/parser.py +++ b/contrib/python/Jinja2/py3/jinja2/parser.py @@ -1,82 +1,82 @@ -"""Parse tokens from the lexer into nodes for the compiler.""" -import typing -import typing as t - -from . import nodes -from .exceptions import TemplateAssertionError -from .exceptions import TemplateSyntaxError -from .lexer import describe_token -from .lexer import describe_token_expr - -if t.TYPE_CHECKING: - import typing_extensions as te - from .environment import Environment - -_ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include) -_MacroCall = t.TypeVar("_MacroCall", nodes.Macro, nodes.CallBlock) - -_statement_keywords = frozenset( - [ - "for", - "if", - "block", - "extends", - "print", - "macro", - "include", - "from", - "import", - "set", - "with", - "autoescape", - ] -) -_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"]) - -_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = { - "add": nodes.Add, - "sub": nodes.Sub, - "mul": nodes.Mul, - "div": nodes.Div, - "floordiv": nodes.FloorDiv, - "mod": nodes.Mod, +"""Parse tokens from the lexer into nodes for the compiler.""" +import typing +import typing as t + +from . import nodes +from .exceptions import TemplateAssertionError +from .exceptions import TemplateSyntaxError +from .lexer import describe_token +from .lexer import describe_token_expr + +if t.TYPE_CHECKING: + import typing_extensions as te + from .environment import Environment + +_ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include) +_MacroCall = t.TypeVar("_MacroCall", nodes.Macro, nodes.CallBlock) + +_statement_keywords = frozenset( + [ + "for", + "if", + "block", + "extends", + "print", + "macro", + "include", + "from", + "import", + "set", + "with", + "autoescape", + ] +) +_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"]) + +_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = { + "add": nodes.Add, + "sub": nodes.Sub, + "mul": nodes.Mul, + "div": nodes.Div, + "floordiv": nodes.FloorDiv, + "mod": nodes.Mod, } -class Parser: - """This is the central parsing class Jinja uses. It's passed to +class Parser: + """This is the central parsing class Jinja uses. It's passed to extensions and can be used to parse expressions or statements. """ - def __init__( - self, - environment: "Environment", - source: str, - name: t.Optional[str] = None, - filename: t.Optional[str] = None, - state: t.Optional[str] = None, - ) -> None: + def __init__( + self, + environment: "Environment", + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + state: t.Optional[str] = None, + ) -> None: self.environment = environment self.stream = environment._tokenize(source, name, filename, state) self.name = name self.filename = filename self.closed = False - self.extensions: t.Dict[ - str, t.Callable[["Parser"], t.Union[nodes.Node, t.List[nodes.Node]]] - ] = {} + self.extensions: t.Dict[ + str, t.Callable[["Parser"], t.Union[nodes.Node, t.List[nodes.Node]]] + ] = {} for extension in environment.iter_extensions(): for tag in extension.tags: self.extensions[tag] = extension.parse self._last_identifier = 0 - self._tag_stack: t.List[str] = [] - self._end_token_stack: t.List[t.Tuple[str, ...]] = [] - - def fail( - self, - msg: str, - lineno: t.Optional[int] = None, - exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError, - ) -> "te.NoReturn": + self._tag_stack: t.List[str] = [] + self._end_token_stack: t.List[t.Tuple[str, ...]] = [] + + def fail( + self, + msg: str, + lineno: t.Optional[int] = None, + exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError, + ) -> "te.NoReturn": """Convenience method that raises `exc` with the message, passed line number or last line number as well as the current name and filename. @@ -85,97 +85,97 @@ class Parser: lineno = self.stream.current.lineno raise exc(msg, lineno, self.name, self.filename) - def _fail_ut_eof( - self, - name: t.Optional[str], - end_token_stack: t.List[t.Tuple[str, ...]], - lineno: t.Optional[int], - ) -> "te.NoReturn": - expected: t.Set[str] = set() + def _fail_ut_eof( + self, + name: t.Optional[str], + end_token_stack: t.List[t.Tuple[str, ...]], + lineno: t.Optional[int], + ) -> "te.NoReturn": + expected: t.Set[str] = set() for exprs in end_token_stack: - expected.update(map(describe_token_expr, exprs)) + expected.update(map(describe_token_expr, exprs)) if end_token_stack: - currently_looking: t.Optional[str] = " or ".join( - map(repr, map(describe_token_expr, end_token_stack[-1])) - ) + currently_looking: t.Optional[str] = " or ".join( + map(repr, map(describe_token_expr, end_token_stack[-1])) + ) else: currently_looking = None if name is None: - message = ["Unexpected end of template."] + message = ["Unexpected end of template."] else: - message = [f"Encountered unknown tag {name!r}."] + message = [f"Encountered unknown tag {name!r}."] if currently_looking: if name is not None and name in expected: - message.append( - "You probably made a nesting mistake. Jinja is expecting this tag," - f" but currently looking for {currently_looking}." - ) + message.append( + "You probably made a nesting mistake. Jinja is expecting this tag," + f" but currently looking for {currently_looking}." + ) else: - message.append( - f"Jinja was looking for the following tags: {currently_looking}." - ) + message.append( + f"Jinja was looking for the following tags: {currently_looking}." + ) if self._tag_stack: - message.append( - "The innermost block that needs to be closed is" - f" {self._tag_stack[-1]!r}." - ) + message.append( + "The innermost block that needs to be closed is" + f" {self._tag_stack[-1]!r}." + ) - self.fail(" ".join(message), lineno) + self.fail(" ".join(message), lineno) - def fail_unknown_tag( - self, name: str, lineno: t.Optional[int] = None - ) -> "te.NoReturn": + def fail_unknown_tag( + self, name: str, lineno: t.Optional[int] = None + ) -> "te.NoReturn": """Called if the parser encounters an unknown tag. Tries to fail with a human readable error message that could help to identify the problem. """ - self._fail_ut_eof(name, self._end_token_stack, lineno) + self._fail_ut_eof(name, self._end_token_stack, lineno) - def fail_eof( - self, - end_tokens: t.Optional[t.Tuple[str, ...]] = None, - lineno: t.Optional[int] = None, - ) -> "te.NoReturn": + def fail_eof( + self, + end_tokens: t.Optional[t.Tuple[str, ...]] = None, + lineno: t.Optional[int] = None, + ) -> "te.NoReturn": """Like fail_unknown_tag but for end of template situations.""" stack = list(self._end_token_stack) if end_tokens is not None: stack.append(end_tokens) - self._fail_ut_eof(None, stack, lineno) + self._fail_ut_eof(None, stack, lineno) - def is_tuple_end( - self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None - ) -> bool: + def is_tuple_end( + self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None + ) -> bool: """Are we at the end of a tuple?""" - if self.stream.current.type in ("variable_end", "block_end", "rparen"): + if self.stream.current.type in ("variable_end", "block_end", "rparen"): return True elif extra_end_rules is not None: - return self.stream.current.test_any(extra_end_rules) # type: ignore + return self.stream.current.test_any(extra_end_rules) # type: ignore return False - def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName: + def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName: """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" self._last_identifier += 1 rv = object.__new__(nodes.InternalName) - nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno) - return rv # type: ignore + nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno) + return rv # type: ignore - def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]: + def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]: """Parse a single statement.""" token = self.stream.current - if token.type != "name": - self.fail("tag name expected", token.lineno) + if token.type != "name": + self.fail("tag name expected", token.lineno) self._tag_stack.append(token.value) pop_tag = True try: if token.value in _statement_keywords: - f = getattr(self, f"parse_{self.stream.current.value}") - return f() # type: ignore - if token.value == "call": + f = getattr(self, f"parse_{self.stream.current.value}") + return f() # type: ignore + if token.value == "call": return self.parse_call_block() - if token.value == "filter": + if token.value == "filter": return self.parse_filter_block() ext = self.extensions.get(token.value) if ext is not None: @@ -191,9 +191,9 @@ class Parser: if pop_tag: self._tag_stack.pop() - def parse_statements( - self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False - ) -> t.List[nodes.Node]: + def parse_statements( + self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False + ) -> t.List[nodes.Node]: """Parse multiple statements into a list until one of the end tokens is reached. This is used to parse the body of statements as it also parses template data if appropriate. The parser checks first if the @@ -204,282 +204,282 @@ class Parser: can be set to `True` and the end token is removed. """ # the first token may be a colon for python compatibility - self.stream.skip_if("colon") + self.stream.skip_if("colon") # in the future it would be possible to add whole code sections # by adding some sort of end of statement token and parsing those here. - self.stream.expect("block_end") + self.stream.expect("block_end") result = self.subparse(end_tokens) # we reached the end of the template too early, the subparser # does not check for this, so we do that now - if self.stream.current.type == "eof": + if self.stream.current.type == "eof": self.fail_eof(end_tokens) if drop_needle: next(self.stream) return result - def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]: + def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]: """Parse an assign statement.""" lineno = next(self.stream).lineno target = self.parse_assign_target(with_namespace=True) - if self.stream.skip_if("assign"): + if self.stream.skip_if("assign"): expr = self.parse_tuple() return nodes.Assign(target, expr, lineno=lineno) filter_node = self.parse_filter(None) - body = self.parse_statements(("name:endset",), drop_needle=True) + body = self.parse_statements(("name:endset",), drop_needle=True) return nodes.AssignBlock(target, filter_node, body, lineno=lineno) - def parse_for(self) -> nodes.For: + def parse_for(self) -> nodes.For: """Parse a for loop.""" - lineno = self.stream.expect("name:for").lineno - target = self.parse_assign_target(extra_end_rules=("name:in",)) - self.stream.expect("name:in") - iter = self.parse_tuple( - with_condexpr=False, extra_end_rules=("name:recursive",) - ) + lineno = self.stream.expect("name:for").lineno + target = self.parse_assign_target(extra_end_rules=("name:in",)) + self.stream.expect("name:in") + iter = self.parse_tuple( + with_condexpr=False, extra_end_rules=("name:recursive",) + ) test = None - if self.stream.skip_if("name:if"): + if self.stream.skip_if("name:if"): test = self.parse_expression() - recursive = self.stream.skip_if("name:recursive") - body = self.parse_statements(("name:endfor", "name:else")) - if next(self.stream).value == "endfor": + recursive = self.stream.skip_if("name:recursive") + body = self.parse_statements(("name:endfor", "name:else")) + if next(self.stream).value == "endfor": else_ = [] else: - else_ = self.parse_statements(("name:endfor",), drop_needle=True) - return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno) + else_ = self.parse_statements(("name:endfor",), drop_needle=True) + return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno) - def parse_if(self) -> nodes.If: + def parse_if(self) -> nodes.If: """Parse an if construct.""" - node = result = nodes.If(lineno=self.stream.expect("name:if").lineno) - while True: + node = result = nodes.If(lineno=self.stream.expect("name:if").lineno) + while True: node.test = self.parse_tuple(with_condexpr=False) - node.body = self.parse_statements(("name:elif", "name:else", "name:endif")) + node.body = self.parse_statements(("name:elif", "name:else", "name:endif")) node.elif_ = [] node.else_ = [] token = next(self.stream) - if token.test("name:elif"): + if token.test("name:elif"): node = nodes.If(lineno=self.stream.current.lineno) result.elif_.append(node) continue - elif token.test("name:else"): - result.else_ = self.parse_statements(("name:endif",), drop_needle=True) + elif token.test("name:else"): + result.else_ = self.parse_statements(("name:endif",), drop_needle=True) break return result - def parse_with(self) -> nodes.With: + def parse_with(self) -> nodes.With: node = nodes.With(lineno=next(self.stream).lineno) - targets: t.List[nodes.Expr] = [] - values: t.List[nodes.Expr] = [] - while self.stream.current.type != "block_end": + targets: t.List[nodes.Expr] = [] + values: t.List[nodes.Expr] = [] + while self.stream.current.type != "block_end": if targets: - self.stream.expect("comma") + self.stream.expect("comma") target = self.parse_assign_target() - target.set_ctx("param") + target.set_ctx("param") targets.append(target) - self.stream.expect("assign") + self.stream.expect("assign") values.append(self.parse_expression()) node.targets = targets node.values = values - node.body = self.parse_statements(("name:endwith",), drop_needle=True) + node.body = self.parse_statements(("name:endwith",), drop_needle=True) return node - def parse_autoescape(self) -> nodes.Scope: + def parse_autoescape(self) -> nodes.Scope: node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno) - node.options = [nodes.Keyword("autoescape", self.parse_expression())] - node.body = self.parse_statements(("name:endautoescape",), drop_needle=True) + node.options = [nodes.Keyword("autoescape", self.parse_expression())] + node.body = self.parse_statements(("name:endautoescape",), drop_needle=True) return nodes.Scope([node]) - def parse_block(self) -> nodes.Block: + def parse_block(self) -> nodes.Block: node = nodes.Block(lineno=next(self.stream).lineno) - node.name = self.stream.expect("name").value - node.scoped = self.stream.skip_if("name:scoped") - node.required = self.stream.skip_if("name:required") + node.name = self.stream.expect("name").value + node.scoped = self.stream.skip_if("name:scoped") + node.required = self.stream.skip_if("name:required") # common problem people encounter when switching from django # to jinja. we do not support hyphens in block names, so let's # raise a nicer error message in that case. - if self.stream.current.type == "sub": - self.fail( - "Block names in Jinja have to be valid Python identifiers and may not" - " contain hyphens, use an underscore instead." - ) - - node.body = self.parse_statements(("name:endblock",), drop_needle=True) - - # enforce that required blocks only contain whitespace or comments - # by asserting that the body, if not empty, is just TemplateData nodes - # with whitespace data - if node.required and not all( - isinstance(child, nodes.TemplateData) and child.data.isspace() - for body in node.body - for child in body.nodes # type: ignore - ): - self.fail("Required blocks can only contain comments or whitespace") - - self.stream.skip_if("name:" + node.name) + if self.stream.current.type == "sub": + self.fail( + "Block names in Jinja have to be valid Python identifiers and may not" + " contain hyphens, use an underscore instead." + ) + + node.body = self.parse_statements(("name:endblock",), drop_needle=True) + + # enforce that required blocks only contain whitespace or comments + # by asserting that the body, if not empty, is just TemplateData nodes + # with whitespace data + if node.required and not all( + isinstance(child, nodes.TemplateData) and child.data.isspace() + for body in node.body + for child in body.nodes # type: ignore + ): + self.fail("Required blocks can only contain comments or whitespace") + + self.stream.skip_if("name:" + node.name) return node - def parse_extends(self) -> nodes.Extends: + def parse_extends(self) -> nodes.Extends: node = nodes.Extends(lineno=next(self.stream).lineno) node.template = self.parse_expression() return node - def parse_import_context( - self, node: _ImportInclude, default: bool - ) -> _ImportInclude: - if self.stream.current.test_any( - "name:with", "name:without" - ) and self.stream.look().test("name:context"): - node.with_context = next(self.stream).value == "with" + def parse_import_context( + self, node: _ImportInclude, default: bool + ) -> _ImportInclude: + if self.stream.current.test_any( + "name:with", "name:without" + ) and self.stream.look().test("name:context"): + node.with_context = next(self.stream).value == "with" self.stream.skip() else: node.with_context = default return node - def parse_include(self) -> nodes.Include: + def parse_include(self) -> nodes.Include: node = nodes.Include(lineno=next(self.stream).lineno) node.template = self.parse_expression() - if self.stream.current.test("name:ignore") and self.stream.look().test( - "name:missing" - ): + if self.stream.current.test("name:ignore") and self.stream.look().test( + "name:missing" + ): node.ignore_missing = True self.stream.skip(2) else: node.ignore_missing = False return self.parse_import_context(node, True) - def parse_import(self) -> nodes.Import: + def parse_import(self) -> nodes.Import: node = nodes.Import(lineno=next(self.stream).lineno) node.template = self.parse_expression() - self.stream.expect("name:as") + self.stream.expect("name:as") node.target = self.parse_assign_target(name_only=True).name return self.parse_import_context(node, False) - def parse_from(self) -> nodes.FromImport: + def parse_from(self) -> nodes.FromImport: node = nodes.FromImport(lineno=next(self.stream).lineno) node.template = self.parse_expression() - self.stream.expect("name:import") + self.stream.expect("name:import") node.names = [] - def parse_context() -> bool: - if ( - self.stream.current.value - in { - "with", - "without", - } - and self.stream.look().test("name:context") - ): - node.with_context = next(self.stream).value == "with" + def parse_context() -> bool: + 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 return False - while True: + while True: if node.names: - self.stream.expect("comma") - if self.stream.current.type == "name": + self.stream.expect("comma") + if self.stream.current.type == "name": if parse_context(): break target = self.parse_assign_target(name_only=True) - if target.name.startswith("_"): - self.fail( - "names starting with an underline can not be imported", - target.lineno, - exc=TemplateAssertionError, - ) - if self.stream.skip_if("name:as"): + if target.name.startswith("_"): + self.fail( + "names starting with an underline can not be imported", + target.lineno, + exc=TemplateAssertionError, + ) + if self.stream.skip_if("name:as"): alias = self.parse_assign_target(name_only=True) node.names.append((target.name, alias.name)) else: node.names.append(target.name) - if parse_context() or self.stream.current.type != "comma": + if parse_context() or self.stream.current.type != "comma": break else: - self.stream.expect("name") - if not hasattr(node, "with_context"): + self.stream.expect("name") + if not hasattr(node, "with_context"): node.with_context = False return node - def parse_signature(self, node: _MacroCall) -> None: - args = node.args = [] - defaults = node.defaults = [] - self.stream.expect("lparen") - while self.stream.current.type != "rparen": + def parse_signature(self, node: _MacroCall) -> None: + args = node.args = [] + defaults = node.defaults = [] + self.stream.expect("lparen") + while self.stream.current.type != "rparen": if args: - self.stream.expect("comma") + self.stream.expect("comma") arg = self.parse_assign_target(name_only=True) - arg.set_ctx("param") - if self.stream.skip_if("assign"): + arg.set_ctx("param") + if self.stream.skip_if("assign"): defaults.append(self.parse_expression()) elif defaults: - self.fail("non-default argument follows default argument") + self.fail("non-default argument follows default argument") args.append(arg) - self.stream.expect("rparen") + self.stream.expect("rparen") - def parse_call_block(self) -> nodes.CallBlock: + def parse_call_block(self) -> nodes.CallBlock: node = nodes.CallBlock(lineno=next(self.stream).lineno) - if self.stream.current.type == "lparen": + if self.stream.current.type == "lparen": self.parse_signature(node) else: node.args = [] node.defaults = [] - call_node = self.parse_expression() - if not isinstance(call_node, nodes.Call): - self.fail("expected call", node.lineno) - node.call = call_node - node.body = self.parse_statements(("name:endcall",), drop_needle=True) + call_node = self.parse_expression() + if not isinstance(call_node, nodes.Call): + self.fail("expected call", node.lineno) + node.call = call_node + node.body = self.parse_statements(("name:endcall",), drop_needle=True) return node - def parse_filter_block(self) -> nodes.FilterBlock: + def parse_filter_block(self) -> nodes.FilterBlock: node = nodes.FilterBlock(lineno=next(self.stream).lineno) - node.filter = self.parse_filter(None, start_inline=True) # type: ignore - node.body = self.parse_statements(("name:endfilter",), drop_needle=True) + node.filter = self.parse_filter(None, start_inline=True) # type: ignore + node.body = self.parse_statements(("name:endfilter",), drop_needle=True) return node - def parse_macro(self) -> nodes.Macro: + def parse_macro(self) -> nodes.Macro: node = nodes.Macro(lineno=next(self.stream).lineno) node.name = self.parse_assign_target(name_only=True).name self.parse_signature(node) - node.body = self.parse_statements(("name:endmacro",), drop_needle=True) + node.body = self.parse_statements(("name:endmacro",), drop_needle=True) return node - def parse_print(self) -> nodes.Output: + def parse_print(self) -> nodes.Output: node = nodes.Output(lineno=next(self.stream).lineno) node.nodes = [] - while self.stream.current.type != "block_end": + while self.stream.current.type != "block_end": if node.nodes: - self.stream.expect("comma") + self.stream.expect("comma") node.nodes.append(self.parse_expression()) return node - @typing.overload - def parse_assign_target( - self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ... - ) -> nodes.Name: - ... - - @typing.overload - def parse_assign_target( - self, - with_tuple: bool = True, - name_only: bool = False, - extra_end_rules: t.Optional[t.Tuple[str, ...]] = None, - with_namespace: bool = False, - ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: - ... - - def parse_assign_target( - self, - with_tuple: bool = True, - name_only: bool = False, - extra_end_rules: t.Optional[t.Tuple[str, ...]] = None, - with_namespace: bool = False, - ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: - """Parse an assignment target. As Jinja allows assignments to + @typing.overload + def parse_assign_target( + self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ... + ) -> nodes.Name: + ... + + @typing.overload + def parse_assign_target( + self, + with_tuple: bool = True, + name_only: bool = False, + extra_end_rules: t.Optional[t.Tuple[str, ...]] = None, + with_namespace: bool = False, + ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: + ... + + def parse_assign_target( + self, + with_tuple: bool = True, + name_only: bool = False, + extra_end_rules: t.Optional[t.Tuple[str, ...]] = None, + with_namespace: bool = False, + ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: + """Parse an assignment target. As Jinja allows assignments to tuples, this function can parse all allowed assignment targets. Per default assignments to tuples are parsed, that can be disable however by setting `with_tuple` to `False`. If only assignments to names are @@ -487,34 +487,34 @@ class Parser: parameter is forwarded to the tuple parsing function. If `with_namespace` is enabled, a namespace assignment may be parsed. """ - target: nodes.Expr - - if with_namespace and self.stream.look().type == "dot": - token = self.stream.expect("name") + target: nodes.Expr + + if with_namespace and self.stream.look().type == "dot": + token = self.stream.expect("name") next(self.stream) # dot - attr = self.stream.expect("name") + attr = self.stream.expect("name") target = nodes.NSRef(token.value, attr.value, lineno=token.lineno) elif name_only: - token = self.stream.expect("name") - target = nodes.Name(token.value, "store", lineno=token.lineno) + token = self.stream.expect("name") + target = nodes.Name(token.value, "store", lineno=token.lineno) else: if with_tuple: - target = self.parse_tuple( - simplified=True, extra_end_rules=extra_end_rules - ) + target = self.parse_tuple( + simplified=True, extra_end_rules=extra_end_rules + ) else: target = self.parse_primary() - - target.set_ctx("store") - + + target.set_ctx("store") + if not target.can_assign(): - self.fail( - f"can't assign to {type(target).__name__.lower()!r}", target.lineno - ) - - return target # type: ignore + self.fail( + f"can't assign to {type(target).__name__.lower()!r}", target.lineno + ) - def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr: + return target # type: ignore + + def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr: """Parse an expression. Per default all expressions are parsed, if the optional `with_condexpr` parameter is set to `False` conditional expressions are not parsed. @@ -523,14 +523,14 @@ class Parser: return self.parse_condexpr() return self.parse_or() - def parse_condexpr(self) -> nodes.Expr: + def parse_condexpr(self) -> nodes.Expr: lineno = self.stream.current.lineno expr1 = self.parse_or() - expr3: t.Optional[nodes.Expr] - - while self.stream.skip_if("name:if"): + expr3: t.Optional[nodes.Expr] + + while self.stream.skip_if("name:if"): expr2 = self.parse_or() - if self.stream.skip_if("name:else"): + if self.stream.skip_if("name:else"): expr3 = self.parse_condexpr() else: expr3 = None @@ -538,46 +538,46 @@ class Parser: lineno = self.stream.current.lineno return expr1 - def parse_or(self) -> nodes.Expr: + def parse_or(self) -> nodes.Expr: lineno = self.stream.current.lineno left = self.parse_and() - while self.stream.skip_if("name:or"): + while self.stream.skip_if("name:or"): right = self.parse_and() left = nodes.Or(left, right, lineno=lineno) lineno = self.stream.current.lineno return left - def parse_and(self) -> nodes.Expr: + def parse_and(self) -> nodes.Expr: lineno = self.stream.current.lineno left = self.parse_not() - while self.stream.skip_if("name:and"): + while self.stream.skip_if("name:and"): right = self.parse_not() left = nodes.And(left, right, lineno=lineno) lineno = self.stream.current.lineno return left - def parse_not(self) -> nodes.Expr: - if self.stream.current.test("name:not"): + def parse_not(self) -> nodes.Expr: + if self.stream.current.test("name:not"): lineno = next(self.stream).lineno return nodes.Not(self.parse_not(), lineno=lineno) return self.parse_compare() - def parse_compare(self) -> nodes.Expr: + def parse_compare(self) -> nodes.Expr: lineno = self.stream.current.lineno expr = self.parse_math1() ops = [] - while True: + while True: token_type = self.stream.current.type if token_type in _compare_operators: next(self.stream) ops.append(nodes.Operand(token_type, self.parse_math1())) - elif self.stream.skip_if("name:in"): - ops.append(nodes.Operand("in", self.parse_math1())) - elif self.stream.current.test("name:not") and self.stream.look().test( - "name:in" - ): + elif self.stream.skip_if("name:in"): + ops.append(nodes.Operand("in", self.parse_math1())) + elif self.stream.current.test("name:not") and self.stream.look().test( + "name:in" + ): self.stream.skip(2) - ops.append(nodes.Operand("notin", self.parse_math1())) + ops.append(nodes.Operand("notin", self.parse_math1())) else: break lineno = self.stream.current.lineno @@ -585,10 +585,10 @@ class Parser: return expr return nodes.Compare(expr, ops, lineno=lineno) - def parse_math1(self) -> nodes.Expr: + def parse_math1(self) -> nodes.Expr: lineno = self.stream.current.lineno left = self.parse_concat() - while self.stream.current.type in ("add", "sub"): + while self.stream.current.type in ("add", "sub"): cls = _math_nodes[self.stream.current.type] next(self.stream) right = self.parse_concat() @@ -596,20 +596,20 @@ class Parser: lineno = self.stream.current.lineno return left - def parse_concat(self) -> nodes.Expr: + def parse_concat(self) -> nodes.Expr: lineno = self.stream.current.lineno args = [self.parse_math2()] - while self.stream.current.type == "tilde": + while self.stream.current.type == "tilde": next(self.stream) args.append(self.parse_math2()) if len(args) == 1: return args[0] return nodes.Concat(args, lineno=lineno) - def parse_math2(self) -> nodes.Expr: + def parse_math2(self) -> nodes.Expr: lineno = self.stream.current.lineno left = self.parse_pow() - while self.stream.current.type in ("mul", "div", "floordiv", "mod"): + while self.stream.current.type in ("mul", "div", "floordiv", "mod"): cls = _math_nodes[self.stream.current.type] next(self.stream) right = self.parse_pow() @@ -617,25 +617,25 @@ class Parser: lineno = self.stream.current.lineno return left - def parse_pow(self) -> nodes.Expr: + def parse_pow(self) -> nodes.Expr: lineno = self.stream.current.lineno left = self.parse_unary() - while self.stream.current.type == "pow": + while self.stream.current.type == "pow": next(self.stream) right = self.parse_unary() left = nodes.Pow(left, right, lineno=lineno) lineno = self.stream.current.lineno return left - def parse_unary(self, with_filter: bool = True) -> nodes.Expr: + def parse_unary(self, with_filter: bool = True) -> nodes.Expr: token_type = self.stream.current.type lineno = self.stream.current.lineno - node: nodes.Expr - - if token_type == "sub": + node: nodes.Expr + + if token_type == "sub": next(self.stream) node = nodes.Neg(self.parse_unary(False), lineno=lineno) - elif token_type == "add": + elif token_type == "add": next(self.stream) node = nodes.Pos(self.parse_unary(False), lineno=lineno) else: @@ -645,47 +645,47 @@ class Parser: node = self.parse_filter_expr(node) return node - def parse_primary(self) -> nodes.Expr: + def parse_primary(self) -> nodes.Expr: token = self.stream.current - node: nodes.Expr - if token.type == "name": - if token.value in ("true", "false", "True", "False"): - node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno) - elif token.value in ("none", "None"): + node: nodes.Expr + if token.type == "name": + if token.value in ("true", "false", "True", "False"): + node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno) + elif token.value in ("none", "None"): node = nodes.Const(None, lineno=token.lineno) else: - node = nodes.Name(token.value, "load", lineno=token.lineno) + node = nodes.Name(token.value, "load", lineno=token.lineno) next(self.stream) - elif token.type == "string": + elif token.type == "string": next(self.stream) buf = [token.value] lineno = token.lineno - while self.stream.current.type == "string": + while self.stream.current.type == "string": buf.append(self.stream.current.value) next(self.stream) - node = nodes.Const("".join(buf), lineno=lineno) - elif token.type in ("integer", "float"): + node = nodes.Const("".join(buf), lineno=lineno) + elif token.type in ("integer", "float"): next(self.stream) node = nodes.Const(token.value, lineno=token.lineno) - elif token.type == "lparen": + elif token.type == "lparen": next(self.stream) node = self.parse_tuple(explicit_parentheses=True) - self.stream.expect("rparen") - elif token.type == "lbracket": + self.stream.expect("rparen") + elif token.type == "lbracket": node = self.parse_list() - elif token.type == "lbrace": + elif token.type == "lbrace": node = self.parse_dict() else: - self.fail(f"unexpected {describe_token(token)!r}", token.lineno) + self.fail(f"unexpected {describe_token(token)!r}", token.lineno) return node - def parse_tuple( - self, - simplified: bool = False, - with_condexpr: bool = True, - extra_end_rules: t.Optional[t.Tuple[str, ...]] = None, - explicit_parentheses: bool = False, - ) -> t.Union[nodes.Tuple, nodes.Expr]: + def parse_tuple( + self, + simplified: bool = False, + with_condexpr: bool = True, + extra_end_rules: t.Optional[t.Tuple[str, ...]] = None, + explicit_parentheses: bool = False, + ) -> t.Union[nodes.Tuple, nodes.Expr]: """Works like `parse_expression` but if multiple expressions are delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created. This method could also return a regular expression instead of a tuple @@ -710,20 +710,20 @@ class Parser: elif with_condexpr: parse = self.parse_expression else: - - def parse() -> nodes.Expr: - return self.parse_expression(with_condexpr=False) - - args: t.List[nodes.Expr] = [] + + def parse() -> nodes.Expr: + return self.parse_expression(with_condexpr=False) + + args: t.List[nodes.Expr] = [] is_tuple = False - - while True: + + while True: if args: - self.stream.expect("comma") + self.stream.expect("comma") if self.is_tuple_end(extra_end_rules): break args.append(parse()) - if self.stream.current.type == "comma": + if self.stream.current.type == "comma": is_tuple = True else: break @@ -738,123 +738,123 @@ class Parser: # nothing) in the spot of an expression would be an empty # tuple. if not explicit_parentheses: - self.fail( - "Expected an expression," - f" got {describe_token(self.stream.current)!r}" - ) + self.fail( + "Expected an expression," + f" got {describe_token(self.stream.current)!r}" + ) - return nodes.Tuple(args, "load", lineno=lineno) + return nodes.Tuple(args, "load", lineno=lineno) - def parse_list(self) -> nodes.List: - token = self.stream.expect("lbracket") - items: t.List[nodes.Expr] = [] - while self.stream.current.type != "rbracket": + def parse_list(self) -> nodes.List: + token = self.stream.expect("lbracket") + items: t.List[nodes.Expr] = [] + while self.stream.current.type != "rbracket": if items: - self.stream.expect("comma") - if self.stream.current.type == "rbracket": + self.stream.expect("comma") + if self.stream.current.type == "rbracket": break items.append(self.parse_expression()) - self.stream.expect("rbracket") + self.stream.expect("rbracket") return nodes.List(items, lineno=token.lineno) - def parse_dict(self) -> nodes.Dict: - token = self.stream.expect("lbrace") - items: t.List[nodes.Pair] = [] - while self.stream.current.type != "rbrace": + def parse_dict(self) -> nodes.Dict: + token = self.stream.expect("lbrace") + items: t.List[nodes.Pair] = [] + while self.stream.current.type != "rbrace": if items: - self.stream.expect("comma") - if self.stream.current.type == "rbrace": + self.stream.expect("comma") + if self.stream.current.type == "rbrace": break key = self.parse_expression() - self.stream.expect("colon") + self.stream.expect("colon") value = self.parse_expression() items.append(nodes.Pair(key, value, lineno=key.lineno)) - self.stream.expect("rbrace") + self.stream.expect("rbrace") return nodes.Dict(items, lineno=token.lineno) - def parse_postfix(self, node: nodes.Expr) -> nodes.Expr: - while True: + def parse_postfix(self, node: nodes.Expr) -> nodes.Expr: + while True: token_type = self.stream.current.type - if token_type == "dot" or token_type == "lbracket": + if token_type == "dot" or token_type == "lbracket": node = self.parse_subscript(node) # calls are valid both after postfix expressions (getattr # and getitem) as well as filters and tests - elif token_type == "lparen": + elif token_type == "lparen": node = self.parse_call(node) else: break return node - def parse_filter_expr(self, node: nodes.Expr) -> nodes.Expr: - while True: + def parse_filter_expr(self, node: nodes.Expr) -> nodes.Expr: + while True: token_type = self.stream.current.type - if token_type == "pipe": - node = self.parse_filter(node) # type: ignore - elif token_type == "name" and self.stream.current.value == "is": + if token_type == "pipe": + node = self.parse_filter(node) # type: ignore + elif token_type == "name" and self.stream.current.value == "is": node = self.parse_test(node) # calls are valid both after postfix expressions (getattr # and getitem) as well as filters and tests - elif token_type == "lparen": + elif token_type == "lparen": node = self.parse_call(node) else: break return node - def parse_subscript( - self, node: nodes.Expr - ) -> t.Union[nodes.Getattr, nodes.Getitem]: + def parse_subscript( + self, node: nodes.Expr + ) -> t.Union[nodes.Getattr, nodes.Getitem]: token = next(self.stream) - arg: nodes.Expr - - if token.type == "dot": + arg: nodes.Expr + + if token.type == "dot": attr_token = self.stream.current next(self.stream) - if attr_token.type == "name": - return nodes.Getattr( - node, attr_token.value, "load", lineno=token.lineno - ) - elif attr_token.type != "integer": - self.fail("expected name or number", attr_token.lineno) + if attr_token.type == "name": + return nodes.Getattr( + node, attr_token.value, "load", lineno=token.lineno + ) + elif attr_token.type != "integer": + self.fail("expected name or number", attr_token.lineno) arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) - return nodes.Getitem(node, arg, "load", lineno=token.lineno) - if token.type == "lbracket": - args: t.List[nodes.Expr] = [] - while self.stream.current.type != "rbracket": + return nodes.Getitem(node, arg, "load", lineno=token.lineno) + if token.type == "lbracket": + args: t.List[nodes.Expr] = [] + while self.stream.current.type != "rbracket": if args: - self.stream.expect("comma") + self.stream.expect("comma") args.append(self.parse_subscribed()) - self.stream.expect("rbracket") + self.stream.expect("rbracket") if len(args) == 1: arg = args[0] else: - arg = nodes.Tuple(args, "load", lineno=token.lineno) - return nodes.Getitem(node, arg, "load", lineno=token.lineno) - self.fail("expected subscript expression", token.lineno) + arg = nodes.Tuple(args, "load", lineno=token.lineno) + return nodes.Getitem(node, arg, "load", lineno=token.lineno) + self.fail("expected subscript expression", token.lineno) - def parse_subscribed(self) -> nodes.Expr: + def parse_subscribed(self) -> nodes.Expr: lineno = self.stream.current.lineno - args: t.List[t.Optional[nodes.Expr]] + args: t.List[t.Optional[nodes.Expr]] - if self.stream.current.type == "colon": + if self.stream.current.type == "colon": next(self.stream) args = [None] else: node = self.parse_expression() - if self.stream.current.type != "colon": + if self.stream.current.type != "colon": return node next(self.stream) args = [node] - if self.stream.current.type == "colon": + if self.stream.current.type == "colon": args.append(None) - elif self.stream.current.type not in ("rbracket", "comma"): + elif self.stream.current.type not in ("rbracket", "comma"): args.append(self.parse_expression()) else: args.append(None) - if self.stream.current.type == "colon": + if self.stream.current.type == "colon": next(self.stream) - if self.stream.current.type not in ("rbracket", "comma"): + if self.stream.current.type not in ("rbracket", "comma"): args.append(self.parse_expression()) else: args.append(None) @@ -863,138 +863,138 @@ class Parser: return nodes.Slice(lineno=lineno, *args) - def parse_call_args(self) -> t.Tuple: - token = self.stream.expect("lparen") + def parse_call_args(self) -> t.Tuple: + token = self.stream.expect("lparen") args = [] kwargs = [] - dyn_args = None - dyn_kwargs = None + dyn_args = None + dyn_kwargs = None require_comma = False - def ensure(expr: bool) -> None: + def ensure(expr: bool) -> None: if not expr: - self.fail("invalid syntax for function call expression", token.lineno) + self.fail("invalid syntax for function call expression", token.lineno) - while self.stream.current.type != "rparen": + while self.stream.current.type != "rparen": if require_comma: - self.stream.expect("comma") - + self.stream.expect("comma") + # support for trailing comma - if self.stream.current.type == "rparen": + if self.stream.current.type == "rparen": break - - if self.stream.current.type == "mul": + + if self.stream.current.type == "mul": ensure(dyn_args is None and dyn_kwargs is None) next(self.stream) dyn_args = self.parse_expression() - elif self.stream.current.type == "pow": + elif self.stream.current.type == "pow": ensure(dyn_kwargs is None) next(self.stream) dyn_kwargs = self.parse_expression() else: - if ( - self.stream.current.type == "name" - and self.stream.look().type == "assign" - ): - # Parsing a kwarg - ensure(dyn_kwargs is None) + if ( + self.stream.current.type == "name" + and self.stream.look().type == "assign" + ): + # Parsing a kwarg + ensure(dyn_kwargs is None) key = self.stream.current.value self.stream.skip(2) value = self.parse_expression() - kwargs.append(nodes.Keyword(key, value, lineno=value.lineno)) + kwargs.append(nodes.Keyword(key, value, lineno=value.lineno)) else: - # Parsing an arg - ensure(dyn_args is None and dyn_kwargs is None and not kwargs) + # Parsing an arg + ensure(dyn_args is None and dyn_kwargs is None and not kwargs) args.append(self.parse_expression()) require_comma = True - - self.stream.expect("rparen") - return args, kwargs, dyn_args, dyn_kwargs - - def parse_call(self, node: nodes.Expr) -> nodes.Call: - # The lparen will be expected in parse_call_args, but the lineno - # needs to be recorded before the stream is advanced. - token = self.stream.current - args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() - return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) - - def parse_filter( - self, node: t.Optional[nodes.Expr], start_inline: bool = False - ) -> t.Optional[nodes.Expr]: - while self.stream.current.type == "pipe" or start_inline: + + self.stream.expect("rparen") + return args, kwargs, dyn_args, dyn_kwargs + + def parse_call(self, node: nodes.Expr) -> nodes.Call: + # The lparen will be expected in parse_call_args, but the lineno + # needs to be recorded before the stream is advanced. + token = self.stream.current + args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() + return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) + + def parse_filter( + self, node: t.Optional[nodes.Expr], start_inline: bool = False + ) -> t.Optional[nodes.Expr]: + while self.stream.current.type == "pipe" or start_inline: if not start_inline: next(self.stream) - token = self.stream.expect("name") + token = self.stream.expect("name") name = token.value - while self.stream.current.type == "dot": + while self.stream.current.type == "dot": next(self.stream) - name += "." + self.stream.expect("name").value - if self.stream.current.type == "lparen": - args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() + name += "." + self.stream.expect("name").value + if self.stream.current.type == "lparen": + args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() else: args = [] kwargs = [] dyn_args = dyn_kwargs = None - node = nodes.Filter( - node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno - ) + node = nodes.Filter( + node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno + ) start_inline = False return node - def parse_test(self, node: nodes.Expr) -> nodes.Expr: + def parse_test(self, node: nodes.Expr) -> nodes.Expr: token = next(self.stream) - if self.stream.current.test("name:not"): + if self.stream.current.test("name:not"): next(self.stream) negated = True else: negated = False - name = self.stream.expect("name").value - while self.stream.current.type == "dot": + name = self.stream.expect("name").value + while self.stream.current.type == "dot": next(self.stream) - name += "." + self.stream.expect("name").value + name += "." + self.stream.expect("name").value dyn_args = dyn_kwargs = None 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") - ): - if self.stream.current.test("name:is"): - self.fail("You cannot chain multiple tests with is") - arg_node = self.parse_primary() - arg_node = self.parse_postfix(arg_node) - args = [arg_node] + 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") + ): + if self.stream.current.test("name:is"): + self.fail("You cannot chain multiple tests with is") + 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, dyn_kwargs, lineno=token.lineno - ) + node = nodes.Test( + node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno + ) if negated: node = nodes.Not(node, lineno=token.lineno) return node - def subparse( - self, end_tokens: t.Optional[t.Tuple[str, ...]] = None - ) -> t.List[nodes.Node]: - body: t.List[nodes.Node] = [] - data_buffer: t.List[nodes.Node] = [] + def subparse( + self, end_tokens: t.Optional[t.Tuple[str, ...]] = None + ) -> t.List[nodes.Node]: + body: t.List[nodes.Node] = [] + data_buffer: t.List[nodes.Node] = [] add_data = data_buffer.append if end_tokens is not None: self._end_token_stack.append(end_tokens) - def flush_data() -> None: + def flush_data() -> None: if data_buffer: lineno = data_buffer[0].lineno body.append(nodes.Output(data_buffer[:], lineno=lineno)) @@ -1003,29 +1003,29 @@ class Parser: try: while self.stream: token = self.stream.current - if token.type == "data": + if token.type == "data": if token.value: - add_data(nodes.TemplateData(token.value, lineno=token.lineno)) + add_data(nodes.TemplateData(token.value, lineno=token.lineno)) next(self.stream) - elif token.type == "variable_begin": + elif token.type == "variable_begin": next(self.stream) add_data(self.parse_tuple(with_condexpr=True)) - self.stream.expect("variable_end") - elif token.type == "block_begin": + self.stream.expect("variable_end") + elif token.type == "block_begin": flush_data() next(self.stream) - if end_tokens is not None and self.stream.current.test_any( - *end_tokens - ): + if end_tokens is not None and self.stream.current.test_any( + *end_tokens + ): return body rv = self.parse_statement() if isinstance(rv, list): body.extend(rv) else: body.append(rv) - self.stream.expect("block_end") + self.stream.expect("block_end") else: - raise AssertionError("internal parsing error") + raise AssertionError("internal parsing error") flush_data() finally: @@ -1033,7 +1033,7 @@ class Parser: self._end_token_stack.pop() return body - def parse(self) -> nodes.Template: + def parse(self) -> nodes.Template: """Parse the whole template into a `Template` node.""" result = nodes.Template(self.subparse(), lineno=1) result.set_environment(self.environment) diff --git a/contrib/python/Jinja2/py3/jinja2/runtime.py b/contrib/python/Jinja2/py3/jinja2/runtime.py index 2346cf2bac..93dfcf3313 100644 --- a/contrib/python/Jinja2/py3/jinja2/runtime.py +++ b/contrib/python/Jinja2/py3/jinja2/runtime.py @@ -1,116 +1,116 @@ -"""The runtime functions and state used by compiled templates.""" -import functools +"""The runtime functions and state used by compiled templates.""" +import functools import sys -import typing as t -from collections import abc +import typing as t +from collections import abc from itertools import chain -from markupsafe import escape # noqa: F401 -from markupsafe import Markup -from markupsafe import soft_str - -from .async_utils import auto_aiter -from .async_utils import auto_await # noqa: F401 -from .exceptions import TemplateNotFound # noqa: F401 -from .exceptions import TemplateRuntimeError # noqa: F401 -from .exceptions import UndefinedError -from .nodes import EvalContext -from .utils import _PassArg -from .utils import concat -from .utils import internalcode -from .utils import missing -from .utils import Namespace # noqa: F401 -from .utils import object_type_repr -from .utils import pass_eval_context - -V = t.TypeVar("V") -F = t.TypeVar("F", bound=t.Callable[..., t.Any]) - -if t.TYPE_CHECKING: - import logging - import typing_extensions as te - from .environment import Environment - - class LoopRenderFunc(te.Protocol): - def __call__( - self, - reciter: t.Iterable[V], - loop_render_func: "LoopRenderFunc", - depth: int = 0, - ) -> str: - ... - - +from markupsafe import escape # noqa: F401 +from markupsafe import Markup +from markupsafe import soft_str + +from .async_utils import auto_aiter +from .async_utils import auto_await # noqa: F401 +from .exceptions import TemplateNotFound # noqa: F401 +from .exceptions import TemplateRuntimeError # noqa: F401 +from .exceptions import UndefinedError +from .nodes import EvalContext +from .utils import _PassArg +from .utils import concat +from .utils import internalcode +from .utils import missing +from .utils import Namespace # noqa: F401 +from .utils import object_type_repr +from .utils import pass_eval_context + +V = t.TypeVar("V") +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +if t.TYPE_CHECKING: + import logging + import typing_extensions as te + from .environment import Environment + + class LoopRenderFunc(te.Protocol): + def __call__( + self, + reciter: t.Iterable[V], + loop_render_func: "LoopRenderFunc", + depth: int = 0, + ) -> str: + ... + + # these variables are exported to the template runtime -exported = [ - "LoopContext", - "TemplateReference", - "Macro", - "Markup", - "TemplateRuntimeError", - "missing", - "concat", - "escape", - "markup_join", - "str_join", - "identity", - "TemplateNotFound", - "Namespace", - "Undefined", - "internalcode", -] -async_exported = [ - "AsyncLoopContext", - "auto_aiter", - "auto_await", -] - - -def identity(x: V) -> V: - """Returns its argument. Useful for certain things in the - environment. - """ - return x - - -def markup_join(seq: t.Iterable[t.Any]) -> str: - """Concatenation that escapes if necessary and converts to string.""" +exported = [ + "LoopContext", + "TemplateReference", + "Macro", + "Markup", + "TemplateRuntimeError", + "missing", + "concat", + "escape", + "markup_join", + "str_join", + "identity", + "TemplateNotFound", + "Namespace", + "Undefined", + "internalcode", +] +async_exported = [ + "AsyncLoopContext", + "auto_aiter", + "auto_await", +] + + +def identity(x: V) -> V: + """Returns its argument. Useful for certain things in the + environment. + """ + return x + + +def markup_join(seq: t.Iterable[t.Any]) -> str: + """Concatenation that escapes if necessary and converts to string.""" buf = [] - iterator = map(soft_str, seq) + iterator = map(soft_str, seq) for arg in iterator: buf.append(arg) - if hasattr(arg, "__html__"): - return Markup("").join(chain(buf, iterator)) + if hasattr(arg, "__html__"): + return Markup("").join(chain(buf, iterator)) return concat(buf) -def str_join(seq: t.Iterable[t.Any]) -> str: - """Simple args to string conversion and concatenation.""" - 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], - blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]], - vars: t.Optional[t.Dict[str, t.Any]] = None, - shared: bool = False, - globals: t.Optional[t.MutableMapping[str, t.Any]] = None, - locals: t.Optional[t.Mapping[str, t.Any]] = None, -) -> "Context": - """Internal helper for context creation.""" +def str_join(seq: t.Iterable[t.Any]) -> str: + """Simple args to string conversion and concatenation.""" + 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], + blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]], + vars: t.Optional[t.Dict[str, t.Any]] = None, + shared: bool = False, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, + locals: t.Optional[t.Mapping[str, t.Any]] = None, +) -> "Context": + """Internal helper for context creation.""" if vars is None: vars = {} if shared: @@ -122,38 +122,38 @@ def new_context( # we don't want to modify the dict passed if shared: parent = dict(parent) - for key, value in locals.items(): + for key, value in locals.items(): if value is not missing: parent[key] = value - return environment.context_class( - environment, parent, template_name, blocks, globals=globals - ) + return environment.context_class( + environment, parent, template_name, blocks, globals=globals + ) -class TemplateReference: +class TemplateReference: """The `self` in templates.""" - def __init__(self, context: "Context") -> None: + def __init__(self, context: "Context") -> None: self.__context = context - def __getitem__(self, name: str) -> t.Any: + def __getitem__(self, name: str) -> t.Any: blocks = self.__context.blocks[name] return BlockReference(name, self.__context, blocks, 0) - def __repr__(self) -> str: - return f"<{type(self).__name__} {self.__context.name!r}>" + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.__context.name!r}>" -def _dict_method_all(dict_method: F) -> F: - @functools.wraps(dict_method) - def f_all(self: "Context") -> t.Any: - return dict_method(self.get_all()) +def _dict_method_all(dict_method: F) -> F: + @functools.wraps(dict_method) + def f_all(self: "Context") -> t.Any: + return dict_method(self.get_all()) - return t.cast(F, f_all) + return t.cast(F, f_all) -@abc.Mapping.register -class Context: +@abc.Mapping.register +class Context: """The template context holds the variables of a template. It stores the values passed to the template and also the names the template exports. Creating instances is neither supported nor useful as it's created @@ -163,7 +163,7 @@ class Context: The context is immutable. Modifications on :attr:`parent` **must not** happen and modifications on :attr:`vars` are allowed from generated template code only. Template filters and global functions marked as - :func:`pass_context` get the active context passed as first argument + :func:`pass_context` get the active context passed as first argument and are allowed to access the context read-only. The template context supports read only dict operations (`get`, @@ -172,132 +172,132 @@ class Context: method that doesn't fail with a `KeyError` but returns an :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", - parent: t.Dict[str, t.Any], - name: t.Optional[str], - blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]], - globals: t.Optional[t.MutableMapping[str, t.Any]] = None, - ): + + _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", + parent: t.Dict[str, t.Any], + name: t.Optional[str], + blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]], + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, + ): self.parent = parent - self.vars: t.Dict[str, t.Any] = {} - self.environment: "Environment" = environment + self.vars: t.Dict[str, t.Any] = {} + self.environment: "Environment" = environment self.eval_ctx = EvalContext(self.environment, name) - self.exported_vars: t.Set[str] = set() + self.exported_vars: t.Set[str] = set() self.name = name - self.globals_keys = set() if globals is None else set(globals) + self.globals_keys = set() if globals is None else set(globals) # create the initial mapping of blocks. Whenever template inheritance # takes place the runtime will update this mapping with the new blocks # from the template. - self.blocks = {k: [v] for k, v in blocks.items()} + self.blocks = {k: [v] for k, v in blocks.items()} - def super( - self, name: str, current: t.Callable[["Context"], t.Iterator[str]] - ) -> t.Union["BlockReference", "Undefined"]: + def super( + self, name: str, current: t.Callable[["Context"], t.Iterator[str]] + ) -> t.Union["BlockReference", "Undefined"]: """Render a parent block.""" try: blocks = self.blocks[name] index = blocks.index(current) + 1 blocks[index] except LookupError: - return self.environment.undefined( - f"there is no parent block called {name!r}.", name="super" - ) + return self.environment.undefined( + f"there is no parent block called {name!r}.", name="super" + ) return BlockReference(name, self, blocks, index) - def get(self, key: str, default: t.Any = None) -> t.Any: - """Look up a variable by name, or return a default if the key is - not found. - - :param key: The variable name to look up. - :param default: The value to return if the key is not found. + def get(self, key: str, default: t.Any = None) -> t.Any: + """Look up a variable by name, or return a default if the key is + not found. + + :param key: The variable name to look up. + :param default: The value to return if the key is not found. """ try: return self[key] except KeyError: return default - def resolve(self, key: str) -> t.Union[t.Any, "Undefined"]: - """Look up a variable by name, or return an :class:`Undefined` - object if the key is not found. - - If you need to add custom behavior, override - :meth:`resolve_or_missing`, not this method. The various lookup - functions use that method, not this one. - - :param key: The variable name to look up. + def resolve(self, key: str) -> t.Union[t.Any, "Undefined"]: + """Look up a variable by name, or return an :class:`Undefined` + object if the key is not found. + + If you need to add custom behavior, override + :meth:`resolve_or_missing`, not this method. The various lookup + functions use that method, not this one. + + :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 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: return self.environment.undefined(name=key) - + return rv - def resolve_or_missing(self, key: str) -> t.Any: - """Look up a variable by name, or return a ``missing`` sentinel - if the key is not found. - - Override this method to add custom lookup behavior. - :meth:`resolve`, :meth:`get`, and :meth:`__getitem__` use this - method. Don't call this method directly. - - :param key: The variable name to look up. + def resolve_or_missing(self, key: str) -> t.Any: + """Look up a variable by name, or return a ``missing`` sentinel + if the key is not found. + + Override this method to add custom lookup behavior. + :meth:`resolve`, :meth:`get`, and :meth:`__getitem__` use this + method. Don't call this method directly. + + :param key: The variable name to look up. """ if self._legacy_resolve_mode: rv = self.resolve(key) - + if isinstance(rv, Undefined): - return missing - + return missing + return rv - if key in self.vars: - return self.vars[key] - - if key in self.parent: - return self.parent[key] - - return missing - - def get_exported(self) -> t.Dict[str, t.Any]: + if key in self.vars: + return self.vars[key] + + if key in self.parent: + return self.parent[key] + + return missing + + def get_exported(self) -> t.Dict[str, t.Any]: """Get a new dict with the exported variables.""" - return {k: self.vars[k] for k in self.exported_vars} + return {k: self.vars[k] for k in self.exported_vars} - def get_all(self) -> t.Dict[str, t.Any]: + def get_all(self) -> t.Dict[str, t.Any]: """Return the complete context as dict including the exported variables. For optimizations reasons this might not return an actual copy so be careful with using it. @@ -309,414 +309,414 @@ class Context: return dict(self.parent, **self.vars) @internalcode - def call( - __self, __obj: t.Callable, *args: t.Any, **kwargs: t.Any # noqa: B902 - ) -> t.Union[t.Any, "Undefined"]: + def call( + __self, __obj: t.Callable, *args: t.Any, **kwargs: t.Any # noqa: B902 + ) -> t.Union[t.Any, "Undefined"]: """Call the callable with the arguments and keyword arguments provided but inject the active context or environment as first - argument if the callable has :func:`pass_context` or - :func:`pass_environment`. + argument if the callable has :func:`pass_context` or + :func:`pass_environment`. """ if __debug__: __traceback_hide__ = True # noqa # Allow callable classes to take a context - if ( - hasattr(__obj, "__call__") # noqa: B004 - and _PassArg.from_obj(__obj.__call__) is not None # type: ignore - ): - __obj = __obj.__call__ # type: ignore - - pass_arg = _PassArg.from_obj(__obj) - - if pass_arg is _PassArg.context: - # the active context should have access to variables set in - # loops and blocks without mutating the context itself - if kwargs.get("_loop_vars"): - __self = __self.derived(kwargs["_loop_vars"]) - if kwargs.get("_block_vars"): - __self = __self.derived(kwargs["_block_vars"]) - args = (__self,) + args - elif pass_arg is _PassArg.eval_context: - args = (__self.eval_ctx,) + args - elif pass_arg is _PassArg.environment: - args = (__self.environment,) + args - - kwargs.pop("_block_vars", None) - kwargs.pop("_loop_vars", None) - + if ( + hasattr(__obj, "__call__") # noqa: B004 + and _PassArg.from_obj(__obj.__call__) is not None # type: ignore + ): + __obj = __obj.__call__ # type: ignore + + pass_arg = _PassArg.from_obj(__obj) + + if pass_arg is _PassArg.context: + # the active context should have access to variables set in + # loops and blocks without mutating the context itself + if kwargs.get("_loop_vars"): + __self = __self.derived(kwargs["_loop_vars"]) + if kwargs.get("_block_vars"): + __self = __self.derived(kwargs["_block_vars"]) + args = (__self,) + args + elif pass_arg is _PassArg.eval_context: + args = (__self.eval_ctx,) + args + elif pass_arg is _PassArg.environment: + args = (__self.environment,) + args + + kwargs.pop("_block_vars", None) + kwargs.pop("_loop_vars", None) + try: return __obj(*args, **kwargs) except StopIteration: - return __self.environment.undefined( - "value was undefined because a callable raised a" - " StopIteration exception" - ) + return __self.environment.undefined( + "value was undefined because a callable raised a" + " StopIteration exception" + ) - def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> "Context": + def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> "Context": """Internal helper function to create a derived context. This is used in situations where the system needs a new context in the same template that is independent. """ - context = new_context( - self.environment, self.name, {}, self.get_all(), True, None, locals - ) + context = new_context( + self.environment, self.name, {}, self.get_all(), True, None, locals + ) context.eval_ctx = self.eval_ctx - context.blocks.update((k, list(v)) for k, v in self.blocks.items()) + context.blocks.update((k, list(v)) for k, v in self.blocks.items()) return context - keys = _dict_method_all(dict.keys) - values = _dict_method_all(dict.values) - items = _dict_method_all(dict.items) - - def __contains__(self, name: str) -> bool: + keys = _dict_method_all(dict.keys) + values = _dict_method_all(dict.values) + items = _dict_method_all(dict.items) + + def __contains__(self, name: str) -> bool: return name in self.vars or name in self.parent - def __getitem__(self, key: str) -> t.Any: - """Look up a variable by name with ``[]`` syntax, or raise a - ``KeyError`` if the key is not found. + def __getitem__(self, key: str) -> t.Any: + """Look up a variable by name with ``[]`` syntax, or raise a + ``KeyError`` if the key is not found. """ item = self.resolve_or_missing(key) - + if item is missing: raise KeyError(key) - + return item - def __repr__(self) -> str: - return f"<{type(self).__name__} {self.get_all()!r} of {self.name!r}>" + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.get_all()!r} of {self.name!r}>" -class BlockReference: +class BlockReference: """One block on a template reference.""" - def __init__( - self, - name: str, - context: "Context", - stack: t.List[t.Callable[["Context"], t.Iterator[str]]], - depth: int, - ) -> None: + def __init__( + self, + name: str, + context: "Context", + stack: t.List[t.Callable[["Context"], t.Iterator[str]]], + depth: int, + ) -> None: self.name = name self._context = context self._stack = stack self._depth = depth @property - def super(self) -> t.Union["BlockReference", "Undefined"]: + def super(self) -> t.Union["BlockReference", "Undefined"]: """Super the block.""" if self._depth + 1 >= len(self._stack): - return self._context.environment.undefined( - f"there is no parent block called {self.name!r}.", name="super" - ) - return BlockReference(self.name, self._context, self._stack, self._depth + 1) + return self._context.environment.undefined( + f"there is no parent block called {self.name!r}.", name="super" + ) + return BlockReference(self.name, self._context, self._stack, self._depth + 1) @internalcode - async def _async_call(self) -> str: - rv = concat( - [x async for x in self._stack[self._depth](self._context)] # type: ignore - ) - - if self._context.eval_ctx.autoescape: - return Markup(rv) - - return rv - - @internalcode - def __call__(self) -> str: - if self._context.environment.is_async: - return self._async_call() # type: ignore - + async def _async_call(self) -> str: + rv = concat( + [x async for x in self._stack[self._depth](self._context)] # type: ignore + ) + + if self._context.eval_ctx.autoescape: + return Markup(rv) + + return rv + + @internalcode + def __call__(self) -> str: + if self._context.environment.is_async: + return self._async_call() # type: ignore + rv = concat(self._stack[self._depth](self._context)) - + if self._context.eval_ctx.autoescape: - return Markup(rv) - + return Markup(rv) + return rv -class LoopContext: - """A wrapper iterable for dynamic ``for`` loops, with information - about the loop and iteration. - """ - - #: Current iteration of the loop, starting at 0. - index0 = -1 - - _length: t.Optional[int] = None - _after: t.Any = missing - _current: t.Any = missing - _before: t.Any = missing - _last_changed_value: t.Any = missing - - def __init__( - self, - iterable: t.Iterable[V], - undefined: t.Type["Undefined"], - recurse: t.Optional["LoopRenderFunc"] = None, - depth0: int = 0, - ) -> None: - """ - :param iterable: Iterable to wrap. - :param undefined: :class:`Undefined` class to use for next and - previous items. - :param recurse: The function to render the loop body when the - loop is marked recursive. - :param depth0: Incremented when looping recursively. - """ - self._iterable = iterable - self._iterator = self._to_iterator(iterable) +class LoopContext: + """A wrapper iterable for dynamic ``for`` loops, with information + about the loop and iteration. + """ + + #: Current iteration of the loop, starting at 0. + index0 = -1 + + _length: t.Optional[int] = None + _after: t.Any = missing + _current: t.Any = missing + _before: t.Any = missing + _last_changed_value: t.Any = missing + + def __init__( + self, + iterable: t.Iterable[V], + undefined: t.Type["Undefined"], + recurse: t.Optional["LoopRenderFunc"] = None, + depth0: int = 0, + ) -> None: + """ + :param iterable: Iterable to wrap. + :param undefined: :class:`Undefined` class to use for next and + previous items. + :param recurse: The function to render the loop body when the + loop is marked recursive. + :param depth0: Incremented when looping recursively. + """ + self._iterable = iterable + self._iterator = self._to_iterator(iterable) self._undefined = undefined self._recurse = recurse - #: How many levels deep a recursive loop currently is, starting at 0. + #: How many levels deep a recursive loop currently is, starting at 0. self.depth0 = depth0 - @staticmethod - def _to_iterator(iterable: t.Iterable[V]) -> t.Iterator[V]: - return iter(iterable) + @staticmethod + def _to_iterator(iterable: t.Iterable[V]) -> t.Iterator[V]: + return iter(iterable) - @property - def length(self) -> int: - """Length of the iterable. + @property + def length(self) -> int: + """Length of the iterable. - If the iterable is a generator or otherwise does not have a - size, it is eagerly evaluated to get a size. - """ - if self._length is not None: - return self._length + If the iterable is a generator or otherwise does not have a + size, it is eagerly evaluated to get a size. + """ + if self._length is not None: + return self._length - try: - self._length = len(self._iterable) # type: ignore - except TypeError: - iterable = list(self._iterator) - self._iterator = self._to_iterator(iterable) - self._length = len(iterable) + self.index + (self._after is not missing) + try: + self._length = len(self._iterable) # type: ignore + except TypeError: + iterable = list(self._iterator) + self._iterator = self._to_iterator(iterable) + self._length = len(iterable) + self.index + (self._after is not missing) - return self._length + return self._length - def __len__(self) -> int: + def __len__(self) -> int: return self.length + @property + def depth(self) -> int: + """How many levels deep a recursive loop currently is, starting at 1.""" + return self.depth0 + 1 + + @property + def index(self) -> int: + """Current iteration of the loop, starting at 1.""" + return self.index0 + 1 + + @property + def revindex0(self) -> int: + """Number of iterations from the end of the loop, ending at 0. + + Requires calculating :attr:`length`. + """ + return self.length - self.index + + @property + def revindex(self) -> int: + """Number of iterations from the end of the loop, ending at 1. + + Requires calculating :attr:`length`. + """ + return self.length - self.index0 + + @property + def first(self) -> bool: + """Whether this is the first iteration of the loop.""" + return self.index0 == 0 + + def _peek_next(self) -> t.Any: + """Return the next element in the iterable, or :data:`missing` + if the iterable is exhausted. Only peeks one item ahead, caching + the result in :attr:`_last` for use in subsequent checks. The + cache is reset when :meth:`__next__` is called. + """ + if self._after is not missing: + return self._after + + self._after = next(self._iterator, missing) + return self._after + @property - def depth(self) -> int: - """How many levels deep a recursive loop currently is, starting at 1.""" - return self.depth0 + 1 - - @property - def index(self) -> int: - """Current iteration of the loop, starting at 1.""" - return self.index0 + 1 - - @property - def revindex0(self) -> int: - """Number of iterations from the end of the loop, ending at 0. - - Requires calculating :attr:`length`. - """ - return self.length - self.index - - @property - def revindex(self) -> int: - """Number of iterations from the end of the loop, ending at 1. - - Requires calculating :attr:`length`. - """ - return self.length - self.index0 - - @property - def first(self) -> bool: - """Whether this is the first iteration of the loop.""" - return self.index0 == 0 - - def _peek_next(self) -> t.Any: - """Return the next element in the iterable, or :data:`missing` - if the iterable is exhausted. Only peeks one item ahead, caching - the result in :attr:`_last` for use in subsequent checks. The - cache is reset when :meth:`__next__` is called. - """ - if self._after is not missing: - return self._after - - self._after = next(self._iterator, missing) - return self._after - - @property - def last(self) -> bool: - """Whether this is the last iteration of the loop. - - Causes the iterable to advance early. See - :func:`itertools.groupby` for issues this can cause. - The :func:`groupby` filter avoids that issue. - """ - return self._peek_next() is missing - - @property - def previtem(self) -> t.Union[t.Any, "Undefined"]: - """The item in the previous iteration. Undefined during the - first iteration. - """ - if self.first: - return self._undefined("there is no previous item") - - return self._before - - @property - def nextitem(self) -> t.Union[t.Any, "Undefined"]: - """The item in the next iteration. Undefined during the last - iteration. - - Causes the iterable to advance early. See - :func:`itertools.groupby` for issues this can cause. - The :func:`jinja-filters.groupby` filter avoids that issue. - """ - rv = self._peek_next() - - if rv is missing: - return self._undefined("there is no next item") - - return rv - - def cycle(self, *args: V) -> V: - """Return a value from the given args, cycling through based on - the current :attr:`index0`. - - :param args: One or more values to cycle through. - """ - if not args: - raise TypeError("no items for cycling given") - - return args[self.index0 % len(args)] - - def changed(self, *value: t.Any) -> bool: - """Return ``True`` if previously called with a different value - (including when called for the first time). - - :param value: One or more values to compare to the last call. - """ - if self._last_changed_value != value: - self._last_changed_value = value - return True - - return False - - def __iter__(self) -> "LoopContext": - return self - - def __next__(self) -> t.Tuple[t.Any, "LoopContext"]: - if self._after is not missing: - rv = self._after - self._after = missing - else: - rv = next(self._iterator) - - self.index0 += 1 - self._before = self._current - self._current = rv - return rv, self - - @internalcode - def __call__(self, iterable: t.Iterable[V]) -> str: - """When iterating over nested data, render the body of the loop - recursively with the given inner iterable data. - - The loop must have the ``recursive`` marker for this to work. - """ - if self._recurse is None: - raise TypeError( - "The loop must have the 'recursive' marker to be called recursively." - ) - - return self._recurse(iterable, self._recurse, depth=self.depth) - - def __repr__(self) -> str: - return f"<{type(self).__name__} {self.index}/{self.length}>" - - -class AsyncLoopContext(LoopContext): - _iterator: t.AsyncIterator[t.Any] # type: ignore - - @staticmethod - def _to_iterator( # type: ignore - iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]] - ) -> t.AsyncIterator[V]: - return auto_aiter(iterable) - - @property - async def length(self) -> int: # type: ignore - if self._length is not None: - return self._length - - try: - self._length = len(self._iterable) # type: ignore - except TypeError: - iterable = [x async for x in self._iterator] - self._iterator = self._to_iterator(iterable) - self._length = len(iterable) + self.index + (self._after is not missing) - - return self._length - - @property - async def revindex0(self) -> int: # type: ignore - return await self.length - self.index - - @property - async def revindex(self) -> int: # type: ignore - return await self.length - self.index0 - - async def _peek_next(self) -> t.Any: - if self._after is not missing: - return self._after - - try: - self._after = await self._iterator.__anext__() - except StopAsyncIteration: - self._after = missing - - return self._after - - @property - async def last(self) -> bool: # type: ignore - return await self._peek_next() is missing - - @property - async def nextitem(self) -> t.Union[t.Any, "Undefined"]: - rv = await self._peek_next() - - if rv is missing: - return self._undefined("there is no next item") - - return rv - - def __aiter__(self) -> "AsyncLoopContext": + def last(self) -> bool: + """Whether this is the last iteration of the loop. + + Causes the iterable to advance early. See + :func:`itertools.groupby` for issues this can cause. + The :func:`groupby` filter avoids that issue. + """ + return self._peek_next() is missing + + @property + def previtem(self) -> t.Union[t.Any, "Undefined"]: + """The item in the previous iteration. Undefined during the + first iteration. + """ + if self.first: + return self._undefined("there is no previous item") + + return self._before + + @property + def nextitem(self) -> t.Union[t.Any, "Undefined"]: + """The item in the next iteration. Undefined during the last + iteration. + + Causes the iterable to advance early. See + :func:`itertools.groupby` for issues this can cause. + The :func:`jinja-filters.groupby` filter avoids that issue. + """ + rv = self._peek_next() + + if rv is missing: + return self._undefined("there is no next item") + + return rv + + def cycle(self, *args: V) -> V: + """Return a value from the given args, cycling through based on + the current :attr:`index0`. + + :param args: One or more values to cycle through. + """ + if not args: + raise TypeError("no items for cycling given") + + return args[self.index0 % len(args)] + + def changed(self, *value: t.Any) -> bool: + """Return ``True`` if previously called with a different value + (including when called for the first time). + + :param value: One or more values to compare to the last call. + """ + if self._last_changed_value != value: + self._last_changed_value = value + return True + + return False + + def __iter__(self) -> "LoopContext": return self - async def __anext__(self) -> t.Tuple[t.Any, "AsyncLoopContext"]: - if self._after is not missing: - rv = self._after - self._after = missing - else: - rv = await self._iterator.__anext__() - - self.index0 += 1 - self._before = self._current - self._current = rv - return rv, self - - -class Macro: + def __next__(self) -> t.Tuple[t.Any, "LoopContext"]: + if self._after is not missing: + rv = self._after + self._after = missing + else: + rv = next(self._iterator) + + self.index0 += 1 + self._before = self._current + self._current = rv + return rv, self + + @internalcode + def __call__(self, iterable: t.Iterable[V]) -> str: + """When iterating over nested data, render the body of the loop + recursively with the given inner iterable data. + + The loop must have the ``recursive`` marker for this to work. + """ + if self._recurse is None: + raise TypeError( + "The loop must have the 'recursive' marker to be called recursively." + ) + + return self._recurse(iterable, self._recurse, depth=self.depth) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.index}/{self.length}>" + + +class AsyncLoopContext(LoopContext): + _iterator: t.AsyncIterator[t.Any] # type: ignore + + @staticmethod + def _to_iterator( # type: ignore + iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]] + ) -> t.AsyncIterator[V]: + return auto_aiter(iterable) + + @property + async def length(self) -> int: # type: ignore + if self._length is not None: + return self._length + + try: + self._length = len(self._iterable) # type: ignore + except TypeError: + iterable = [x async for x in self._iterator] + self._iterator = self._to_iterator(iterable) + self._length = len(iterable) + self.index + (self._after is not missing) + + return self._length + + @property + async def revindex0(self) -> int: # type: ignore + return await self.length - self.index + + @property + async def revindex(self) -> int: # type: ignore + return await self.length - self.index0 + + async def _peek_next(self) -> t.Any: + if self._after is not missing: + return self._after + + try: + self._after = await self._iterator.__anext__() + except StopAsyncIteration: + self._after = missing + + return self._after + + @property + async def last(self) -> bool: # type: ignore + return await self._peek_next() is missing + + @property + async def nextitem(self) -> t.Union[t.Any, "Undefined"]: + rv = await self._peek_next() + + if rv is missing: + return self._undefined("there is no next item") + + return rv + + def __aiter__(self) -> "AsyncLoopContext": + return self + + async def __anext__(self) -> t.Tuple[t.Any, "AsyncLoopContext"]: + if self._after is not missing: + rv = self._after + self._after = missing + else: + rv = await self._iterator.__anext__() + + self.index0 += 1 + self._before = self._current + self._current = rv + return rv, self + + +class Macro: """Wraps a macro function.""" - def __init__( - self, - environment: "Environment", - func: t.Callable[..., str], - name: str, - arguments: t.List[str], - catch_kwargs: bool, - catch_varargs: bool, - caller: bool, - default_autoescape: t.Optional[bool] = None, - ): + def __init__( + self, + environment: "Environment", + func: t.Callable[..., str], + name: str, + arguments: t.List[str], + catch_kwargs: bool, + catch_varargs: bool, + caller: bool, + default_autoescape: t.Optional[bool] = None, + ): self._environment = environment self._func = func self._argument_count = len(arguments) @@ -725,25 +725,25 @@ class Macro: self.catch_kwargs = catch_kwargs self.catch_varargs = catch_varargs self.caller = caller - self.explicit_caller = "caller" in arguments - + self.explicit_caller = "caller" in arguments + if default_autoescape is None: - if callable(environment.autoescape): - default_autoescape = environment.autoescape(None) - else: - default_autoescape = environment.autoescape - + if callable(environment.autoescape): + default_autoescape = environment.autoescape(None) + else: + default_autoescape = environment.autoescape + self._default_autoescape = default_autoescape @internalcode - @pass_eval_context - def __call__(self, *args: t.Any, **kwargs: t.Any) -> str: + @pass_eval_context + def __call__(self, *args: t.Any, **kwargs: t.Any) -> str: # This requires a bit of explanation, In the past we used to # decide largely based on compile-time information if a macro is # safe or unsafe. While there was a volatile mode it was largely # unused for deciding on escaping. This turns out to be - # problematic for macros because whether a macro is safe depends not - # on the escape mode when it was defined, but rather when it was used. + # problematic for macros because whether a macro is safe depends not + # on the escape mode when it was defined, but rather when it was used. # # Because however we export macros from the module system and # there are historic callers that do not pass an eval context (and @@ -751,7 +751,7 @@ class Macro: # check here. # # This is considered safe because an eval context is not a valid - # argument to callables otherwise anyway. 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): @@ -761,7 +761,7 @@ class Macro: autoescape = self._default_autoescape # try to consume the positional arguments - arguments = list(args[: self._argument_count]) + arguments = list(args[: self._argument_count]) off = len(arguments) # For information why this is necessary refer to the handling @@ -772,12 +772,12 @@ class Macro: # arguments expected we start filling in keyword arguments # and defaults. if off != self._argument_count: - for name in self.arguments[len(arguments) :]: + for name in self.arguments[len(arguments) :]: try: value = kwargs.pop(name) except KeyError: value = missing - if name == "caller": + if name == "caller": found_caller = True arguments.append(value) else: @@ -787,59 +787,59 @@ class Macro: # if not also changed in the compiler's `function_scoping` method. # the order is caller, keyword arguments, positional arguments! if self.caller and not found_caller: - caller = kwargs.pop("caller", None) + caller = kwargs.pop("caller", None) if caller is None: - caller = self._environment.undefined("No caller defined", name="caller") + caller = self._environment.undefined("No caller defined", name="caller") arguments.append(caller) if self.catch_kwargs: arguments.append(kwargs) elif kwargs: - if "caller" in kwargs: - raise TypeError( - f"macro {self.name!r} was invoked with two values for the special" - " caller argument. This is most likely a bug." - ) - raise TypeError( - f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}" - ) + if "caller" in kwargs: + raise TypeError( + f"macro {self.name!r} was invoked with two values for the special" + " caller argument. This is most likely a bug." + ) + raise TypeError( + f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}" + ) if self.catch_varargs: - arguments.append(args[self._argument_count :]) + arguments.append(args[self._argument_count :]) elif len(args) > self._argument_count: - raise TypeError( - f"macro {self.name!r} takes not more than" - f" {len(self.arguments)} argument(s)" - ) + raise TypeError( + f"macro {self.name!r} takes not more than" + f" {len(self.arguments)} argument(s)" + ) return self._invoke(arguments, autoescape) - async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str: - rv = await self._func(*arguments) # type: ignore - - if autoescape: - return Markup(rv) - - return rv # type: ignore - - def _invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str: - if self._environment.is_async: - return self._async_invoke(arguments, autoescape) # type: ignore - + async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str: + rv = await self._func(*arguments) # type: ignore + + if autoescape: + return Markup(rv) + + return rv # type: ignore + + def _invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str: + if self._environment.is_async: + return self._async_invoke(arguments, autoescape) # type: ignore + rv = self._func(*arguments) - + if autoescape: rv = Markup(rv) - + return rv - def __repr__(self) -> str: - name = "anonymous" if self.name is None else repr(self.name) - return f"<{type(self).__name__} {name}>" + def __repr__(self) -> str: + name = "anonymous" if self.name is None else repr(self.name) + return f"<{type(self).__name__} {name}>" -class Undefined: +class Undefined: """The default undefined type. This undefined type can be printed and - iterated over, but every other access will raise an :exc:`UndefinedError`: + iterated over, but every other access will raise an :exc:`UndefinedError`: >>> foo = Undefined(name='foo') >>> str(foo) @@ -852,106 +852,106 @@ class Undefined: jinja2.exceptions.UndefinedError: 'foo' is undefined """ - __slots__ = ( - "_undefined_hint", - "_undefined_obj", - "_undefined_name", - "_undefined_exception", - ) - - def __init__( - self, - hint: t.Optional[str] = None, - obj: t.Any = missing, - name: t.Optional[str] = None, - exc: t.Type[TemplateRuntimeError] = UndefinedError, - ) -> None: + __slots__ = ( + "_undefined_hint", + "_undefined_obj", + "_undefined_name", + "_undefined_exception", + ) + + def __init__( + self, + hint: t.Optional[str] = None, + obj: t.Any = missing, + name: t.Optional[str] = None, + exc: t.Type[TemplateRuntimeError] = UndefinedError, + ) -> None: self._undefined_hint = hint self._undefined_obj = obj self._undefined_name = name self._undefined_exception = exc - @property - def _undefined_message(self) -> str: - """Build a message about the undefined value based on how it was - accessed. - """ - if self._undefined_hint: - return self._undefined_hint - - if self._undefined_obj is missing: - return f"{self._undefined_name!r} is undefined" - - if not isinstance(self._undefined_name, str): - return ( - f"{object_type_repr(self._undefined_obj)} has no" - f" element {self._undefined_name!r}" - ) - - return ( - f"{object_type_repr(self._undefined_obj)!r} has no" - f" attribute {self._undefined_name!r}" - ) - + @property + def _undefined_message(self) -> str: + """Build a message about the undefined value based on how it was + accessed. + """ + if self._undefined_hint: + return self._undefined_hint + + if self._undefined_obj is missing: + return f"{self._undefined_name!r} is undefined" + + if not isinstance(self._undefined_name, str): + return ( + f"{object_type_repr(self._undefined_obj)} has no" + f" element {self._undefined_name!r}" + ) + + return ( + f"{object_type_repr(self._undefined_obj)!r} has no" + f" attribute {self._undefined_name!r}" + ) + @internalcode - def _fail_with_undefined_error( - self, *args: t.Any, **kwargs: t.Any - ) -> "te.NoReturn": - """Raise an :exc:`UndefinedError` when operations are performed - on the undefined value. + def _fail_with_undefined_error( + self, *args: t.Any, **kwargs: t.Any + ) -> "te.NoReturn": + """Raise an :exc:`UndefinedError` when operations are performed + on the undefined value. """ - raise self._undefined_exception(self._undefined_message) + raise self._undefined_exception(self._undefined_message) @internalcode - def __getattr__(self, name: str) -> t.Any: - if name[:2] == "__": + def __getattr__(self, name: str) -> t.Any: + if name[:2] == "__": raise AttributeError(name) - + return self._fail_with_undefined_error() - __add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error - __mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error - __truediv__ = __rtruediv__ = _fail_with_undefined_error - __floordiv__ = __rfloordiv__ = _fail_with_undefined_error - __mod__ = __rmod__ = _fail_with_undefined_error - __pos__ = __neg__ = _fail_with_undefined_error - __call__ = __getitem__ = _fail_with_undefined_error - __lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error - __int__ = __float__ = __complex__ = _fail_with_undefined_error - __pow__ = __rpow__ = _fail_with_undefined_error - - def __eq__(self, other: t.Any) -> bool: + __add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error + __mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error + __truediv__ = __rtruediv__ = _fail_with_undefined_error + __floordiv__ = __rfloordiv__ = _fail_with_undefined_error + __mod__ = __rmod__ = _fail_with_undefined_error + __pos__ = __neg__ = _fail_with_undefined_error + __call__ = __getitem__ = _fail_with_undefined_error + __lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error + __int__ = __float__ = __complex__ = _fail_with_undefined_error + __pow__ = __rpow__ = _fail_with_undefined_error + + def __eq__(self, other: t.Any) -> bool: return type(self) is type(other) - def __ne__(self, other: t.Any) -> bool: + def __ne__(self, other: t.Any) -> bool: return not self.__eq__(other) - def __hash__(self) -> int: + def __hash__(self) -> int: return id(type(self)) - def __str__(self) -> str: - return "" + def __str__(self) -> str: + return "" - def __len__(self) -> int: + def __len__(self) -> int: return 0 - def __iter__(self) -> t.Iterator[t.Any]: - yield from () - - async def __aiter__(self) -> t.AsyncIterator[t.Any]: - for _ in (): - yield + def __iter__(self) -> t.Iterator[t.Any]: + yield from () - def __bool__(self) -> bool: + async def __aiter__(self) -> t.AsyncIterator[t.Any]: + for _ in (): + yield + + def __bool__(self) -> bool: return False - - def __repr__(self) -> str: - return "Undefined" + + def __repr__(self) -> str: + return "Undefined" -def make_logging_undefined( - logger: t.Optional["logging.Logger"] = None, base: t.Type[Undefined] = Undefined -) -> t.Type[Undefined]: +def make_logging_undefined( + logger: t.Optional["logging.Logger"] = None, base: t.Type[Undefined] = Undefined +) -> t.Type[Undefined]: """Given a logger object this returns a new undefined class that will log certain failures. It will log iterations and printing. If no logger is given a default logger is created. @@ -973,69 +973,69 @@ def make_logging_undefined( """ if logger is None: import logging - + logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler(sys.stderr)) - def _log_message(undef: Undefined) -> None: - logger.warning( # type: ignore - "Template variable warning: %s", undef._undefined_message - ) - - class LoggingUndefined(base): # type: ignore - __slots__ = () - - def _fail_with_undefined_error( # type: ignore - self, *args: t.Any, **kwargs: t.Any - ) -> "te.NoReturn": + def _log_message(undef: Undefined) -> None: + logger.warning( # type: ignore + "Template variable warning: %s", undef._undefined_message + ) + + class LoggingUndefined(base): # type: ignore + __slots__ = () + + def _fail_with_undefined_error( # type: ignore + self, *args: t.Any, **kwargs: t.Any + ) -> "te.NoReturn": try: - super()._fail_with_undefined_error(*args, **kwargs) + super()._fail_with_undefined_error(*args, **kwargs) except self._undefined_exception as e: - logger.error("Template variable error: %s", e) # type: ignore + logger.error("Template variable error: %s", e) # type: ignore raise e - def __str__(self) -> str: + def __str__(self) -> str: _log_message(self) - return super().__str__() # type: ignore + return super().__str__() # type: ignore - def __iter__(self) -> t.Iterator[t.Any]: + def __iter__(self) -> t.Iterator[t.Any]: _log_message(self) - return super().__iter__() # type: ignore - - def __bool__(self) -> bool: - _log_message(self) - return super().__bool__() # type: ignore + return super().__iter__() # type: ignore + def __bool__(self) -> bool: + _log_message(self) + return super().__bool__() # type: ignore + return LoggingUndefined -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.0 - """ - - __slots__ = () - - def __html__(self) -> str: - return str(self) - - def __getattr__(self, _: str) -> "ChainableUndefined": - return self - - __getitem__ = __getattr__ # type: ignore - - +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.0 + """ + + __slots__ = () + + def __html__(self) -> str: + return str(self) + + def __getattr__(self, _: str) -> "ChainableUndefined": + return self + + __getitem__ = __getattr__ # type: ignore + + class DebugUndefined(Undefined): """An undefined that returns the debug info when printed. @@ -1049,25 +1049,25 @@ class DebugUndefined(Undefined): ... jinja2.exceptions.UndefinedError: 'foo' is undefined """ - + __slots__ = () - def __str__(self) -> str: - if self._undefined_hint: - message = f"undefined value printed: {self._undefined_hint}" - - elif self._undefined_obj is missing: - message = self._undefined_name # type: ignore - - else: - message = ( - f"no such element: {object_type_repr(self._undefined_obj)}" - f"[{self._undefined_name!r}]" + def __str__(self) -> str: + if self._undefined_hint: + message = f"undefined value printed: {self._undefined_hint}" + + elif self._undefined_obj is missing: + message = self._undefined_name # type: ignore + + else: + message = ( + f"no such element: {object_type_repr(self._undefined_obj)}" + f"[{self._undefined_name!r}]" ) - return f"{{{{ {message} }}}}" - + return f"{{{{ {message} }}}}" + class StrictUndefined(Undefined): """An undefined that barks on print and iteration as well as boolean tests and all kinds of comparisons. In other words: you can do nothing @@ -1087,18 +1087,18 @@ class StrictUndefined(Undefined): ... jinja2.exceptions.UndefinedError: 'foo' is undefined """ - + __slots__ = () - __iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error - __eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error - __contains__ = Undefined._fail_with_undefined_error - - -# Remove slots attributes, after the metaclass is applied they are -# unneeded and contain wrong data for subclasses. -del ( - Undefined.__slots__, - ChainableUndefined.__slots__, - DebugUndefined.__slots__, - StrictUndefined.__slots__, -) + __iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error + __eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error + __contains__ = Undefined._fail_with_undefined_error + + +# Remove slots attributes, after the metaclass is applied they are +# unneeded and contain wrong data for subclasses. +del ( + Undefined.__slots__, + ChainableUndefined.__slots__, + DebugUndefined.__slots__, + StrictUndefined.__slots__, +) diff --git a/contrib/python/Jinja2/py3/jinja2/sandbox.py b/contrib/python/Jinja2/py3/jinja2/sandbox.py index 4294884776..3f7735b579 100644 --- a/contrib/python/Jinja2/py3/jinja2/sandbox.py +++ b/contrib/python/Jinja2/py3/jinja2/sandbox.py @@ -1,128 +1,128 @@ -"""A sandbox layer that ensures unsafe operations cannot be performed. -Useful when the template itself comes from an untrusted source. +"""A sandbox layer that ensures unsafe operations cannot be performed. +Useful when the template itself comes from an untrusted source. """ -import operator +import operator import types -import typing as t -from _string import formatter_field_name_split # type: ignore -from collections import abc -from collections import deque -from string import Formatter +import typing as t +from _string import formatter_field_name_split # type: ignore +from collections import abc +from collections import deque +from string import Formatter from markupsafe import EscapeFormatter -from markupsafe import Markup +from markupsafe import Markup -from .environment import Environment -from .exceptions import SecurityError -from .runtime import Context -from .runtime import Undefined - -F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +from .environment import Environment +from .exceptions import SecurityError +from .runtime import Context +from .runtime import Undefined +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + #: maximum number of items a range may produce MAX_RANGE = 100000 -#: Unsafe function attributes. -UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set() +#: Unsafe function attributes. +UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set() -#: Unsafe method attributes. Function attributes are unsafe for methods too. -UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set() +#: Unsafe method attributes. Function attributes are unsafe for methods too. +UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set() -#: unsafe generator attributes. -UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"} +#: unsafe generator attributes. +UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"} #: unsafe attributes on coroutines -UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"} +UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"} #: unsafe attributes on async generators -UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"} - -_mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = ( - ( - abc.MutableSet, - frozenset( - [ - "add", - "clear", - "difference_update", - "discard", - "pop", - "remove", - "symmetric_difference_update", - "update", - ] - ), - ), - ( - abc.MutableMapping, - frozenset(["clear", "pop", "popitem", "setdefault", "update"]), - ), - ( - abc.MutableSequence, - frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]), - ), - ( - deque, - frozenset( - [ - "append", - "appendleft", - "clear", - "extend", - "extendleft", - "pop", - "popleft", - "remove", - "rotate", - ] - ), - ), +UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"} + +_mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = ( + ( + abc.MutableSet, + frozenset( + [ + "add", + "clear", + "difference_update", + "discard", + "pop", + "remove", + "symmetric_difference_update", + "update", + ] + ), + ), + ( + abc.MutableMapping, + frozenset(["clear", "pop", "popitem", "setdefault", "update"]), + ), + ( + abc.MutableSequence, + frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]), + ), + ( + deque, + frozenset( + [ + "append", + "appendleft", + "clear", + "extend", + "extendleft", + "pop", + "popleft", + "remove", + "rotate", + ] + ), + ), ) -def inspect_format_method(callable: t.Callable) -> t.Optional[str]: - if not isinstance( - callable, (types.MethodType, types.BuiltinMethodType) - ) or callable.__name__ not in ("format", "format_map"): +def inspect_format_method(callable: t.Callable) -> t.Optional[str]: + if not isinstance( + callable, (types.MethodType, types.BuiltinMethodType) + ) or callable.__name__ not in ("format", "format_map"): return None - + obj = callable.__self__ - - if isinstance(obj, str): + + if isinstance(obj, str): return obj - return None - + return None -def safe_range(*args: int) -> range: + +def safe_range(*args: int) -> range: """A range that can't generate ranges with a length of more than MAX_RANGE items. """ - rng = range(*args) - + rng = range(*args) + if len(rng) > MAX_RANGE: - raise OverflowError( - "Range too big. The sandbox blocks ranges larger than" - f" MAX_RANGE ({MAX_RANGE})." - ) - + raise OverflowError( + "Range too big. The sandbox blocks ranges larger than" + f" MAX_RANGE ({MAX_RANGE})." + ) + return rng -def unsafe(f: F) -> F: +def unsafe(f: F) -> F: """Marks a function or method as unsafe. - .. code-block: python + .. code-block: python @unsafe def delete(self): pass """ - f.unsafe_callable = True # type: ignore + f.unsafe_callable = True # type: ignore return f -def is_internal_attribute(obj: t.Any, attr: str) -> bool: +def is_internal_attribute(obj: t.Any, attr: str) -> bool: """Test if the attribute given is an internal python attribute. For example this function returns `True` for the `func_code` attribute of python objects. This is useful if the environment method @@ -138,31 +138,31 @@ def is_internal_attribute(obj: t.Any, attr: str) -> bool: if attr in UNSAFE_FUNCTION_ATTRIBUTES: return True elif isinstance(obj, types.MethodType): - if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES: + if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES: return True elif isinstance(obj, type): - if attr == "mro": + if attr == "mro": return True elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)): return True elif isinstance(obj, types.GeneratorType): if attr in UNSAFE_GENERATOR_ATTRIBUTES: return True - elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType): + elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType): if attr in UNSAFE_COROUTINE_ATTRIBUTES: return True - elif hasattr(types, "AsyncGeneratorType") and isinstance( - obj, types.AsyncGeneratorType - ): + elif hasattr(types, "AsyncGeneratorType") and isinstance( + obj, types.AsyncGeneratorType + ): if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES: return True - return attr.startswith("__") + return attr.startswith("__") -def modifies_known_mutable(obj: t.Any, attr: str) -> bool: +def modifies_known_mutable(obj: t.Any, attr: str) -> bool: """This function checks if an attribute on a builtin mutable object - (list, dict, set or deque) or the corresponding ABCs would modify it - if called. + (list, dict, set or deque) or the corresponding ABCs would modify it + if called. >>> modifies_known_mutable({}, "clear") True @@ -173,7 +173,7 @@ def modifies_known_mutable(obj: t.Any, attr: str) -> bool: >>> modifies_known_mutable([], "index") False - If called with an unsupported object, ``False`` is returned. + If called with an unsupported object, ``False`` is returned. >>> modifies_known_mutable("foo", "upper") False @@ -194,29 +194,29 @@ class SandboxedEnvironment(Environment): raised. However also other exceptions may occur during the rendering so the caller has to ensure that all exceptions are caught. """ - + sandboxed = True #: default callback table for the binary operators. A copy of this is #: available on each instance of a sandboxed environment as #: :attr:`binop_table` - default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = { - "+": operator.add, - "-": operator.sub, - "*": operator.mul, - "/": operator.truediv, - "//": operator.floordiv, - "**": operator.pow, - "%": operator.mod, + default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = { + "+": operator.add, + "-": operator.sub, + "*": operator.mul, + "/": operator.truediv, + "//": operator.floordiv, + "**": operator.pow, + "%": operator.mod, } #: default callback table for the unary operators. A copy of this is #: available on each instance of a sandboxed environment as #: :attr:`unop_table` - default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = { - "+": operator.pos, - "-": operator.neg, - } + default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = { + "+": operator.pos, + "-": operator.neg, + } #: a set of binary operators that should be intercepted. Each operator #: that is added to this set (empty by default) is delegated to the @@ -232,7 +232,7 @@ class SandboxedEnvironment(Environment): #: interested in. #: #: .. versionadded:: 2.6 - intercepted_binops: t.FrozenSet[str] = frozenset() + intercepted_binops: t.FrozenSet[str] = frozenset() #: a set of unary operators that should be intercepted. Each operator #: that is added to this set (empty by default) is delegated to the @@ -247,37 +247,37 @@ class SandboxedEnvironment(Environment): #: interested in. #: #: .. versionadded:: 2.6 - intercepted_unops: t.FrozenSet[str] = frozenset() + intercepted_unops: t.FrozenSet[str] = frozenset() - def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: - super().__init__(*args, **kwargs) - self.globals["range"] = safe_range + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super().__init__(*args, **kwargs) + self.globals["range"] = safe_range self.binop_table = self.default_binop_table.copy() self.unop_table = self.default_unop_table.copy() - def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool: + def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool: """The sandboxed environment will call this method to check if the attribute of an object is safe to access. Per default all attributes starting with an underscore are considered private as well as the special attributes of internal python objects as returned by the :func:`is_internal_attribute` function. """ - return not (attr.startswith("_") or is_internal_attribute(obj, attr)) - - def is_safe_callable(self, obj: t.Any) -> bool: - """Check if an object is safely callable. By default callables - are considered safe unless decorated with :func:`unsafe`. - - This also recognizes the Django convention of setting - ``func.alters_data = True``. + return not (attr.startswith("_") or is_internal_attribute(obj, attr)) + + def is_safe_callable(self, obj: t.Any) -> bool: + """Check if an object is safely callable. By default callables + are considered safe unless decorated with :func:`unsafe`. + + This also recognizes the Django convention of setting + ``func.alters_data = True``. """ - return not ( - getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False) - ) + return not ( + getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False) + ) - def call_binop( - self, context: Context, operator: str, left: t.Any, right: t.Any - ) -> t.Any: + def call_binop( + self, context: Context, operator: str, left: t.Any, right: t.Any + ) -> t.Any: """For intercepted binary operator calls (:meth:`intercepted_binops`) this function is executed instead of the builtin operator. This can be used to fine tune the behavior of certain operators. @@ -286,7 +286,7 @@ class SandboxedEnvironment(Environment): """ return self.binop_table[operator](left, right) - def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any: + def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any: """For intercepted unary operator calls (:meth:`intercepted_unops`) this function is executed instead of the builtin operator. This can be used to fine tune the behavior of certain operators. @@ -295,14 +295,14 @@ class SandboxedEnvironment(Environment): """ return self.unop_table[operator](arg) - def getitem( - self, obj: t.Any, argument: t.Union[str, t.Any] - ) -> t.Union[t.Any, Undefined]: + def getitem( + self, obj: t.Any, argument: t.Union[str, t.Any] + ) -> t.Union[t.Any, Undefined]: """Subscribe an object from sandboxed code.""" try: return obj[argument] except (TypeError, LookupError): - if isinstance(argument, str): + if isinstance(argument, str): try: attr = str(argument) except Exception: @@ -318,7 +318,7 @@ class SandboxedEnvironment(Environment): return self.unsafe_undefined(obj, argument) return self.undefined(obj=obj, name=argument) - def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]: + def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]: """Subscribe an object from sandboxed code and prefer the attribute. The attribute passed *must* be a bytestring. """ @@ -335,61 +335,61 @@ class SandboxedEnvironment(Environment): return self.unsafe_undefined(obj, attribute) return self.undefined(obj=obj, name=attribute) - def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined: + def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined: """Return an undefined object for unsafe attributes.""" - return self.undefined( - f"access to attribute {attribute!r} of" - f" {type(obj).__name__!r} object is unsafe.", - name=attribute, - obj=obj, - exc=SecurityError, - ) - - def format_string( - self, - s: str, - args: t.Tuple[t.Any, ...], - kwargs: t.Dict[str, t.Any], - format_func: t.Optional[t.Callable] = None, - ) -> str: + return self.undefined( + f"access to attribute {attribute!r} of" + f" {type(obj).__name__!r} object is unsafe.", + name=attribute, + obj=obj, + exc=SecurityError, + ) + + def format_string( + self, + s: str, + args: t.Tuple[t.Any, ...], + kwargs: t.Dict[str, t.Any], + format_func: t.Optional[t.Callable] = None, + ) -> str: """If a format call is detected, then this is routed through this method so that our safety sandbox can be used for it. """ - formatter: SandboxedFormatter + formatter: SandboxedFormatter if isinstance(s, Markup): - formatter = SandboxedEscapeFormatter(self, escape=s.escape) + formatter = SandboxedEscapeFormatter(self, escape=s.escape) else: formatter = SandboxedFormatter(self) - - if format_func is not None and format_func.__name__ == "format_map": - if len(args) != 1 or kwargs: - raise TypeError( - "format_map() takes exactly one argument" - f" {len(args) + (kwargs is not None)} given" - ) - - kwargs = args[0] - args = () - + + if format_func is not None and format_func.__name__ == "format_map": + if len(args) != 1 or kwargs: + raise TypeError( + "format_map() takes exactly one argument" + f" {len(args) + (kwargs is not None)} given" + ) + + kwargs = args[0] + args = () + rv = formatter.vformat(s, args, kwargs) return type(s)(rv) - def call( - __self, # noqa: B902 - __context: Context, - __obj: t.Any, - *args: t.Any, - **kwargs: t.Any, - ) -> t.Any: + def call( + __self, # noqa: B902 + __context: Context, + __obj: t.Any, + *args: t.Any, + **kwargs: t.Any, + ) -> t.Any: """Call an object from sandboxed code.""" fmt = inspect_format_method(__obj) if fmt is not None: - return __self.format_string(fmt, args, kwargs, __obj) + return __self.format_string(fmt, args, kwargs, __obj) # the double prefixes are to avoid double keyword argument # errors when proxying the call. if not __self.is_safe_callable(__obj): - raise SecurityError(f"{__obj!r} is not safely callable") + raise SecurityError(f"{__obj!r} is not safely callable") return __context.call(__obj, *args, **kwargs) @@ -399,21 +399,21 @@ class ImmutableSandboxedEnvironment(SandboxedEnvironment): `dict` by using the :func:`modifies_known_mutable` function. """ - def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool: - if not super().is_safe_attribute(obj, attr, value): + def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool: + if not super().is_safe_attribute(obj, attr, value): return False - + return not modifies_known_mutable(obj, attr) -class SandboxedFormatter(Formatter): - def __init__(self, env: Environment, **kwargs: t.Any) -> None: +class SandboxedFormatter(Formatter): + def __init__(self, env: Environment, **kwargs: t.Any) -> None: self._env = env - super().__init__(**kwargs) # type: ignore + super().__init__(**kwargs) # type: ignore - def get_field( - self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any] - ) -> t.Tuple[t.Any, str]: + def get_field( + self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any] + ) -> t.Tuple[t.Any, str]: first, rest = formatter_field_name_split(field_name) obj = self.get_value(first, args, kwargs) for is_attr, i in rest: @@ -423,6 +423,6 @@ class SandboxedFormatter(Formatter): obj = self._env.getitem(obj, i) return obj, first - -class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter): - pass + +class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter): + pass diff --git a/contrib/python/Jinja2/py3/jinja2/tests.py b/contrib/python/Jinja2/py3/jinja2/tests.py index a467cf08b5..b266761ee7 100644 --- a/contrib/python/Jinja2/py3/jinja2/tests.py +++ b/contrib/python/Jinja2/py3/jinja2/tests.py @@ -1,32 +1,32 @@ -"""Built-in template tests used with the ``is`` operator.""" +"""Built-in template tests used with the ``is`` operator.""" import operator -import typing as t -from collections import abc -from numbers import Number +import typing as t +from collections import abc +from numbers import Number -from .runtime import Undefined -from .utils import pass_environment +from .runtime import Undefined +from .utils import pass_environment + +if t.TYPE_CHECKING: + from .environment import Environment -if t.TYPE_CHECKING: - from .environment import Environment - -def test_odd(value: int) -> bool: +def test_odd(value: int) -> bool: """Return true if the variable is odd.""" return value % 2 == 1 -def test_even(value: int) -> bool: +def test_even(value: int) -> bool: """Return true if the variable is even.""" return value % 2 == 0 -def test_divisibleby(value: int, num: int) -> bool: +def test_divisibleby(value: int, num: int) -> bool: """Check if a variable is divisible by a number.""" return value % num == 0 -def test_defined(value: t.Any) -> bool: +def test_defined(value: t.Any) -> bool: """Return true if the variable is defined: .. sourcecode:: jinja @@ -43,140 +43,140 @@ def test_defined(value: t.Any) -> bool: return not isinstance(value, Undefined) -def test_undefined(value: t.Any) -> bool: +def test_undefined(value: t.Any) -> bool: """Like :func:`defined` but the other way round.""" return isinstance(value, Undefined) -@pass_environment -def test_filter(env: "Environment", value: str) -> bool: - """Check if a filter exists by name. Useful if a filter may be - optionally available. - - .. code-block:: jinja - - {% if 'markdown' is filter %} - {{ value | markdown }} - {% else %} - {{ value }} - {% endif %} - - .. versionadded:: 3.0 - """ - return value in env.filters - - -@pass_environment -def test_test(env: "Environment", value: str) -> bool: - """Check if a test exists by name. Useful if a test may be - optionally available. - - .. code-block:: jinja - - {% if 'loud' is test %} - {% if value is loud %} - {{ value|upper }} - {% else %} - {{ value|lower }} - {% endif %} - {% else %} - {{ value }} - {% endif %} - - .. versionadded:: 3.0 - """ - return value in env.tests - - -def test_none(value: t.Any) -> bool: +@pass_environment +def test_filter(env: "Environment", value: str) -> bool: + """Check if a filter exists by name. Useful if a filter may be + optionally available. + + .. code-block:: jinja + + {% if 'markdown' is filter %} + {{ value | markdown }} + {% else %} + {{ value }} + {% endif %} + + .. versionadded:: 3.0 + """ + return value in env.filters + + +@pass_environment +def test_test(env: "Environment", value: str) -> bool: + """Check if a test exists by name. Useful if a test may be + optionally available. + + .. code-block:: jinja + + {% if 'loud' is test %} + {% if value is loud %} + {{ value|upper }} + {% else %} + {{ value|lower }} + {% endif %} + {% else %} + {{ value }} + {% endif %} + + .. versionadded:: 3.0 + """ + return value in env.tests + + +def test_none(value: t.Any) -> bool: """Return true if the variable is none.""" return value is None -def test_boolean(value: t.Any) -> bool: - """Return true if the object is a boolean value. - - .. versionadded:: 2.11 - """ - return value is True or value is False - - -def test_false(value: t.Any) -> bool: - """Return true if the object is False. - - .. versionadded:: 2.11 - """ - return value is False - - -def test_true(value: t.Any) -> bool: - """Return true if the object is True. - - .. versionadded:: 2.11 - """ - return value is True - - -# NOTE: The existing 'number' test matches booleans and floats -def test_integer(value: t.Any) -> bool: - """Return true if the object is an integer. - - .. versionadded:: 2.11 - """ - return isinstance(value, int) and value is not True and value is not False - - -# NOTE: The existing 'number' test matches booleans and integers -def test_float(value: t.Any) -> bool: - """Return true if the object is a float. - - .. versionadded:: 2.11 - """ - return isinstance(value, float) - - -def test_lower(value: str) -> bool: +def test_boolean(value: t.Any) -> bool: + """Return true if the object is a boolean value. + + .. versionadded:: 2.11 + """ + return value is True or value is False + + +def test_false(value: t.Any) -> bool: + """Return true if the object is False. + + .. versionadded:: 2.11 + """ + return value is False + + +def test_true(value: t.Any) -> bool: + """Return true if the object is True. + + .. versionadded:: 2.11 + """ + return value is True + + +# NOTE: The existing 'number' test matches booleans and floats +def test_integer(value: t.Any) -> bool: + """Return true if the object is an integer. + + .. versionadded:: 2.11 + """ + return isinstance(value, int) and value is not True and value is not False + + +# NOTE: The existing 'number' test matches booleans and integers +def test_float(value: t.Any) -> bool: + """Return true if the object is a float. + + .. versionadded:: 2.11 + """ + return isinstance(value, float) + + +def test_lower(value: str) -> bool: """Return true if the variable is lowercased.""" - return str(value).islower() + return str(value).islower() -def test_upper(value: str) -> bool: +def test_upper(value: str) -> bool: """Return true if the variable is uppercased.""" - return str(value).isupper() + return str(value).isupper() -def test_string(value: t.Any) -> bool: +def test_string(value: t.Any) -> bool: """Return true if the object is a string.""" - return isinstance(value, str) + return isinstance(value, str) -def test_mapping(value: t.Any) -> bool: +def test_mapping(value: t.Any) -> bool: """Return true if the object is a mapping (dict etc.). .. versionadded:: 2.6 """ - return isinstance(value, abc.Mapping) + return isinstance(value, abc.Mapping) -def test_number(value: t.Any) -> bool: +def test_number(value: t.Any) -> bool: """Return true if the variable is a number.""" - return isinstance(value, Number) + return isinstance(value, Number) -def test_sequence(value: t.Any) -> bool: +def test_sequence(value: t.Any) -> bool: """Return true if the variable is a sequence. Sequences are variables that are iterable. """ try: len(value) value.__getitem__ - except Exception: + except Exception: return False - + return True -def test_sameas(value: t.Any, other: t.Any) -> bool: +def test_sameas(value: t.Any, other: t.Any) -> bool: """Check if an object points to the same memory address than another object: @@ -189,22 +189,22 @@ def test_sameas(value: t.Any, other: t.Any) -> bool: return value is other -def test_iterable(value: t.Any) -> bool: +def test_iterable(value: t.Any) -> bool: """Check if it's possible to iterate over an object.""" try: iter(value) except TypeError: return False - + return True -def test_escaped(value: t.Any) -> bool: +def test_escaped(value: t.Any) -> bool: """Check if the value is escaped.""" - return hasattr(value, "__html__") + return hasattr(value, "__html__") -def test_in(value: t.Any, seq: t.Container) -> bool: +def test_in(value: t.Any, seq: t.Container) -> bool: """Check if value is in seq. .. versionadded:: 2.10 @@ -213,43 +213,43 @@ def test_in(value: t.Any, seq: t.Container) -> bool: TESTS = { - "odd": test_odd, - "even": test_even, - "divisibleby": test_divisibleby, - "defined": test_defined, - "undefined": test_undefined, - "filter": test_filter, - "test": test_test, - "none": test_none, - "boolean": test_boolean, - "false": test_false, - "true": test_true, - "integer": test_integer, - "float": test_float, - "lower": test_lower, - "upper": test_upper, - "string": test_string, - "mapping": test_mapping, - "number": test_number, - "sequence": test_sequence, - "iterable": test_iterable, - "callable": callable, - "sameas": test_sameas, - "escaped": test_escaped, - "in": test_in, - "==": operator.eq, - "eq": operator.eq, - "equalto": operator.eq, - "!=": operator.ne, - "ne": operator.ne, - ">": operator.gt, - "gt": operator.gt, - "greaterthan": operator.gt, - "ge": operator.ge, - ">=": operator.ge, - "<": operator.lt, - "lt": operator.lt, - "lessthan": operator.lt, - "<=": operator.le, - "le": operator.le, + "odd": test_odd, + "even": test_even, + "divisibleby": test_divisibleby, + "defined": test_defined, + "undefined": test_undefined, + "filter": test_filter, + "test": test_test, + "none": test_none, + "boolean": test_boolean, + "false": test_false, + "true": test_true, + "integer": test_integer, + "float": test_float, + "lower": test_lower, + "upper": test_upper, + "string": test_string, + "mapping": test_mapping, + "number": test_number, + "sequence": test_sequence, + "iterable": test_iterable, + "callable": callable, + "sameas": test_sameas, + "escaped": test_escaped, + "in": test_in, + "==": operator.eq, + "eq": operator.eq, + "equalto": operator.eq, + "!=": operator.ne, + "ne": operator.ne, + ">": operator.gt, + "gt": operator.gt, + "greaterthan": operator.gt, + "ge": operator.ge, + ">=": operator.ge, + "<": operator.lt, + "lt": operator.lt, + "lessthan": operator.lt, + "<=": operator.le, + "le": operator.le, } diff --git a/contrib/python/Jinja2/py3/jinja2/utils.py b/contrib/python/Jinja2/py3/jinja2/utils.py index 567185f41f..20bbfa90cd 100644 --- a/contrib/python/Jinja2/py3/jinja2/utils.py +++ b/contrib/python/Jinja2/py3/jinja2/utils.py @@ -1,164 +1,164 @@ -import enum -import json -import os +import enum +import json +import os import re -import typing as t -import warnings -from collections import abc +import typing as t +import warnings +from collections import abc from collections import deque -from random import choice -from random import randrange +from random import choice +from random import randrange from threading import Lock -from types import CodeType -from urllib.parse import quote_from_bytes +from types import CodeType +from urllib.parse import quote_from_bytes -import markupsafe - -if t.TYPE_CHECKING: - import typing_extensions as te - -F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +import markupsafe +if t.TYPE_CHECKING: + import typing_extensions as te + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + # special singleton representing missing values for the runtime -missing: t.Any = type("MissingType", (), {"__repr__": lambda x: "missing"})() - -internal_code: t.MutableSet[CodeType] = set() +missing: t.Any = type("MissingType", (), {"__repr__": lambda x: "missing"})() -concat = "".join +internal_code: t.MutableSet[CodeType] = set() +concat = "".join -def pass_context(f: F) -> F: - """Pass the :class:`~jinja2.runtime.Context` as the first argument - to the decorated function when called while rendering a template. - Can be used on functions, filters, and tests. +def pass_context(f: F) -> F: + """Pass the :class:`~jinja2.runtime.Context` as the first argument + to the decorated function when called while rendering a template. - If only ``Context.eval_context`` is needed, use - :func:`pass_eval_context`. If only ``Context.environment`` is - needed, use :func:`pass_environment`. + Can be used on functions, filters, and tests. - .. versionadded:: 3.0.0 - Replaces ``contextfunction`` and ``contextfilter``. + If only ``Context.eval_context`` is needed, use + :func:`pass_eval_context`. If only ``Context.environment`` is + needed, use :func:`pass_environment`. + + .. versionadded:: 3.0.0 + Replaces ``contextfunction`` and ``contextfilter``. """ - f.jinja_pass_arg = _PassArg.context # type: ignore + f.jinja_pass_arg = _PassArg.context # type: ignore return f -def pass_eval_context(f: F) -> F: - """Pass the :class:`~jinja2.nodes.EvalContext` as the first argument - to the decorated function when called while rendering a template. - See :ref:`eval-context`. - - Can be used on functions, filters, and tests. - - If only ``EvalContext.environment`` is needed, use - :func:`pass_environment`. +def pass_eval_context(f: F) -> F: + """Pass the :class:`~jinja2.nodes.EvalContext` as the first argument + to the decorated function when called while rendering a template. + See :ref:`eval-context`. - .. versionadded:: 3.0.0 - Replaces ``evalcontextfunction`` and ``evalcontextfilter``. + Can be used on functions, filters, and tests. + + If only ``EvalContext.environment`` is needed, use + :func:`pass_environment`. + + .. versionadded:: 3.0.0 + Replaces ``evalcontextfunction`` and ``evalcontextfilter``. """ - f.jinja_pass_arg = _PassArg.eval_context # type: ignore + f.jinja_pass_arg = _PassArg.eval_context # type: ignore return f -def pass_environment(f: F) -> F: - """Pass the :class:`~jinja2.Environment` as the first argument to - the decorated function when called while rendering a template. - - Can be used on functions, filters, and tests. - - .. versionadded:: 3.0.0 - Replaces ``environmentfunction`` and ``environmentfilter``. +def pass_environment(f: F) -> F: + """Pass the :class:`~jinja2.Environment` as the first argument to + the decorated function when called while rendering a template. + + Can be used on functions, filters, and tests. + + .. versionadded:: 3.0.0 + Replaces ``environmentfunction`` and ``environmentfilter``. """ - f.jinja_pass_arg = _PassArg.environment # type: ignore + f.jinja_pass_arg = _PassArg.environment # type: ignore return f -class _PassArg(enum.Enum): - context = enum.auto() - eval_context = enum.auto() - environment = enum.auto() - - @classmethod - def from_obj(cls, obj: F) -> t.Optional["_PassArg"]: - 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: +class _PassArg(enum.Enum): + context = enum.auto() + eval_context = enum.auto() + environment = enum.auto() + + @classmethod + def from_obj(cls, obj: F) -> t.Optional["_PassArg"]: + 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__) return f -def is_undefined(obj: t.Any) -> bool: +def is_undefined(obj: t.Any) -> bool: """Check if the object passed is undefined. This does nothing more than performing an instance check against :class:`Undefined` but looks nicer. This can be used for custom filters or tests that want to react to @@ -170,31 +170,31 @@ def is_undefined(obj: t.Any) -> bool: return default return var """ - from .runtime import Undefined - + from .runtime import Undefined + return isinstance(obj, Undefined) -def consume(iterable: t.Iterable[t.Any]) -> None: +def consume(iterable: t.Iterable[t.Any]) -> None: """Consumes an iterable without doing anything with it.""" - for _ in iterable: + for _ in iterable: pass -def clear_caches() -> None: - """Jinja keeps internal caches for environments and lexers. These are - used so that Jinja doesn't have to recreate environments and lexers all +def clear_caches() -> None: + """Jinja keeps internal caches for environments and lexers. These are + used so that Jinja doesn't have to recreate environments and lexers all the time. Normally you don't have to care about that but if you are measuring memory consumption you may want to clean the caches. """ - from .environment import get_spontaneous_environment - from .lexer import _lexer_cache - - get_spontaneous_environment.cache_clear() + from .environment import get_spontaneous_environment + from .lexer import _lexer_cache + + get_spontaneous_environment.cache_clear() _lexer_cache.clear() -def import_string(import_name: str, silent: bool = False) -> t.Any: +def import_string(import_name: str, silent: bool = False) -> t.Any: """Imports an object based on a string. This is useful if you want to use import paths as endpoints or something similar. An import path can be specified either in dotted notation (``xml.sax.saxutils.escape``) @@ -206,10 +206,10 @@ def import_string(import_name: str, silent: bool = False) -> t.Any: :return: imported object """ try: - if ":" in import_name: - module, obj = import_name.split(":", 1) - elif "." in import_name: - module, _, obj = import_name.rpartition(".") + if ":" in import_name: + module, obj = import_name.split(":", 1) + elif "." in import_name: + module, _, obj = import_name.rpartition(".") else: return __import__(import_name) return getattr(__import__(module, None, None, [obj]), obj) @@ -218,199 +218,199 @@ def import_string(import_name: str, silent: bool = False) -> t.Any: raise -def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO]: +def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO]: """Returns a file descriptor for the filename if that file exists, - otherwise ``None``. + otherwise ``None``. """ - if not os.path.isfile(filename): - return None + if not os.path.isfile(filename): + return None - return open(filename, mode) + return open(filename, mode) - -def object_type_repr(obj: t.Any) -> str: + +def object_type_repr(obj: t.Any) -> str: """Returns the name of the object's type. For some recognized singletons the name of the object is returned instead. (For example for `None` and `Ellipsis`). """ if obj is None: - return "None" + return "None" elif obj is Ellipsis: - return "Ellipsis" - - cls = type(obj) - - if cls.__module__ == "builtins": - return f"{cls.__name__} object" - - return f"{cls.__module__}.{cls.__name__} object" - - -def pformat(obj: t.Any) -> str: - """Format an object using :func:`pprint.pformat`.""" - from pprint import pformat # type: ignore - - return pformat(obj) - - -_http_re = re.compile( - r""" - ^ - ( - (https?://|www\.) # scheme or www - (([\w%-]+\.)+)? # subdomain - ( - [a-z]{2,63} # basic tld - | - xn--[\w%]{2,59} # idna tld - ) - | - ([\w%-]{2,63}\.)+ # basic domain - (com|net|int|edu|gov|org|info|mil) # basic tld - | - (https?://) # scheme - ( - (([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4 - | - (\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6 - ) - ) - (?::[\d]{1,5})? # port - (?:[/?#]\S*)? # path, query, and fragment - $ - """, - re.IGNORECASE | re.VERBOSE, -) -_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$") - - -def urlize( - text: str, - trim_url_limit: t.Optional[int] = None, - rel: t.Optional[str] = None, - target: t.Optional[str] = None, - extra_schemes: t.Optional[t.Iterable[str]] = None, -) -> str: - """Convert URLs in text into clickable links. - - This may not recognize links in some situations. Usually, a more - comprehensive formatter, such as a Markdown library, is a better - choice. - - Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email - addresses. Links with trailing punctuation (periods, commas, closing - parentheses) and leading punctuation (opening parentheses) are - recognized excluding the punctuation. Email addresses that include - header fields are not recognized (for example, - ``mailto:address@example.com?cc=copy@example.com``). - - :param text: Original text containing URLs to link. - :param trim_url_limit: Shorten displayed URL values to this length. - :param target: Add the ``target`` attribute to links. - :param rel: Add the ``rel`` attribute to links. - :param extra_schemes: Recognize URLs that start with these schemes - in addition to the default behavior. - - .. versionchanged:: 3.0 - The ``extra_schemes`` parameter was added. - - .. versionchanged:: 3.0 - Generate ``https://`` links for URLs without a scheme. - - .. versionchanged:: 3.0 - The parsing rules were updated. Recognize email addresses with - or without the ``mailto:`` scheme. Validate IP addresses. Ignore - parentheses and brackets in more cases. + return "Ellipsis" + + cls = type(obj) + + if cls.__module__ == "builtins": + return f"{cls.__name__} object" + + return f"{cls.__module__}.{cls.__name__} object" + + +def pformat(obj: t.Any) -> str: + """Format an object using :func:`pprint.pformat`.""" + from pprint import pformat # type: ignore + + return pformat(obj) + + +_http_re = re.compile( + r""" + ^ + ( + (https?://|www\.) # scheme or www + (([\w%-]+\.)+)? # subdomain + ( + [a-z]{2,63} # basic tld + | + xn--[\w%]{2,59} # idna tld + ) + | + ([\w%-]{2,63}\.)+ # basic domain + (com|net|int|edu|gov|org|info|mil) # basic tld + | + (https?://) # scheme + ( + (([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4 + | + (\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6 + ) + ) + (?::[\d]{1,5})? # port + (?:[/?#]\S*)? # path, query, and fragment + $ + """, + re.IGNORECASE | re.VERBOSE, +) +_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$") + + +def urlize( + text: str, + trim_url_limit: t.Optional[int] = None, + rel: t.Optional[str] = None, + target: t.Optional[str] = None, + extra_schemes: t.Optional[t.Iterable[str]] = None, +) -> str: + """Convert URLs in text into clickable links. + + This may not recognize links in some situations. Usually, a more + comprehensive formatter, such as a Markdown library, is a better + choice. + + Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email + addresses. Links with trailing punctuation (periods, commas, closing + parentheses) and leading punctuation (opening parentheses) are + recognized excluding the punctuation. Email addresses that include + header fields are not recognized (for example, + ``mailto:address@example.com?cc=copy@example.com``). + + :param text: Original text containing URLs to link. + :param trim_url_limit: Shorten displayed URL values to this length. + :param target: Add the ``target`` attribute to links. + :param rel: Add the ``rel`` attribute to links. + :param extra_schemes: Recognize URLs that start with these schemes + in addition to the default behavior. + + .. versionchanged:: 3.0 + The ``extra_schemes`` parameter was added. + + .. versionchanged:: 3.0 + Generate ``https://`` links for URLs without a scheme. + + .. versionchanged:: 3.0 + The parsing rules were updated. Recognize email addresses with + or without the ``mailto:`` scheme. Validate IP addresses. Ignore + parentheses and brackets in more cases. """ - if trim_url_limit is not None: - - def trim_url(x: str) -> str: - if len(x) > trim_url_limit: # type: ignore - return f"{x[:trim_url_limit]}..." - - return x - - else: - - def trim_url(x: str) -> str: - return x - - words = re.split(r"(\s+)", str(markupsafe.escape(text))) - rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else "" - target_attr = f' target="{markupsafe.escape(target)}"' if target else "" - + if trim_url_limit is not None: + + def trim_url(x: str) -> str: + if len(x) > trim_url_limit: # type: ignore + return f"{x[:trim_url_limit]}..." + + return x + + else: + + def trim_url(x: str) -> str: + return x + + words = re.split(r"(\s+)", str(markupsafe.escape(text))) + rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else "" + target_attr = f' target="{markupsafe.escape(target)}"' if target else "" + for i, word in enumerate(words): - head, middle, tail = "", word, "" - match = re.match(r"^([(<]|<)+", middle) - + head, middle, tail = "", word, "" + match = re.match(r"^([(<]|<)+", middle) + if match: - head = match.group() - middle = middle[match.end() :] - - # Unlike lead, which is anchored to the start of the string, - # need to check that the string ends with any of the characters - # before trying to match all of them, to avoid backtracking. - if middle.endswith((")", ">", ".", ",", "\n", ">")): - match = re.search(r"([)>.,\n]|>)+$", middle) - - if match: - tail = match.group() - middle = middle[: match.start()] - - # Prefer balancing parentheses in URLs instead of ignoring a - # trailing character. - for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"): - start_count = middle.count(start_char) - - if start_count <= middle.count(end_char): - # Balanced, or lighter on the left - continue - - # Move as many as possible from the tail to balance - for _ in range(min(start_count, tail.count(end_char))): - end_index = tail.index(end_char) + len(end_char) - # Move anything in the tail before the end char too - middle += tail[:end_index] - tail = tail[end_index:] - - if _http_re.match(middle): - if middle.startswith("https://") or middle.startswith("http://"): - middle = ( - f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>' - ) - else: - middle = ( - f'<a href="https://{middle}"{rel_attr}{target_attr}>' - f"{trim_url(middle)}</a>" - ) - - elif middle.startswith("mailto:") and _email_re.match(middle[7:]): - middle = f'<a href="{middle}">{middle[7:]}</a>' - - elif ( - "@" in middle - and not middle.startswith("www.") - and ":" not in middle - and _email_re.match(middle) - ): - middle = f'<a href="mailto:{middle}">{middle}</a>' - - elif extra_schemes is not None: - for scheme in extra_schemes: - if middle != scheme and middle.startswith(scheme): - middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>' - - words[i] = f"{head}{middle}{tail}" - - return "".join(words) - - -def generate_lorem_ipsum( - n: int = 5, html: bool = True, min: int = 20, max: int = 100 -) -> str: + head = match.group() + middle = middle[match.end() :] + + # Unlike lead, which is anchored to the start of the string, + # need to check that the string ends with any of the characters + # before trying to match all of them, to avoid backtracking. + if middle.endswith((")", ">", ".", ",", "\n", ">")): + match = re.search(r"([)>.,\n]|>)+$", middle) + + if match: + tail = match.group() + middle = middle[: match.start()] + + # Prefer balancing parentheses in URLs instead of ignoring a + # trailing character. + for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"): + start_count = middle.count(start_char) + + if start_count <= middle.count(end_char): + # Balanced, or lighter on the left + continue + + # Move as many as possible from the tail to balance + for _ in range(min(start_count, tail.count(end_char))): + end_index = tail.index(end_char) + len(end_char) + # Move anything in the tail before the end char too + middle += tail[:end_index] + tail = tail[end_index:] + + if _http_re.match(middle): + if middle.startswith("https://") or middle.startswith("http://"): + middle = ( + f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>' + ) + else: + middle = ( + f'<a href="https://{middle}"{rel_attr}{target_attr}>' + f"{trim_url(middle)}</a>" + ) + + elif middle.startswith("mailto:") and _email_re.match(middle[7:]): + middle = f'<a href="{middle}">{middle[7:]}</a>' + + elif ( + "@" in middle + and not middle.startswith("www.") + and ":" not in middle + and _email_re.match(middle) + ): + middle = f'<a href="mailto:{middle}">{middle}</a>' + + elif extra_schemes is not None: + for scheme in extra_schemes: + if middle != scheme and middle.startswith(scheme): + middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>' + + words[i] = f"{head}{middle}{tail}" + + return "".join(words) + + +def generate_lorem_ipsum( + n: int = 5, html: bool = True, min: int = 20, max: int = 100 +) -> str: """Generate some lorem ipsum for the template.""" - from .constants import LOREM_IPSUM_WORDS - + from .constants import LOREM_IPSUM_WORDS + words = LOREM_IPSUM_WORDS.split() result = [] @@ -435,81 +435,81 @@ def generate_lorem_ipsum( if idx - randrange(3, 8) > last_comma: last_comma = idx last_fullstop += 2 - word += "," + word += "," # add end of sentences if idx - randrange(10, 20) > last_fullstop: last_comma = last_fullstop = idx - word += "." + word += "." next_capitalized = True p.append(word) # ensure that the paragraph ends with a dot. - p_str = " ".join(p) - - if p_str.endswith(","): - p_str = p_str[:-1] + "." - elif not p_str.endswith("."): - p_str += "." - - result.append(p_str) - + p_str = " ".join(p) + + if p_str.endswith(","): + p_str = p_str[:-1] + "." + elif not p_str.endswith("."): + p_str += "." + + result.append(p_str) + if not html: - return "\n\n".join(result) - return markupsafe.Markup( - "\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result) - ) + return "\n\n".join(result) + return markupsafe.Markup( + "\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result) + ) -def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str: - """Quote a string for use in a URL using the given charset. +def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str: + """Quote a string for use in a URL using the given charset. - :param obj: String or bytes to quote. Other types are converted to - string then encoded to bytes using the given charset. - :param charset: Encode text to bytes using this charset. - :param for_qs: Quote "/" and use "+" for spaces. + :param obj: String or bytes to quote. Other types are converted to + string then encoded to bytes using the given charset. + :param charset: Encode text to bytes using this charset. + :param for_qs: Quote "/" and use "+" for spaces. """ - if not isinstance(obj, bytes): - if not isinstance(obj, str): - obj = str(obj) - + if not isinstance(obj, bytes): + if not isinstance(obj, str): + obj = str(obj) + obj = obj.encode(charset) - - safe = b"" if for_qs else b"/" - rv = quote_from_bytes(obj, safe) - + + safe = b"" if for_qs else b"/" + rv = quote_from_bytes(obj, safe) + if for_qs: - rv = rv.replace("%20", "+") - + rv = rv.replace("%20", "+") + 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: +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.""" # this is fast for small capacities (something below 1000) but doesn't # scale. But as long as it's only used as storage for templates this # won't do any harm. - def __init__(self, capacity: int) -> None: + def __init__(self, capacity: int) -> None: self.capacity = capacity - self._mapping: t.Dict[t.Any, t.Any] = {} - self._queue: "te.Deque[t.Any]" = deque() + self._mapping: t.Dict[t.Any, t.Any] = {} + self._queue: "te.Deque[t.Any]" = deque() self._postinit() - def _postinit(self) -> None: + def _postinit(self) -> None: # alias all queue methods for faster lookup self._popleft = self._queue.popleft self._pop = self._queue.pop @@ -517,70 +517,70 @@ class LRUCache: self._wlock = Lock() self._append = self._queue.append - def __getstate__(self) -> t.Mapping[str, t.Any]: + def __getstate__(self) -> t.Mapping[str, t.Any]: return { - "capacity": self.capacity, - "_mapping": self._mapping, - "_queue": self._queue, + "capacity": self.capacity, + "_mapping": self._mapping, + "_queue": self._queue, } - def __setstate__(self, d: t.Mapping[str, t.Any]) -> None: + def __setstate__(self, d: t.Mapping[str, t.Any]) -> None: self.__dict__.update(d) self._postinit() - def __getnewargs__(self) -> t.Tuple: + def __getnewargs__(self) -> t.Tuple: return (self.capacity,) - def copy(self) -> "LRUCache": + def copy(self) -> "LRUCache": """Return a shallow copy of the instance.""" rv = self.__class__(self.capacity) rv._mapping.update(self._mapping) - rv._queue.extend(self._queue) + rv._queue.extend(self._queue) return rv - def get(self, key: t.Any, default: t.Any = None) -> t.Any: + def get(self, key: t.Any, default: t.Any = None) -> t.Any: """Return an item from the cache dict or `default`""" try: return self[key] except KeyError: return default - def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any: + def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any: """Set `default` if the key is not in the cache otherwise leave unchanged. Return the value of this key. """ try: - return self[key] - except KeyError: - self[key] = default - return default + return self[key] + except KeyError: + self[key] = default + return default - def clear(self) -> None: + def clear(self) -> None: """Clear the cache.""" - with self._wlock: + with self._wlock: self._mapping.clear() self._queue.clear() - def __contains__(self, key: t.Any) -> bool: + def __contains__(self, key: t.Any) -> bool: """Check if a key exists in this cache.""" return key in self._mapping - def __len__(self) -> int: + def __len__(self) -> int: """Return the current size of the cache.""" return len(self._mapping) - def __repr__(self) -> str: - return f"<{type(self).__name__} {self._mapping!r}>" + def __repr__(self) -> str: + return f"<{type(self).__name__} {self._mapping!r}>" - def __getitem__(self, key: t.Any) -> t.Any: + def __getitem__(self, key: t.Any) -> t.Any: """Get an item from the cache. Moves the item up so that it has the highest priority then. Raise a `KeyError` if it does not exist. """ - with self._wlock: + with self._wlock: rv = self._mapping[key] - + if self._queue[-1] != key: try: self._remove(key) @@ -589,55 +589,55 @@ class LRUCache: # when we read, ignore the ValueError that we would # get otherwise. pass - + self._append(key) - + return rv - def __setitem__(self, key: t.Any, value: t.Any) -> None: + def __setitem__(self, key: t.Any, value: t.Any) -> None: """Sets the value for an item. Moves the item up so that it has the highest priority then. """ - with self._wlock: + with self._wlock: if key in self._mapping: self._remove(key) elif len(self._mapping) == self.capacity: del self._mapping[self._popleft()] - + self._append(key) self._mapping[key] = value - def __delitem__(self, key: t.Any) -> None: + def __delitem__(self, key: t.Any) -> None: """Remove an item from the cache dict. Raise a `KeyError` if it does not exist. """ - with self._wlock: + with self._wlock: del self._mapping[key] - + try: self._remove(key) except ValueError: pass - def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]: + def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]: """Return a list of items.""" result = [(key, self._mapping[key]) for key in list(self._queue)] result.reverse() return result - def values(self) -> t.Iterable[t.Any]: + def values(self) -> t.Iterable[t.Any]: """Return a list of all values.""" return [x[1] for x in self.items()] - def keys(self) -> t.Iterable[t.Any]: + def keys(self) -> t.Iterable[t.Any]: """Return a list of all keys ordered by most recent usage.""" return list(self) - def __iter__(self) -> t.Iterator[t.Any]: + def __iter__(self) -> t.Iterator[t.Any]: return reversed(tuple(self._queue)) - def __reversed__(self) -> t.Iterator[t.Any]: - """Iterate over the keys in the cache dict, oldest items + def __reversed__(self) -> t.Iterator[t.Any]: + """Iterate over the keys in the cache dict, oldest items coming first. """ return iter(tuple(self._queue)) @@ -645,12 +645,12 @@ class LRUCache: __copy__ = copy -def select_autoescape( - enabled_extensions: t.Collection[str] = ("html", "htm", "xml"), - disabled_extensions: t.Collection[str] = (), - default_for_string: bool = True, - default: bool = False, -) -> t.Callable[[t.Optional[str]], bool]: +def select_autoescape( + enabled_extensions: t.Collection[str] = ("html", "htm", "xml"), + disabled_extensions: t.Collection[str] = (), + default_for_string: bool = True, + default: bool = False, +) -> t.Callable[[t.Optional[str]], bool]: """Intelligently sets the initial value of autoescaping based on the filename of the template. This is the recommended way to configure autoescaping if you do not want to write a custom function yourself. @@ -685,10 +685,10 @@ def select_autoescape( .. versionadded:: 2.9 """ - enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions) - disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions) - - def autoescape(template_name: t.Optional[str]) -> bool: + enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions) + disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions) + + def autoescape(template_name: t.Optional[str]) -> bool: if template_name is None: return default_for_string template_name = template_name.lower() @@ -697,97 +697,97 @@ def select_autoescape( if template_name.endswith(disabled_patterns): return False return default - + return autoescape -def htmlsafe_json_dumps( - obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any -) -> markupsafe.Markup: - """Serialize an object to a string of JSON with :func:`json.dumps`, - then replace HTML-unsafe characters with Unicode escapes and mark - the result safe with :class:`~markupsafe.Markup`. - - This is available in templates as the ``|tojson`` filter. - - The following characters are escaped: ``<``, ``>``, ``&``, ``'``. - - The returned string is safe to render in HTML documents and - ``<script>`` tags. The exception is in HTML attributes that are - double quoted; either use single quotes or the ``|forceescape`` - filter. - - :param obj: The object to serialize to JSON. - :param dumps: The ``dumps`` function to use. Defaults to - ``env.policies["json.dumps_function"]``, which defaults to - :func:`json.dumps`. - :param kwargs: Extra arguments to pass to ``dumps``. Merged onto - ``env.policies["json.dumps_kwargs"]``. - - .. versionchanged:: 3.0 - The ``dumper`` parameter is renamed to ``dumps``. - - .. versionadded:: 2.9 - """ - if dumps is None: - dumps = json.dumps - - return markupsafe.Markup( - dumps(obj, **kwargs) - .replace("<", "\\u003c") - .replace(">", "\\u003e") - .replace("&", "\\u0026") - .replace("'", "\\u0027") - ) - - -class Cycler: - """Cycle through values by yield them one at a time, then restarting - once the end is reached. Available as ``cycler`` in templates. - - Similar to ``loop.cycle``, but can be used outside loops or across - multiple loops. For example, render a list of folders and files in a - list, alternating giving them "odd" and "even" classes. - - .. code-block:: html+jinja - - {% set row_class = cycler("odd", "even") %} - <ul class="browser"> - {% for folder in folders %} - <li class="folder {{ row_class.next() }}">{{ folder }} - {% endfor %} - {% for file in files %} - <li class="file {{ row_class.next() }}">{{ file }} - {% endfor %} - </ul> - - :param items: Each positional argument will be yielded in the order - given for each cycle. - - .. versionadded:: 2.1 +def htmlsafe_json_dumps( + obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any +) -> markupsafe.Markup: + """Serialize an object to a string of JSON with :func:`json.dumps`, + then replace HTML-unsafe characters with Unicode escapes and mark + the result safe with :class:`~markupsafe.Markup`. + + This is available in templates as the ``|tojson`` filter. + + The following characters are escaped: ``<``, ``>``, ``&``, ``'``. + + The returned string is safe to render in HTML documents and + ``<script>`` tags. The exception is in HTML attributes that are + double quoted; either use single quotes or the ``|forceescape`` + filter. + + :param obj: The object to serialize to JSON. + :param dumps: The ``dumps`` function to use. Defaults to + ``env.policies["json.dumps_function"]``, which defaults to + :func:`json.dumps`. + :param kwargs: Extra arguments to pass to ``dumps``. Merged onto + ``env.policies["json.dumps_kwargs"]``. + + .. versionchanged:: 3.0 + The ``dumper`` parameter is renamed to ``dumps``. + + .. versionadded:: 2.9 """ - - def __init__(self, *items: t.Any) -> None: + if dumps is None: + dumps = json.dumps + + return markupsafe.Markup( + dumps(obj, **kwargs) + .replace("<", "\\u003c") + .replace(">", "\\u003e") + .replace("&", "\\u0026") + .replace("'", "\\u0027") + ) + + +class Cycler: + """Cycle through values by yield them one at a time, then restarting + once the end is reached. Available as ``cycler`` in templates. + + Similar to ``loop.cycle``, but can be used outside loops or across + multiple loops. For example, render a list of folders and files in a + list, alternating giving them "odd" and "even" classes. + + .. code-block:: html+jinja + + {% set row_class = cycler("odd", "even") %} + <ul class="browser"> + {% for folder in folders %} + <li class="folder {{ row_class.next() }}">{{ folder }} + {% endfor %} + {% for file in files %} + <li class="file {{ row_class.next() }}">{{ file }} + {% endfor %} + </ul> + + :param items: Each positional argument will be yielded in the order + given for each cycle. + + .. versionadded:: 2.1 + """ + + def __init__(self, *items: t.Any) -> None: if not items: - raise RuntimeError("at least one item has to be provided") + raise RuntimeError("at least one item has to be provided") self.items = items - self.pos = 0 + self.pos = 0 - def reset(self) -> None: - """Resets the current item to the first item.""" + def reset(self) -> None: + """Resets the current item to the first item.""" self.pos = 0 @property - def current(self) -> t.Any: - """Return the current item. Equivalent to the item that will be - returned next time :meth:`next` is called. - """ + def current(self) -> t.Any: + """Return the current item. Equivalent to the item that will be + returned next time :meth:`next` is called. + """ return self.items[self.pos] - def next(self) -> t.Any: - """Return the current item, then advance :attr:`current` to the - next item. - """ + def next(self) -> t.Any: + """Return the current item, then advance :attr:`current` to the + next item. + """ rv = self.current self.pos = (self.pos + 1) % len(self.items) return rv @@ -795,60 +795,60 @@ class Cycler: __next__ = next -class Joiner: +class Joiner: """A joining helper for templates.""" - def __init__(self, sep: str = ", ") -> None: + def __init__(self, sep: str = ", ") -> None: self.sep = sep self.used = False - def __call__(self) -> str: + def __call__(self) -> str: if not self.used: self.used = True - return "" + return "" return self.sep -class Namespace: +class Namespace: """A namespace object that can hold arbitrary attributes. It may be - initialized from a dictionary or with keyword arguments.""" + initialized from a dictionary or with keyword arguments.""" - def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902 + def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902 self, args = args[0], args[1:] self.__attrs = dict(*args, **kwargs) - def __getattribute__(self, name: str) -> t.Any: - # __class__ is needed for the awaitable check in async mode - if name in {"_Namespace__attrs", "__class__"}: + def __getattribute__(self, name: str) -> t.Any: + # __class__ is needed for the awaitable check in async mode + if name in {"_Namespace__attrs", "__class__"}: return object.__getattribute__(self, name) try: return self.__attrs[name] except KeyError: - raise AttributeError(name) from None + raise AttributeError(name) from None - def __setitem__(self, name: str, value: t.Any) -> None: + def __setitem__(self, name: str, value: t.Any) -> None: self.__attrs[name] = value - 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) + 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) diff --git a/contrib/python/Jinja2/py3/jinja2/visitor.py b/contrib/python/Jinja2/py3/jinja2/visitor.py index b150e578a8..851c3623ad 100644 --- a/contrib/python/Jinja2/py3/jinja2/visitor.py +++ b/contrib/python/Jinja2/py3/jinja2/visitor.py @@ -1,19 +1,19 @@ -"""API for traversing the AST nodes. Implemented by the compiler and -meta introspection. +"""API for traversing the AST nodes. Implemented by the compiler and +meta introspection. """ -import typing as t - -from .nodes import Node - -if t.TYPE_CHECKING: - import typing_extensions as te - - class VisitCallable(te.Protocol): - def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: - ... - - -class NodeVisitor: +import typing as t + +from .nodes import Node + +if t.TYPE_CHECKING: + import typing_extensions as te + + class VisitCallable(te.Protocol): + def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: + ... + + +class NodeVisitor: """Walks the abstract syntax tree and call visitor functions for every node found. The visitor functions may return values which will be forwarded by the `visit` method. @@ -25,23 +25,23 @@ class NodeVisitor: (return value `None`) the `generic_visit` visitor is used instead. """ - def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]": + def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]": """Return the visitor function for this node or `None` if no visitor 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) # type: ignore - def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: + def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: """Visit a node.""" f = self.get_visitor(node) - + if f is not None: return f(node, *args, **kwargs) - + return self.generic_visit(node, *args, **kwargs) - def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: + def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: """Called if no explicit visitor function exists for a node.""" for node in node.iter_child_nodes(): self.visit(node, *args, **kwargs) @@ -58,7 +58,7 @@ class NodeTransformer(NodeVisitor): replacement takes place. """ - def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> Node: + def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> Node: for field, old_value in node.iter_fields(): if isinstance(old_value, list): new_values = [] @@ -80,13 +80,13 @@ class NodeTransformer(NodeVisitor): setattr(node, field, new_node) return node - def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.List[Node]: + def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.List[Node]: """As transformers may return lists in some places this method can be used to enforce a list as return value. """ rv = self.visit(node, *args, **kwargs) - + if not isinstance(rv, list): - return [rv] - + return [rv] + return rv diff --git a/contrib/python/Jinja2/py3/patches/01-arcadia.patch b/contrib/python/Jinja2/py3/patches/01-arcadia.patch index b78b648b37..4a7109dbae 100644 --- a/contrib/python/Jinja2/py3/patches/01-arcadia.patch +++ b/contrib/python/Jinja2/py3/patches/01-arcadia.patch @@ -1,30 +1,30 @@ ---- contrib/python/Jinja2/py3/jinja2/__init__.py (index) -+++ contrib/python/Jinja2/py3/jinja2/__init__.py (working tree) -@@ -25,6 +25,7 @@ from .loaders import FunctionLoader - from .loaders import ModuleLoader as ModuleLoader - from .loaders import PackageLoader as PackageLoader - from .loaders import PrefixLoader as PrefixLoader -+from .loaders import ResourceLoader as ResourceLoader - from .runtime import ChainableUndefined as ChainableUndefined - from .runtime import DebugUndefined as DebugUndefined - from .runtime import make_logging_undefined as make_logging_undefined ---- contrib/python/Jinja2/py3/jinja2/loaders.py (index) -+++ contrib/python/Jinja2/py3/jinja2/loaders.py (working tree) +--- contrib/python/Jinja2/py3/jinja2/__init__.py (index) ++++ contrib/python/Jinja2/py3/jinja2/__init__.py (working tree) +@@ -25,6 +25,7 @@ from .loaders import FunctionLoader + from .loaders import ModuleLoader as ModuleLoader + from .loaders import PackageLoader as PackageLoader + from .loaders import PrefixLoader as PrefixLoader ++from .loaders import ResourceLoader as ResourceLoader + from .runtime import ChainableUndefined as ChainableUndefined + from .runtime import DebugUndefined as DebugUndefined + from .runtime import make_logging_undefined as make_logging_undefined +--- contrib/python/Jinja2/py3/jinja2/loaders.py (index) ++++ contrib/python/Jinja2/py3/jinja2/loaders.py (working tree) @@ -650,3 +650,17 @@ class ModuleLoader(BaseLoader): - return environment.template_class.from_module_dict( - environment, mod.__dict__, globals - ) -+ -+ -+class ResourceLoader(BaseLoader): -+ def __init__(self, prefix, module_loader): -+ self.prefix = prefix -+ self.module_loader = module_loader -+ -+ def get_source(self, environment, template): -+ if self.module_loader is None: -+ raise TemplateNotFound(template) -+ try: -+ return self.module_loader.get_data(os.path.join(self.prefix, template)).decode('utf-8'), None, None -+ except IOError: -+ raise TemplateNotFound(template) + return environment.template_class.from_module_dict( + environment, mod.__dict__, globals + ) ++ ++ ++class ResourceLoader(BaseLoader): ++ def __init__(self, prefix, module_loader): ++ self.prefix = prefix ++ self.module_loader = module_loader ++ ++ def get_source(self, environment, template): ++ if self.module_loader is None: ++ raise TemplateNotFound(template) ++ try: ++ return self.module_loader.get_data(os.path.join(self.prefix, template)).decode('utf-8'), None, None ++ except IOError: ++ raise TemplateNotFound(template) diff --git a/contrib/python/Jinja2/py3/patches/02-fix-tests.patch b/contrib/python/Jinja2/py3/patches/02-fix-tests.patch index d23c1d8096..7bd87576c2 100644 --- a/contrib/python/Jinja2/py3/patches/02-fix-tests.patch +++ b/contrib/python/Jinja2/py3/patches/02-fix-tests.patch @@ -1,69 +1,69 @@ ---- contrib/python/Jinja2/py3/tests/conftest.py (index) -+++ contrib/python/Jinja2/py3/tests/conftest.py (working tree) -@@ -27,7 +27,8 @@ def package_loader(): - @pytest.fixture - def filesystem_loader(): - """returns FileSystemLoader initialized to res/templates directory""" -- here = Path(__file__).parent.resolve() -+ import yatest.common -+ here = Path(yatest.common.test_source_path()) - return loaders.FileSystemLoader(here / "res" / "templates") - - ---- contrib/python/Jinja2/py3/tests/test_loader.py (index) -+++ contrib/python/Jinja2/py3/tests/test_loader.py (working tree) -@@ -18,6 +18,8 @@ from jinja2._compat import PYPY - from jinja2.exceptions import TemplateNotFound - from jinja2.loaders import split_template_path - -+import yatest.common as yc -+ - - class TestLoaders: - def test_dict_loader(self, dict_loader): -@@ -118,7 +120,7 @@ class TestLoaders(object): - - - class TestFileSystemLoader: -- searchpath = (Path(__file__) / ".." / "res" / "templates").resolve() -+ searchpath = (Path(yc.test_source_path()) / "res" / "templates").resolve() - - @staticmethod - def _test_common(env): -@@ -146,6 +148,7 @@ class TestFileSystemLoader(object): - env = Environment(loader=filesystem_loader) - self._test_common(env) - -+ @pytest.mark.skip("Arcadia read only") - def test_caches_template_based_on_mtime(self): - filesystem_loader = loaders.FileSystemLoader(self.searchpath) - +--- contrib/python/Jinja2/py3/tests/conftest.py (index) ++++ contrib/python/Jinja2/py3/tests/conftest.py (working tree) +@@ -27,7 +27,8 @@ def package_loader(): + @pytest.fixture + def filesystem_loader(): + """returns FileSystemLoader initialized to res/templates directory""" +- here = Path(__file__).parent.resolve() ++ import yatest.common ++ here = Path(yatest.common.test_source_path()) + return loaders.FileSystemLoader(here / "res" / "templates") + + +--- contrib/python/Jinja2/py3/tests/test_loader.py (index) ++++ contrib/python/Jinja2/py3/tests/test_loader.py (working tree) +@@ -18,6 +18,8 @@ from jinja2._compat import PYPY + from jinja2.exceptions import TemplateNotFound + from jinja2.loaders import split_template_path + ++import yatest.common as yc ++ + + class TestLoaders: + def test_dict_loader(self, dict_loader): +@@ -118,7 +120,7 @@ class TestLoaders(object): + + + class TestFileSystemLoader: +- searchpath = (Path(__file__) / ".." / "res" / "templates").resolve() ++ searchpath = (Path(yc.test_source_path()) / "res" / "templates").resolve() + + @staticmethod + def _test_common(env): +@@ -146,6 +148,7 @@ class TestFileSystemLoader(object): + env = Environment(loader=filesystem_loader) + self._test_common(env) + ++ @pytest.mark.skip("Arcadia read only") + def test_caches_template_based_on_mtime(self): + filesystem_loader = loaders.FileSystemLoader(self.searchpath) + @@ -316,7 +319,7 @@ def test_package_dir_list(package_dir_loader): @pytest.fixture() def package_file_loader(monkeypatch): monkeypatch.syspath_prepend(Path(__file__).parent / "res") - return PackageLoader("__init__") + return PackageLoader("res") - + @pytest.mark.parametrize( @@ -337,7 +340,7 @@ def test_package_dir_list(package_dir_loader): - @pytest.fixture() - def package_zip_loader(monkeypatch): -- package_zip = (Path(__file__) / ".." / "res" / "package.zip").resolve() -+ package_zip = (Path(yc.test_source_path()) / "res" / "package.zip").resolve() - monkeypatch.syspath_prepend(package_zip) - return PackageLoader("t_pack") - ---- contrib/python/Jinja2/py3/tests/test_utils.py (index) -+++ contrib/python/Jinja2/py3/tests/test_utils.py (working tree) -@@ -110,7 +110,7 @@ class TestHelpers(object): - - assert object_type_repr(42) == "int object" - assert object_type_repr([]) == "list object" -- assert object_type_repr(X()) == "test_utils.X object" -+ assert object_type_repr(X()) == "__tests__.test_utils.X object" - assert object_type_repr(None) == "None" - assert object_type_repr(Ellipsis) == "Ellipsis" - + @pytest.fixture() + def package_zip_loader(monkeypatch): +- package_zip = (Path(__file__) / ".." / "res" / "package.zip").resolve() ++ package_zip = (Path(yc.test_source_path()) / "res" / "package.zip").resolve() + monkeypatch.syspath_prepend(package_zip) + return PackageLoader("t_pack") + +--- contrib/python/Jinja2/py3/tests/test_utils.py (index) ++++ contrib/python/Jinja2/py3/tests/test_utils.py (working tree) +@@ -110,7 +110,7 @@ class TestHelpers(object): + + assert object_type_repr(42) == "int object" + assert object_type_repr([]) == "list object" +- assert object_type_repr(X()) == "test_utils.X object" ++ assert object_type_repr(X()) == "__tests__.test_utils.X object" + assert object_type_repr(None) == "None" + assert object_type_repr(Ellipsis) == "Ellipsis" + diff --git a/contrib/python/Jinja2/py3/patches/03-fix-PackageLoader.patch b/contrib/python/Jinja2/py3/patches/03-fix-PackageLoader.patch index 898e0c1189..b2a7691914 100644 --- a/contrib/python/Jinja2/py3/patches/03-fix-PackageLoader.patch +++ b/contrib/python/Jinja2/py3/patches/03-fix-PackageLoader.patch @@ -1,75 +1,75 @@ ---- contrib/python/Jinja2/py3/jinja2/loaders.py (index) -+++ contrib/python/Jinja2/py3/jinja2/loaders.py (working tree) -@@ -4,6 +4,7 @@ sources. - import importlib.util - import os - import sys -+import pkgutil - import typing as t - import weakref - import zipimport -@@ -20,6 +21,8 @@ if t.TYPE_CHECKING: - from .environment import Environment - from .environment import Template - -+import __res as arcadia_res -+ - - def split_template_path(template: str) -> t.List[str]: - """Split a path into segments and perform a sanity check. If it detects -@@ -284,19 +287,22 @@ class PackageLoader(BaseLoader): - - # Make sure the package exists. This also makes namespace - # packages work, otherwise get_loader returns None. -- import_module(package_name) -+ package = import_module(package_name) - spec = importlib.util.find_spec(package_name) - assert spec is not None, "An import spec was not found for the package." - loader = spec.loader - assert loader is not None, "A loader was not found for the package." - self._loader = loader - self._archive = None -+ self._package = package - template_root = None - - 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) -+ elif hasattr(loader, "arcadia_source_finder"): -+ template_root = os.path.dirname(package.__file__) +--- contrib/python/Jinja2/py3/jinja2/loaders.py (index) ++++ contrib/python/Jinja2/py3/jinja2/loaders.py (working tree) +@@ -4,6 +4,7 @@ sources. + import importlib.util + import os + import sys ++import pkgutil + import typing as t + import weakref + import zipimport +@@ -20,6 +21,8 @@ if t.TYPE_CHECKING: + from .environment import Environment + from .environment import Template + ++import __res as arcadia_res ++ + + def split_template_path(template: str) -> t.List[str]: + """Split a path into segments and perform a sanity check. If it detects +@@ -284,19 +287,22 @@ class PackageLoader(BaseLoader): + + # Make sure the package exists. This also makes namespace + # packages work, otherwise get_loader returns None. +- import_module(package_name) ++ package = import_module(package_name) + spec = importlib.util.find_spec(package_name) + assert spec is not None, "An import spec was not found for the package." + loader = spec.loader + assert loader is not None, "A loader was not found for the package." + self._loader = loader + self._archive = None ++ self._package = package + template_root = None + + 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) ++ elif hasattr(loader, "arcadia_source_finder"): ++ template_root = os.path.dirname(package.__file__) else: roots: t.List[str] = [] @@ -329,7 +335,16 @@ class PackageLoader(BaseLoader): - p = os.path.join(self._template_root, *split_template_path(template)) - up_to_date: t.Optional[t.Callable[[], bool]] - -- if self._archive is None: -+ if self._archive is None and hasattr(self, "_package"): -+ try: -+ source = pkgutil.get_data(self.package_name, os.path.join(self.package_path, *split_template_path(template))) -+ except OSError: -+ raise TemplateNotFound(template) -+ -+ def up_to_date() -> bool: -+ return True -+ -+ elif self._archive is None: - # Package is a directory. - if not os.path.isfile(p): - raise TemplateNotFound(template) + p = os.path.join(self._template_root, *split_template_path(template)) + up_to_date: t.Optional[t.Callable[[], bool]] + +- if self._archive is None: ++ if self._archive is None and hasattr(self, "_package"): ++ try: ++ source = pkgutil.get_data(self.package_name, os.path.join(self.package_path, *split_template_path(template))) ++ except OSError: ++ raise TemplateNotFound(template) ++ ++ def up_to_date() -> bool: ++ return True ++ ++ elif self._archive is None: + # Package is a directory. + if not os.path.isfile(p): + raise TemplateNotFound(template) @@ -359,7 +374,12 @@ class PackageLoader(BaseLoader): - def list_templates(self) -> t.List[str]: - results: t.List[str] = [] - -- if self._archive is None: -+ if self._archive is None and hasattr(self, "_package"): -+ prefix = os.path.join(self._template_root, self.package_path).encode() + b"/" -+ for name in arcadia_res.resfs_files(prefix): -+ results.append(name.removeprefix(prefix).decode()) -+ -+ elif self._archive is None: - # Package is a directory. - offset = len(self._template_root) - + def list_templates(self) -> t.List[str]: + results: t.List[str] = [] + +- if self._archive is None: ++ if self._archive is None and hasattr(self, "_package"): ++ prefix = os.path.join(self._template_root, self.package_path).encode() + b"/" ++ for name in arcadia_res.resfs_files(prefix): ++ results.append(name.removeprefix(prefix).decode()) ++ ++ elif self._archive is None: + # Package is a directory. + offset = len(self._template_root) + diff --git a/contrib/python/Jinja2/py3/tests/conftest.py b/contrib/python/Jinja2/py3/tests/conftest.py index 218c3c77cf..9eb2453e5a 100644 --- a/contrib/python/Jinja2/py3/tests/conftest.py +++ b/contrib/python/Jinja2/py3/tests/conftest.py @@ -1,50 +1,50 @@ -from pathlib import Path +from pathlib import Path import pytest from jinja2 import loaders -from jinja2.environment import Environment +from jinja2.environment import Environment @pytest.fixture def env(): - """returns a new environment.""" + """returns a new environment.""" return Environment() @pytest.fixture def dict_loader(): - """returns DictLoader""" - return loaders.DictLoader({"justdict.html": "FOO"}) + """returns DictLoader""" + return loaders.DictLoader({"justdict.html": "FOO"}) @pytest.fixture def package_loader(): - """returns PackageLoader initialized from templates""" - return loaders.PackageLoader("res", "templates") + """returns PackageLoader initialized from templates""" + return loaders.PackageLoader("res", "templates") @pytest.fixture def filesystem_loader(): - """returns FileSystemLoader initialized to res/templates directory""" - import yatest.common - here = Path(yatest.common.test_source_path()) - return loaders.FileSystemLoader(here / "res" / "templates") + """returns FileSystemLoader initialized to res/templates directory""" + import yatest.common + here = Path(yatest.common.test_source_path()) + return loaders.FileSystemLoader(here / "res" / "templates") @pytest.fixture def function_loader(): - """returns a FunctionLoader""" - return loaders.FunctionLoader({"justfunction.html": "FOO"}.get) + """returns a FunctionLoader""" + return loaders.FunctionLoader({"justfunction.html": "FOO"}.get) @pytest.fixture def choice_loader(dict_loader, package_loader): - """returns a ChoiceLoader""" + """returns a ChoiceLoader""" return loaders.ChoiceLoader([dict_loader, package_loader]) @pytest.fixture def prefix_loader(filesystem_loader, dict_loader): - """returns a PrefixLoader""" - return loaders.PrefixLoader({"a": filesystem_loader, "b": dict_loader}) + """returns a PrefixLoader""" + return loaders.PrefixLoader({"a": filesystem_loader, "b": dict_loader}) diff --git a/contrib/python/Jinja2/py3/tests/res/templates/mojibake.txt b/contrib/python/Jinja2/py3/tests/res/templates/mojibake.txt index 4b94aa63dc..b23f594fcf 100644 --- a/contrib/python/Jinja2/py3/tests/res/templates/mojibake.txt +++ b/contrib/python/Jinja2/py3/tests/res/templates/mojibake.txt @@ -1 +1 @@ -文字化け +文字化け diff --git a/contrib/python/Jinja2/py3/tests/res/templates2/foo b/contrib/python/Jinja2/py3/tests/res/templates2/foo index 1c4ad3e496..40e7538a91 100644 --- a/contrib/python/Jinja2/py3/tests/res/templates2/foo +++ b/contrib/python/Jinja2/py3/tests/res/templates2/foo @@ -1,2 +1,2 @@ -Looks like the start of templates/foo/test.html -Tested by test_filesystem_loader_overlapping_names +Looks like the start of templates/foo/test.html +Tested by test_filesystem_loader_overlapping_names diff --git a/contrib/python/Jinja2/py3/tests/test_api.py b/contrib/python/Jinja2/py3/tests/test_api.py index 4db3b4a96a..a1e25d0a95 100644 --- a/contrib/python/Jinja2/py3/tests/test_api.py +++ b/contrib/python/Jinja2/py3/tests/test_api.py @@ -1,86 +1,86 @@ -import shutil +import shutil import tempfile -from pathlib import Path +from pathlib import Path import pytest - -from jinja2 import ChainableUndefined -from jinja2 import DebugUndefined -from jinja2 import DictLoader -from jinja2 import Environment -from jinja2 import is_undefined -from jinja2 import make_logging_undefined -from jinja2 import meta -from jinja2 import StrictUndefined -from jinja2 import Template -from jinja2 import TemplatesNotFound -from jinja2 import Undefined -from jinja2 import UndefinedError + +from jinja2 import ChainableUndefined +from jinja2 import DebugUndefined +from jinja2 import DictLoader +from jinja2 import Environment +from jinja2 import is_undefined +from jinja2 import make_logging_undefined +from jinja2 import meta +from jinja2 import StrictUndefined +from jinja2 import Template +from jinja2 import TemplatesNotFound +from jinja2 import Undefined +from jinja2 import UndefinedError from jinja2.compiler import CodeGenerator from jinja2.runtime import Context from jinja2.utils import Cycler -from jinja2.utils import pass_context -from jinja2.utils import pass_environment -from jinja2.utils import pass_eval_context +from jinja2.utils import pass_context +from jinja2.utils import pass_environment +from jinja2.utils import pass_eval_context -class TestExtendedAPI: +class TestExtendedAPI: def test_item_and_attribute(self, env): from jinja2.sandbox import SandboxedEnvironment for env in Environment(), SandboxedEnvironment(): - tmpl = env.from_string("{{ foo.items()|list }}") - assert tmpl.render(foo={"items": 42}) == "[('items', 42)]" + tmpl = env.from_string("{{ foo.items()|list }}") + assert tmpl.render(foo={"items": 42}) == "[('items', 42)]" tmpl = env.from_string('{{ foo|attr("items")()|list }}') - assert tmpl.render(foo={"items": 42}) == "[('items', 42)]" + assert tmpl.render(foo={"items": 42}) == "[('items', 42)]" tmpl = env.from_string('{{ foo["items"] }}') - assert tmpl.render(foo={"items": 42}) == "42" - - def test_finalize(self): - e = Environment(finalize=lambda v: "" if v is None else v) - t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}") - assert t.render(seq=(None, 1, "foo")) == "||1|foo" - - def test_finalize_constant_expression(self): - e = Environment(finalize=lambda v: "" if v is None else v) - t = e.from_string("<{{ none }}>") - assert t.render() == "<>" - - def test_no_finalize_template_data(self): - e = Environment(finalize=lambda v: type(v).__name__) - t = e.from_string("<{{ value }}>") - # If template data was finalized, it would print "strintstr". - assert t.render(value=123) == "<int>" - - def test_context_finalize(self): - @pass_context - def finalize(context, value): - return value * context["scale"] - - e = Environment(finalize=finalize) - t = e.from_string("{{ value }}") - assert t.render(value=5, scale=3) == "15" - - def test_eval_finalize(self): - @pass_eval_context - def finalize(eval_ctx, value): - return str(eval_ctx.autoescape) + value - - e = Environment(finalize=finalize, autoescape=True) - t = e.from_string("{{ value }}") - assert t.render(value="<script>") == "True<script>" - - def test_env_autoescape(self): - @pass_environment - def finalize(env, value): - return " ".join( - (env.variable_start_string, repr(value), env.variable_end_string) - ) - - e = Environment(finalize=finalize) - t = e.from_string("{{ value }}") - assert t.render(value="hello") == "{{ 'hello' }}" - + assert tmpl.render(foo={"items": 42}) == "42" + + def test_finalize(self): + e = Environment(finalize=lambda v: "" if v is None else v) + t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}") + assert t.render(seq=(None, 1, "foo")) == "||1|foo" + + def test_finalize_constant_expression(self): + e = Environment(finalize=lambda v: "" if v is None else v) + t = e.from_string("<{{ none }}>") + assert t.render() == "<>" + + def test_no_finalize_template_data(self): + e = Environment(finalize=lambda v: type(v).__name__) + t = e.from_string("<{{ value }}>") + # If template data was finalized, it would print "strintstr". + assert t.render(value=123) == "<int>" + + def test_context_finalize(self): + @pass_context + def finalize(context, value): + return value * context["scale"] + + e = Environment(finalize=finalize) + t = e.from_string("{{ value }}") + assert t.render(value=5, scale=3) == "15" + + def test_eval_finalize(self): + @pass_eval_context + def finalize(eval_ctx, value): + return str(eval_ctx.autoescape) + value + + e = Environment(finalize=finalize, autoescape=True) + t = e.from_string("{{ value }}") + assert t.render(value="<script>") == "True<script>" + + def test_env_autoescape(self): + @pass_environment + def finalize(env, value): + return " ".join( + (env.variable_start_string, repr(value), env.variable_end_string) + ) + + e = Environment(finalize=finalize) + t = e.from_string("{{ value }}") + assert t.render(value="hello") == "{{ 'hello' }}" + def test_cycler(self, env): items = 1, 2, 3 c = Cycler(*items) @@ -103,134 +103,134 @@ class TestExtendedAPI: assert expr(foo=42) == 84 def test_template_passthrough(self, env): - t = Template("Content") + t = Template("Content") assert env.get_template(t) is t assert env.select_template([t]) is t assert env.get_or_select_template([t]) is t assert env.get_or_select_template(t) is t - def test_get_template_undefined(self, env): - """Passing Undefined to get/select_template raises an - UndefinedError or shows the undefined message in the list. - """ - env.loader = DictLoader({}) - t = Undefined(name="no_name_1") - - with pytest.raises(UndefinedError): - env.get_template(t) - - with pytest.raises(UndefinedError): - env.get_or_select_template(t) - - with pytest.raises(UndefinedError): - env.select_template(t) - - with pytest.raises(TemplatesNotFound) as exc_info: - env.select_template([t, "no_name_2"]) - - exc_message = str(exc_info.value) - assert "'no_name_1' is undefined" in exc_message - assert "no_name_2" in exc_message - + def test_get_template_undefined(self, env): + """Passing Undefined to get/select_template raises an + UndefinedError or shows the undefined message in the list. + """ + env.loader = DictLoader({}) + t = Undefined(name="no_name_1") + + with pytest.raises(UndefinedError): + env.get_template(t) + + with pytest.raises(UndefinedError): + env.get_or_select_template(t) + + with pytest.raises(UndefinedError): + env.select_template(t) + + with pytest.raises(TemplatesNotFound) as exc_info: + env.select_template([t, "no_name_2"]) + + exc_message = str(exc_info.value) + assert "'no_name_1' is undefined" in exc_message + assert "no_name_2" in exc_message + def test_autoescape_autoselect(self, env): def select_autoescape(name): - if name is None or "." not in name: + if name is None or "." not in name: return False - return name.endswith(".html") - - env = Environment( - autoescape=select_autoescape, - loader=DictLoader({"test.txt": "{{ foo }}", "test.html": "{{ foo }}"}), - ) - t = env.get_template("test.txt") - assert t.render(foo="<foo>") == "<foo>" - t = env.get_template("test.html") - assert t.render(foo="<foo>") == "<foo>" - t = env.from_string("{{ foo }}") - assert t.render(foo="<foo>") == "<foo>" - - def test_sandbox_max_range(self, env): - from jinja2.sandbox import SandboxedEnvironment, MAX_RANGE - - env = SandboxedEnvironment() - t = env.from_string("{% for item in range(total) %}{{ item }}{% endfor %}") - - with pytest.raises(OverflowError): - t.render(total=MAX_RANGE + 1) - - -class TestMeta: + return name.endswith(".html") + + env = Environment( + autoescape=select_autoescape, + loader=DictLoader({"test.txt": "{{ foo }}", "test.html": "{{ foo }}"}), + ) + t = env.get_template("test.txt") + assert t.render(foo="<foo>") == "<foo>" + t = env.get_template("test.html") + assert t.render(foo="<foo>") == "<foo>" + t = env.from_string("{{ foo }}") + assert t.render(foo="<foo>") == "<foo>" + + def test_sandbox_max_range(self, env): + from jinja2.sandbox import SandboxedEnvironment, MAX_RANGE + + env = SandboxedEnvironment() + t = env.from_string("{% for item in range(total) %}{{ item }}{% endfor %}") + + with pytest.raises(OverflowError): + t.render(total=MAX_RANGE + 1) + + +class TestMeta: def test_find_undeclared_variables(self, env): - ast = env.parse("{% set foo = 42 %}{{ bar + foo }}") + ast = env.parse("{% set foo = 42 %}{{ bar + foo }}") x = meta.find_undeclared_variables(ast) - assert x == {"bar"} - - ast = env.parse( - "{% set foo = 42 %}{{ bar + foo }}" - "{% macro meh(x) %}{{ x }}{% endmacro %}" - "{% for item in seq %}{{ muh(item) + meh(seq) }}" - "{% endfor %}" - ) + assert x == {"bar"} + + ast = env.parse( + "{% set foo = 42 %}{{ bar + foo }}" + "{% macro meh(x) %}{{ x }}{% endmacro %}" + "{% for item in seq %}{{ muh(item) + meh(seq) }}" + "{% endfor %}" + ) x = meta.find_undeclared_variables(ast) - assert x == {"bar", "seq", "muh"} - - ast = env.parse("{% for x in range(5) %}{{ x }}{% endfor %}{{ foo }}") - x = meta.find_undeclared_variables(ast) - assert x == {"foo"} + assert x == {"bar", "seq", "muh"} + ast = env.parse("{% for x in range(5) %}{{ x }}{% endfor %}{{ foo }}") + x = meta.find_undeclared_variables(ast) + assert x == {"foo"} + def test_find_refererenced_templates(self, env): ast = env.parse('{% extends "layout.html" %}{% include helper %}') i = meta.find_referenced_templates(ast) - assert next(i) == "layout.html" + assert next(i) == "layout.html" assert next(i) is None assert list(i) == [] - ast = env.parse( - '{% extends "layout.html" %}' - '{% from "test.html" import a, b as c %}' - '{% import "meh.html" as meh %}' - '{% include "muh.html" %}' - ) + ast = env.parse( + '{% extends "layout.html" %}' + '{% from "test.html" import a, b as c %}' + '{% import "meh.html" as meh %}' + '{% include "muh.html" %}' + ) i = meta.find_referenced_templates(ast) - assert list(i) == ["layout.html", "test.html", "meh.html", "muh.html"] + assert list(i) == ["layout.html", "test.html", "meh.html", "muh.html"] def test_find_included_templates(self, env): ast = env.parse('{% include ["foo.html", "bar.html"] %}') i = meta.find_referenced_templates(ast) - assert list(i) == ["foo.html", "bar.html"] + assert list(i) == ["foo.html", "bar.html"] ast = env.parse('{% include ("foo.html", "bar.html") %}') i = meta.find_referenced_templates(ast) - assert list(i) == ["foo.html", "bar.html"] + assert list(i) == ["foo.html", "bar.html"] ast = env.parse('{% include ["foo.html", "bar.html", foo] %}') i = meta.find_referenced_templates(ast) - assert list(i) == ["foo.html", "bar.html", None] + assert list(i) == ["foo.html", "bar.html", None] ast = env.parse('{% include ("foo.html", "bar.html", foo) %}') i = meta.find_referenced_templates(ast) - assert list(i) == ["foo.html", "bar.html", None] + assert list(i) == ["foo.html", "bar.html", None] -class TestStreaming: +class TestStreaming: def test_basic_streaming(self, env): - t = env.from_string( - "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>" - "{%- endfor %}</ul>" - ) - stream = t.stream(seq=list(range(3))) - assert next(stream) == "<ul>" - assert "".join(stream) == "<li>1 - 0</li><li>2 - 1</li><li>3 - 2</li></ul>" + t = env.from_string( + "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>" + "{%- endfor %}</ul>" + ) + stream = t.stream(seq=list(range(3))) + assert next(stream) == "<ul>" + assert "".join(stream) == "<li>1 - 0</li><li>2 - 1</li><li>3 - 2</li></ul>" def test_buffered_streaming(self, env): - tmpl = env.from_string( - "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>" - "{%- endfor %}</ul>" - ) - stream = tmpl.stream(seq=list(range(3))) + tmpl = env.from_string( + "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>" + "{%- endfor %}</ul>" + ) + stream = tmpl.stream(seq=list(range(3))) stream.enable_buffering(size=3) - assert next(stream) == "<ul><li>1" - assert next(stream) == " - 0</li>" + assert next(stream) == "<ul><li>1" + assert next(stream) == " - 0</li>" def test_streaming_behavior(self, env): tmpl = env.from_string("") @@ -242,193 +242,193 @@ class TestStreaming: assert not stream.buffered def test_dump_stream(self, env): - tmp = Path(tempfile.mkdtemp()) + tmp = Path(tempfile.mkdtemp()) try: - tmpl = env.from_string("\u2713") + tmpl = env.from_string("\u2713") stream = tmpl.stream() - stream.dump(str(tmp / "dump.txt"), "utf-8") - assert (tmp / "dump.txt").read_bytes() == b"\xe2\x9c\x93" + stream.dump(str(tmp / "dump.txt"), "utf-8") + assert (tmp / "dump.txt").read_bytes() == b"\xe2\x9c\x93" finally: shutil.rmtree(tmp) -class TestUndefined: +class TestUndefined: def test_stopiteration_is_undefined(self): def test(): raise StopIteration() - - t = Template("A{{ test() }}B") - assert t.render(test=test) == "AB" - t = Template("A{{ test().missingattribute }}B") + + t = Template("A{{ test() }}B") + assert t.render(test=test) == "AB" + t = Template("A{{ test().missingattribute }}B") pytest.raises(UndefinedError, t.render, test=test) def test_undefined_and_special_attributes(self): - with pytest.raises(AttributeError): - Undefined("Foo").__dict__ - - def test_undefined_attribute_error(self): - # Django's LazyObject turns the __class__ attribute into a - # property that resolves the wrapped function. If that wrapped - # function raises an AttributeError, printing the repr of the - # object in the undefined message would cause a RecursionError. - class Error: - @property # type: ignore - def __class__(self): - raise AttributeError() - - u = Undefined(obj=Error(), name="hello") - - with pytest.raises(UndefinedError): - getattr(u, "recursion", None) - + with pytest.raises(AttributeError): + Undefined("Foo").__dict__ + + def test_undefined_attribute_error(self): + # Django's LazyObject turns the __class__ attribute into a + # property that resolves the wrapped function. If that wrapped + # function raises an AttributeError, printing the repr of the + # object in the undefined message would cause a RecursionError. + class Error: + @property # type: ignore + def __class__(self): + raise AttributeError() + + u = Undefined(obj=Error(), name="hello") + + with pytest.raises(UndefinedError): + getattr(u, "recursion", None) + def test_logging_undefined(self): _messages = [] - class DebugLogger: + class DebugLogger: def warning(self, msg, *args): - _messages.append("W:" + msg % args) + _messages.append("W:" + msg % args) def error(self, msg, *args): - _messages.append("E:" + msg % args) + _messages.append("E:" + msg % args) logging_undefined = make_logging_undefined(DebugLogger()) env = Environment(undefined=logging_undefined) - assert env.from_string("{{ missing }}").render() == "" - pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) - 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" + assert env.from_string("{{ missing }}").render() == "" + pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) + 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" assert _messages == [ - "W:Template variable warning: 'missing' is undefined", + "W:Template variable warning: 'missing' is undefined", "E:Template variable error: 'missing' is undefined", - "W:Template variable warning: 'missing' is undefined", - "W:Template variable warning: 'int object' has no attribute 'missing'", - "W:Template variable warning: 'missing' is undefined", + "W:Template variable warning: 'missing' is undefined", + "W:Template variable warning: 'int object' has no attribute 'missing'", + "W:Template variable warning: 'missing' is undefined", ] def test_default_undefined(self): env = Environment(undefined=Undefined) - assert env.from_string("{{ missing }}").render() == "" - pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) - 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) - assert env.from_string("{{ 'foo' in missing }}").render() == "False" - und1 = Undefined(name="x") - und2 = Undefined(name="y") - assert und1 == und2 - assert und1 != 42 - assert hash(und1) == hash(und2) == hash(Undefined()) - with pytest.raises(AttributeError): - getattr(Undefined, "__slots__") # noqa: B009 - - def test_chainable_undefined(self): - env = Environment(undefined=ChainableUndefined) - # The following tests are copied from test_default_undefined - assert env.from_string("{{ missing }}").render() == "" - 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) - with pytest.raises(AttributeError): - getattr(ChainableUndefined, "__slots__") # noqa: B009 - - # The following tests ensure subclass functionality works as expected - assert env.from_string('{{ missing.bar["baz"] }}').render() == "" - assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() == "foo" - assert ( - env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(foo=42) - == "bar" - ) - assert ( - env.from_string('{{ foo.bar["baz"]._undefined_name }}').render( - foo={"bar": 42} - ) - == "baz" - ) - + assert env.from_string("{{ missing }}").render() == "" + pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) + 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) + assert env.from_string("{{ 'foo' in missing }}").render() == "False" + und1 = Undefined(name="x") + und2 = Undefined(name="y") + assert und1 == und2 + assert und1 != 42 + assert hash(und1) == hash(und2) == hash(Undefined()) + with pytest.raises(AttributeError): + getattr(Undefined, "__slots__") # noqa: B009 + + def test_chainable_undefined(self): + env = Environment(undefined=ChainableUndefined) + # The following tests are copied from test_default_undefined + assert env.from_string("{{ missing }}").render() == "" + 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) + with pytest.raises(AttributeError): + getattr(ChainableUndefined, "__slots__") # noqa: B009 + + # The following tests ensure subclass functionality works as expected + assert env.from_string('{{ missing.bar["baz"] }}').render() == "" + assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() == "foo" + assert ( + env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(foo=42) + == "bar" + ) + assert ( + env.from_string('{{ foo.bar["baz"]._undefined_name }}').render( + foo={"bar": 42} + ) + == "baz" + ) + def test_debug_undefined(self): env = Environment(undefined=DebugUndefined) - assert env.from_string("{{ missing }}").render() == "{{ missing }}" - pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) - 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) - == "{{ no such element: int object['missing'] }}" - ) - assert env.from_string("{{ not missing }}").render() == "True" - undefined_hint = "this is testing undefined hint of DebugUndefined" - assert ( - str(DebugUndefined(hint=undefined_hint)) - == f"{{{{ undefined value printed: {undefined_hint} }}}}" - ) - with pytest.raises(AttributeError): - getattr(DebugUndefined, "__slots__") # noqa: B009 + assert env.from_string("{{ missing }}").render() == "{{ missing }}" + pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) + 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) + == "{{ no such element: int object['missing'] }}" + ) + assert env.from_string("{{ not missing }}").render() == "True" + undefined_hint = "this is testing undefined hint of DebugUndefined" + assert ( + str(DebugUndefined(hint=undefined_hint)) + == f"{{{{ undefined value printed: {undefined_hint} }}}}" + ) + with pytest.raises(AttributeError): + getattr(DebugUndefined, "__slots__") # noqa: B009 def test_strict_undefined(self): env = Environment(undefined=StrictUndefined) - pytest.raises(UndefinedError, env.from_string("{{ missing }}").render) - pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) - pytest.raises(UndefinedError, env.from_string("{{ missing|list }}").render) - pytest.raises(UndefinedError, env.from_string("{{ 'foo' in missing }}").render) - assert env.from_string("{{ missing is not defined }}").render() == "True" - pytest.raises( - UndefinedError, env.from_string("{{ foo.missing }}").render, foo=42 - ) - pytest.raises(UndefinedError, env.from_string("{{ not missing }}").render) - assert ( - env.from_string('{{ missing|default("default", true) }}').render() - == "default" - ) - with pytest.raises(AttributeError): - getattr(StrictUndefined, "__slots__") # noqa: B009 - assert env.from_string('{{ "foo" if false }}').render() == "" + pytest.raises(UndefinedError, env.from_string("{{ missing }}").render) + pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render) + pytest.raises(UndefinedError, env.from_string("{{ missing|list }}").render) + pytest.raises(UndefinedError, env.from_string("{{ 'foo' in missing }}").render) + assert env.from_string("{{ missing is not defined }}").render() == "True" + pytest.raises( + UndefinedError, env.from_string("{{ foo.missing }}").render, foo=42 + ) + pytest.raises(UndefinedError, env.from_string("{{ not missing }}").render) + assert ( + env.from_string('{{ missing|default("default", true) }}').render() + == "default" + ) + with pytest.raises(AttributeError): + getattr(StrictUndefined, "__slots__") # noqa: B009 + assert env.from_string('{{ "foo" if false }}').render() == "" def test_indexing_gives_undefined(self): t = Template("{{ var[42].foo }}") pytest.raises(UndefinedError, t.render, var=0) def test_none_gives_proper_error(self): - with pytest.raises(UndefinedError, match="'None' has no attribute 'split'"): - Environment().getattr(None, "split")() + with pytest.raises(UndefinedError, match="'None' has no attribute 'split'"): + Environment().getattr(None, "split")() def test_object_repr(self): - with pytest.raises( - UndefinedError, match="'int object' has no attribute 'upper'" - ): - Undefined(obj=42, name="upper")() + with pytest.raises( + UndefinedError, match="'int object' has no attribute 'upper'" + ): + Undefined(obj=42, name="upper")() -class TestLowLevel: +class TestLowLevel: def test_custom_code_generator(self): class CustomCodeGenerator(CodeGenerator): def visit_Const(self, node, frame=None): # This method is pure nonsense, but works fine for testing... - if node.value == "foo": - self.write(repr("bar")) + if node.value == "foo": + self.write(repr("bar")) else: - super().visit_Const(node, frame) + super().visit_Const(node, frame) class CustomEnvironment(Environment): code_generator_class = CustomCodeGenerator env = CustomEnvironment() tmpl = env.from_string('{% set foo = "foo" %}{{ foo }}') - assert tmpl.render() == "bar" + assert tmpl.render() == "bar" def test_custom_context(self): class CustomContext(Context): def resolve_or_missing(self, key): - return "resolve-" + key + return "resolve-" + key class CustomEnvironment(Environment): context_class = CustomContext env = CustomEnvironment() - tmpl = env.from_string("{{ foo }}") - assert tmpl.render() == "resolve-foo" + tmpl = env.from_string("{{ foo }}") + assert tmpl.render() == "resolve-foo" diff --git a/contrib/python/Jinja2/py3/tests/test_async.py b/contrib/python/Jinja2/py3/tests/test_async.py index 375a7bac33..e30c528562 100644 --- a/contrib/python/Jinja2/py3/tests/test_async.py +++ b/contrib/python/Jinja2/py3/tests/test_async.py @@ -1,46 +1,46 @@ import asyncio -import sys - -import pytest - -from jinja2 import ChainableUndefined -from jinja2 import DictLoader -from jinja2 import Environment -from jinja2 import Template -from jinja2.async_utils import auto_aiter -from jinja2.exceptions import TemplateNotFound -from jinja2.exceptions import TemplatesNotFound -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) - - +import sys + +import pytest + +from jinja2 import ChainableUndefined +from jinja2 import DictLoader +from jinja2 import Environment +from jinja2 import Template +from jinja2.async_utils import auto_aiter +from jinja2.exceptions import TemplateNotFound +from jinja2.exceptions import TemplatesNotFound +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 - ) - + t = Template( + "{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True + ) + async def func(): return await t.render_async() rv = run(func()) - assert rv == "[1][2][3]" + assert rv == "[1][2][3]" def test_await_on_calls(): - t = Template("{{ async_func() + normal_func() }}", enable_async=True) + t = Template("{{ async_func() + normal_func() }}", enable_async=True) async def async_func(): return 42 @@ -49,14 +49,14 @@ def test_await_on_calls(): return 23 async def func(): - return await t.render_async(async_func=async_func, normal_func=normal_func) + return await t.render_async(async_func=async_func, normal_func=normal_func) rv = run(func()) - assert rv == "65" + assert rv == "65" def test_await_on_calls_normal_render(): - t = Template("{{ async_func() + normal_func() }}", enable_async=True) + t = Template("{{ async_func() + normal_func() }}", enable_async=True) async def async_func(): return 42 @@ -64,16 +64,16 @@ def test_await_on_calls_normal_render(): def normal_func(): return 23 - rv = t.render(async_func=async_func, normal_func=normal_func) + rv = t.render(async_func=async_func, normal_func=normal_func) - assert rv == "65" + assert rv == "65" def test_await_and_macros(): - t = Template( - "{% macro foo(x) %}[{{ x }}][{{ async_func() }}]{% endmacro %}{{ foo(42) }}", - enable_async=True, - ) + t = Template( + "{% macro foo(x) %}[{{ x }}][{{ async_func() }}]{% endmacro %}{{ foo(42) }}", + enable_async=True, + ) async def async_func(): return 42 @@ -82,87 +82,87 @@ def test_await_and_macros(): return await t.render_async(async_func=async_func) rv = run(func()) - assert rv == "[42][42]" + assert rv == "[42][42]" def test_async_blocks(): - t = Template( - "{% block foo %}<Test>{% endblock %}{{ self.foo() }}", - enable_async=True, - autoescape=True, - ) - + t = Template( + "{% block foo %}<Test>{% endblock %}{{ self.foo() }}", + enable_async=True, + autoescape=True, + ) + async def func(): return await t.render_async() rv = run(func()) - assert rv == "<Test><Test>" + assert rv == "<Test><Test>" def test_async_generate(): - t = Template("{% for x in [1, 2, 3] %}{{ x }}{% endfor %}", enable_async=True) + t = Template("{% for x in [1, 2, 3] %}{{ x }}{% endfor %}", enable_async=True) rv = list(t.generate()) - assert rv == ["1", "2", "3"] + assert rv == ["1", "2", "3"] def test_async_iteration_in_templates(): - t = Template("{% for x in rng %}{{ x }}{% endfor %}", enable_async=True) - + t = Template("{% for x in rng %}{{ x }}{% endfor %}", enable_async=True) + async def async_iterator(): for item in [1, 2, 3]: yield item - + rv = list(t.generate(rng=async_iterator())) - assert rv == ["1", "2", "3"] + assert rv == ["1", "2", "3"] def test_async_iteration_in_templates_extended(): - t = Template( - "{% for x in rng %}{{ loop.index0 }}/{{ x }}{% endfor %}", enable_async=True - ) - stream = t.generate(rng=auto_aiter(range(1, 4))) - assert next(stream) == "0" - assert "".join(stream) == "/11/22/3" + t = Template( + "{% for x in rng %}{{ loop.index0 }}/{{ x }}{% endfor %}", enable_async=True + ) + stream = t.generate(rng=auto_aiter(range(1, 4))) + assert next(stream) == "0" + assert "".join(stream) == "/11/22/3" @pytest.fixture def test_env_async(): - env = Environment( - loader=DictLoader( - dict( - module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}", - header="[{{ foo }}|{{ 23 }}]", - o_printer="({{ o }})", - ) - ), - enable_async=True, - ) - env.globals["bar"] = 23 + env = Environment( + loader=DictLoader( + dict( + module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}", + header="[{{ foo }}|{{ 23 }}]", + o_printer="({{ o }})", + ) + ), + enable_async=True, + ) + env.globals["bar"] = 23 return env -class TestAsyncImports: +class TestAsyncImports: def test_context_imports(self, test_env_async): t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}') - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env_async.from_string( '{% import "module" as m without context %}{{ m.test() }}' ) - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env_async.from_string( '{% import "module" as m with context %}{{ m.test() }}' ) - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" t = test_env_async.from_string('{% from "module" import test %}{{ test() }}') - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env_async.from_string( '{% from "module" import test without context %}{{ test() }}' ) - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env_async.from_string( '{% from "module" import test with context %}{{ test() }}' ) - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" def test_trailing_comma(self, test_env_async): test_env_async.from_string('{% from "foo" import bar, baz with context %}') @@ -172,113 +172,113 @@ 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( - """ + m = run( + 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()) == "..." - assert not hasattr(m, "__missing") + """ + )._get_default_module_async() + ) + assert run(m.toplevel()) == "..." + assert not hasattr(m, "__missing") assert m.variable == 42 - assert not hasattr(m, "notthere") - - def test_import_with_globals(self, test_env_async): - t = test_env_async.from_string( - '{% import "module" as m %}{{ m.test() }}', globals={"foo": 42} - ) - assert t.render() == "[42|23]" - - t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}') - assert t.render() == "[|23]" - - def test_import_with_globals_override(self, test_env_async): - t = test_env_async.from_string( - '{% set foo = 41 %}{% import "module" as m %}{{ m.test() }}', - globals={"foo": 42}, - ) - assert t.render() == "[42|23]" - - def test_from_import_with_globals(self, test_env_async): - t = test_env_async.from_string( - '{% from "module" import test %}{{ test() }}', - globals={"foo": 42}, - ) - assert t.render() == "[42|23]" - - -class TestAsyncIncludes: + assert not hasattr(m, "notthere") + + def test_import_with_globals(self, test_env_async): + t = test_env_async.from_string( + '{% import "module" as m %}{{ m.test() }}', globals={"foo": 42} + ) + assert t.render() == "[42|23]" + + t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}') + assert t.render() == "[|23]" + + def test_import_with_globals_override(self, test_env_async): + t = test_env_async.from_string( + '{% set foo = 41 %}{% import "module" as m %}{{ m.test() }}', + globals={"foo": 42}, + ) + assert t.render() == "[42|23]" + + def test_from_import_with_globals(self, test_env_async): + t = test_env_async.from_string( + '{% from "module" import test %}{{ test() }}', + globals={"foo": 42}, + ) + assert t.render() == "[42|23]" + + +class TestAsyncIncludes: def test_context_include(self, test_env_async): t = test_env_async.from_string('{% include "header" %}') - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" t = test_env_async.from_string('{% include "header" with context %}') - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" t = test_env_async.from_string('{% include "header" without context %}') - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" def test_choice_includes(self, test_env_async): t = test_env_async.from_string('{% include ["missing", "header"] %}') - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" t = test_env_async.from_string( '{% include ["missing", "missing2"] ignore missing %}' ) - assert t.render(foo=42) == "" + assert t.render(foo=42) == "" t = test_env_async.from_string('{% include ["missing", "missing2"] %}') pytest.raises(TemplateNotFound, t.render) - with pytest.raises(TemplatesNotFound) as e: + with pytest.raises(TemplatesNotFound) as e: t.render() - assert e.value.templates == ["missing", "missing2"] - assert e.value.name == "missing2" - + assert e.value.templates == ["missing", "missing2"] + assert e.value.name == "missing2" + def test_includes(t, **ctx): - ctx["foo"] = 42 - assert t.render(ctx) == "[42|23]" + ctx["foo"] = 42 + assert t.render(ctx) == "[42|23]" t = test_env_async.from_string('{% include ["missing", "header"] %}') test_includes(t) - t = test_env_async.from_string("{% include x %}") - test_includes(t, x=["missing", "header"]) + t = test_env_async.from_string("{% include x %}") + test_includes(t, x=["missing", "header"]) t = test_env_async.from_string('{% include [x, "header"] %}') - test_includes(t, x="missing") - t = test_env_async.from_string("{% include x %}") - test_includes(t, x="header") - t = test_env_async.from_string("{% include x %}") - test_includes(t, x="header") - t = test_env_async.from_string("{% include [x] %}") - test_includes(t, x="header") + test_includes(t, x="missing") + t = test_env_async.from_string("{% include x %}") + test_includes(t, x="header") + t = test_env_async.from_string("{% include x %}") + test_includes(t, x="header") + t = test_env_async.from_string("{% include [x] %}") + test_includes(t, x="header") def test_include_ignoring_missing(self, test_env_async): t = test_env_async.from_string('{% include "missing" %}') pytest.raises(TemplateNotFound, t.render) - for extra in "", "with context", "without context": - t = test_env_async.from_string( - '{% include "missing" ignore missing ' + extra + " %}" - ) - assert t.render() == "" + for extra in "", "with context", "without context": + t = test_env_async.from_string( + '{% include "missing" ignore missing ' + extra + " %}" + ) + assert t.render() == "" def test_context_include_with_overrides(self, test_env_async): - env = Environment( - loader=DictLoader( - dict( - main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}", - item="{{ item }}", - ) - ) - ) + env = Environment( + loader=DictLoader( + dict( + main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}", + item="{{ item }}", + ) + ) + ) assert env.get_template("main").render() == "123" def test_unoptimized_scopes(self, test_env_async): - t = test_env_async.from_string( - """ + t = test_env_async.from_string( + """ {% macro outer(o) %} {% macro inner() %} {% include "o_printer" %} @@ -286,18 +286,18 @@ class TestAsyncIncludes: {{ inner() }} {% endmacro %} {{ outer("FOO") }} - """ - ) - assert t.render().strip() == "(FOO)" + """ + ) + assert t.render().strip() == "(FOO)" def test_unoptimized_scopes_autoescape(self): - env = Environment( - loader=DictLoader({"o_printer": "({{ o }})"}), - autoescape=True, - enable_async=True, - ) - t = env.from_string( - """ + env = Environment( + loader=DictLoader({"o_printer": "({{ o }})"}), + autoescape=True, + enable_async=True, + ) + t = env.from_string( + """ {% macro outer(o) %} {% macro inner() %} {% include "o_printer" %} @@ -305,228 +305,228 @@ class TestAsyncIncludes: {{ inner() }} {% endmacro %} {{ outer("FOO") }} - """ - ) - assert t.render().strip() == "(FOO)" + """ + ) + assert t.render().strip() == "(FOO)" -class TestAsyncForLoop: +class TestAsyncForLoop: def test_simple(self, test_env_async): - tmpl = test_env_async.from_string("{% for item in seq %}{{ item }}{% endfor %}") - assert tmpl.render(seq=list(range(10))) == "0123456789" + tmpl = test_env_async.from_string("{% for item in seq %}{{ item }}{% endfor %}") + assert tmpl.render(seq=list(range(10))) == "0123456789" def test_else(self, test_env_async): tmpl = test_env_async.from_string( - "{% for item in seq %}XXX{% else %}...{% endfor %}" - ) - assert tmpl.render() == "..." + "{% for item in seq %}XXX{% else %}...{% endfor %}" + ) + assert tmpl.render() == "..." def test_empty_blocks(self, test_env_async): - tmpl = test_env_async.from_string( - "<{% for item in seq %}{% else %}{% endfor %}>" - ) - assert tmpl.render() == "<>" - - @pytest.mark.parametrize( - "transform", [lambda x: x, iter, reversed, lambda x: (i for i in x), auto_aiter] - ) - def test_context_vars(self, test_env_async, transform): - t = test_env_async.from_string( - "{% for item in seq %}{{ loop.index }}|{{ loop.index0 }}" - "|{{ loop.revindex }}|{{ loop.revindex0 }}|{{ loop.first }}" - "|{{ loop.last }}|{{ loop.length }}\n{% endfor %}" - ) - out = t.render(seq=transform([42, 24])) - assert out == "1|0|2|1|True|False|2\n2|1|1|0|False|True|2\n" + tmpl = test_env_async.from_string( + "<{% for item in seq %}{% else %}{% endfor %}>" + ) + assert tmpl.render() == "<>" + + @pytest.mark.parametrize( + "transform", [lambda x: x, iter, reversed, lambda x: (i for i in x), auto_aiter] + ) + def test_context_vars(self, test_env_async, transform): + t = test_env_async.from_string( + "{% for item in seq %}{{ loop.index }}|{{ loop.index0 }}" + "|{{ loop.revindex }}|{{ loop.revindex0 }}|{{ loop.first }}" + "|{{ loop.last }}|{{ loop.length }}\n{% endfor %}" + ) + out = t.render(seq=transform([42, 24])) + assert out == "1|0|2|1|True|False|2\n2|1|1|0|False|True|2\n" def test_cycling(self, test_env_async): - tmpl = test_env_async.from_string( - """{% for item in seq %}{{ + tmpl = test_env_async.from_string( + """{% for item in seq %}{{ loop.cycle('<1>', '<2>') }}{% endfor %}{% - for item in seq %}{{ loop.cycle(*through) }}{% endfor %}""" - ) - output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>")) - assert output == "<1><2>" * 4 + for item in seq %}{{ loop.cycle(*through) }}{% endfor %}""" + ) + output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>")) + assert output == "<1><2>" * 4 def test_lookaround(self, test_env_async): - tmpl = test_env_async.from_string( - """{% for item in seq -%} + tmpl = test_env_async.from_string( + """{% for item in seq -%} {{ loop.previtem|default('x') }}-{{ item }}-{{ loop.nextitem|default('x') }}| - {%- endfor %}""" - ) + {%- endfor %}""" + ) output = tmpl.render(seq=list(range(4))) - assert output == "x-0-1|0-1-2|1-2-3|2-3-x|" + assert output == "x-0-1|0-1-2|1-2-3|2-3-x|" def test_changed(self, test_env_async): - tmpl = test_env_async.from_string( - """{% for item in seq -%} + tmpl = test_env_async.from_string( + """{% for item in seq -%} {{ loop.changed(item) }}, - {%- endfor %}""" - ) + {%- endfor %}""" + ) output = tmpl.render(seq=[None, None, 1, 2, 2, 3, 4, 4, 4]) - assert output == "True,False,True,True,False,True,True,False,False," + assert output == "True,False,True,True,False,True,True,False,False," def test_scope(self, test_env_async): - tmpl = test_env_async.from_string("{% for item in seq %}{% endfor %}{{ item }}") + tmpl = test_env_async.from_string("{% for item in seq %}{% endfor %}{{ item }}") output = tmpl.render(seq=list(range(10))) assert not output def test_varlen(self, test_env_async): def inner(): - yield from range(5) - - tmpl = test_env_async.from_string( - "{% for item in iter %}{{ item }}{% endfor %}" - ) + yield from range(5) + + tmpl = test_env_async.from_string( + "{% for item in iter %}{{ item }}{% endfor %}" + ) output = tmpl.render(iter=inner()) - assert output == "01234" + assert output == "01234" def test_noniter(self, test_env_async): - tmpl = test_env_async.from_string("{% for item in none %}...{% endfor %}") + tmpl = test_env_async.from_string("{% for item in none %}...{% endfor %}") pytest.raises(TypeError, tmpl.render) def test_recursive(self, test_env_async): - tmpl = test_env_async.from_string( - """{% for item in seq recursive -%} + tmpl = test_env_async.from_string( + """{% for item in seq recursive -%} [{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] - {%- endfor %}""" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[1<[1][2]>][2<[1][2]>][3<[a]>]" - ) + {%- endfor %}""" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[1<[1][2]>][2<[1][2]>][3<[a]>]" + ) def test_recursive_lookaround(self, test_env_async): - tmpl = test_env_async.from_string( - """{% for item in seq recursive -%} + tmpl = test_env_async.from_string( + """{% for item in seq recursive -%} [{{ loop.previtem.a if loop.previtem is defined else 'x' }}.{{ item.a }}.{{ loop.nextitem.a if loop.nextitem is defined else 'x' }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] - {%- endfor %}""" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]" - ) + {%- endfor %}""" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]" + ) def test_recursive_depth0(self, test_env_async): - tmpl = test_env_async.from_string( - "{% for item in seq recursive %}[{{ loop.depth0 }}:{{ item.a }}" - "{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]" - ) + tmpl = test_env_async.from_string( + "{% for item in seq recursive %}[{{ loop.depth0 }}:{{ item.a }}" + "{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]" + ) def test_recursive_depth(self, test_env_async): - tmpl = test_env_async.from_string( - "{% for item in seq recursive %}[{{ loop.depth }}:{{ item.a }}" - "{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]" - ) + tmpl = test_env_async.from_string( + "{% for item in seq recursive %}[{{ loop.depth }}:{{ item.a }}" + "{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]" + ) def test_looploop(self, test_env_async): - tmpl = test_env_async.from_string( - """{% for row in table %} + tmpl = test_env_async.from_string( + """{% for row in table %} {%- set rowloop = loop -%} {% for cell in row -%} [{{ rowloop.index }}|{{ loop.index }}] {%- endfor %} - {%- endfor %}""" - ) - assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]" + {%- endfor %}""" + ) + assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]" def test_reversed_bug(self, test_env_async): - tmpl = test_env_async.from_string( - "{% for i in items %}{{ i }}" - "{% if not loop.last %}" - ",{% endif %}{% endfor %}" - ) - assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3" + tmpl = test_env_async.from_string( + "{% for i in items %}{{ i }}" + "{% if not loop.last %}" + ",{% endif %}{% endfor %}" + ) + assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3" def test_loop_errors(self, test_env_async): - tmpl = test_env_async.from_string( - """{% for item in [1] if loop.index - == 0 %}...{% endfor %}""" - ) + tmpl = test_env_async.from_string( + """{% for item in [1] if loop.index + == 0 %}...{% endfor %}""" + ) pytest.raises(UndefinedError, tmpl.render) - tmpl = test_env_async.from_string( - """{% for item in [] %}...{% else - %}{{ loop }}{% endfor %}""" - ) - assert tmpl.render() == "" + tmpl = test_env_async.from_string( + """{% for item in [] %}...{% else + %}{{ loop }}{% endfor %}""" + ) + assert tmpl.render() == "" def test_loop_filter(self, test_env_async): - tmpl = test_env_async.from_string( - "{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}" - ) - assert tmpl.render() == "[0][2][4][6][8]" - tmpl = test_env_async.from_string( - """ + tmpl = test_env_async.from_string( + "{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}" + ) + assert tmpl.render() == "[0][2][4][6][8]" + tmpl = test_env_async.from_string( + """ {%- for item in range(10) if item is even %}[{{ - loop.index }}:{{ item }}]{% endfor %}""" - ) - assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]" + loop.index }}:{{ item }}]{% endfor %}""" + ) + assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]" def test_scoped_special_var(self, test_env_async): t = test_env_async.from_string( - "{% for s in seq %}[{{ loop.first }}{% for c in s %}" - "|{{ loop.first }}{% endfor %}]{% endfor %}" - ) - assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]" + "{% for s in seq %}[{{ loop.first }}{% for c in s %}" + "|{{ loop.first }}{% endfor %}]{% endfor %}" + ) + assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]" def test_scoped_loop_var(self, test_env_async): - t = test_env_async.from_string( - "{% for x in seq %}{{ loop.first }}" - "{% for y in seq %}{% endfor %}{% endfor %}" - ) - assert t.render(seq="ab") == "TrueFalse" - t = test_env_async.from_string( - "{% for x in seq %}{% for y in seq %}" - "{{ loop.first }}{% endfor %}{% endfor %}" - ) - assert t.render(seq="ab") == "TrueFalseTrueFalse" + t = test_env_async.from_string( + "{% for x in seq %}{{ loop.first }}" + "{% for y in seq %}{% endfor %}{% endfor %}" + ) + assert t.render(seq="ab") == "TrueFalse" + t = test_env_async.from_string( + "{% for x in seq %}{% for y in seq %}" + "{{ loop.first }}{% endfor %}{% endfor %}" + ) + assert t.render(seq="ab") == "TrueFalseTrueFalse" def test_recursive_empty_loop_iter(self, test_env_async): - t = test_env_async.from_string( - """ + t = test_env_async.from_string( + """ {%- for item in foo recursive -%}{%- endfor -%} - """ - ) - assert t.render(dict(foo=[])) == "" + """ + ) + assert t.render(dict(foo=[])) == "" def test_call_in_loop(self, test_env_async): - t = test_env_async.from_string( - """ + t = test_env_async.from_string( + """ {%- macro do_something() -%} [{{ caller() }}] {%- endmacro %} @@ -536,29 +536,29 @@ class TestAsyncForLoop: {{ i }} {%- endcall %} {%- endfor -%} - """ - ) - assert t.render() == "[1][2][3]" + """ + ) + assert t.render() == "[1][2][3]" def test_scoping_bug(self, test_env_async): - t = test_env_async.from_string( - """ + t = test_env_async.from_string( + """ {%- for item in foo %}...{{ item }}...{% endfor %} {%- macro item(a) %}...{{ a }}...{% endmacro %} {{- item(2) -}} - """ - ) - assert t.render(foo=(1,)) == "...1......2..." + """ + ) + assert t.render(foo=(1,)) == "...1......2..." def test_unpacking(self, test_env_async): - tmpl = test_env_async.from_string( - "{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}" - ) - assert tmpl.render() == "1|2|3" + tmpl = test_env_async.from_string( + "{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}" + ) + assert tmpl.render() == "1|2|3" def test_recursive_loop_filter(self, test_env_async): - t = test_env_async.from_string( - """ + t = test_env_async.from_string( + """ <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> {%- for page in [site.root] if page.url != this recursive %} @@ -566,95 +566,95 @@ class TestAsyncForLoop: {{- loop(page.children) }} {%- endfor %} </urlset> - """ - ) - sm = t.render( - this="/foo", - site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}}, - ) + """ + ) + sm = t.render( + this="/foo", + site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}}, + ) lines = [x.strip() for x in sm.splitlines() if x.strip()] assert lines == [ '<?xml version="1.0" encoding="UTF-8"?>', '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">', - "<url><loc>/</loc></url>", - "<url><loc>/bar</loc></url>", - "</urlset>", + "<url><loc>/</loc></url>", + "<url><loc>/bar</loc></url>", + "</urlset>", ] def test_nonrecursive_loop_filter(self, test_env_async): - t = test_env_async.from_string( - """ + t = test_env_async.from_string( + """ <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> {%- for page in items if page.url != this %} <url><loc>{{ page.url }}</loc></url> {%- endfor %} </urlset> - """ - ) - sm = t.render( - this="/foo", items=[{"url": "/"}, {"url": "/foo"}, {"url": "/bar"}] - ) + """ + ) + sm = t.render( + this="/foo", items=[{"url": "/"}, {"url": "/foo"}, {"url": "/bar"}] + ) lines = [x.strip() for x in sm.splitlines() if x.strip()] assert lines == [ '<?xml version="1.0" encoding="UTF-8"?>', '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">', - "<url><loc>/</loc></url>", - "<url><loc>/bar</loc></url>", - "</urlset>", + "<url><loc>/</loc></url>", + "<url><loc>/bar</loc></url>", + "</urlset>", ] def test_bare_async(self, test_env_async): t = test_env_async.from_string('{% extends "header" %}') - assert t.render(foo=42) == "[42|23]" - - def test_awaitable_property_slicing(self, test_env_async): - t = test_env_async.from_string("{% for x in a.b[:1] %}{{ x }}{% endfor %}") - assert t.render(a=dict(b=[1, 2, 3])) == "1" - - -def test_namespace_awaitable(test_env_async): - async def _test(): - t = test_env_async.from_string( - '{% set ns = namespace(foo="Bar") %}{{ ns.foo }}' - ) - actual = await t.render_async() - assert actual == "Bar" - - run(_test()) - - -def test_chainable_undefined_aiter(): - async def _test(): - t = Template( - "{% for x in a['b']['c'] %}{{ x }}{% endfor %}", - enable_async=True, - undefined=ChainableUndefined, - ) - rv = await t.render_async(a={}) - assert rv == "" - - run(_test()) - - -@pytest.fixture -def async_native_env(): - return NativeEnvironment(enable_async=True) - - -def test_native_async(async_native_env): - async def _test(): - t = async_native_env.from_string("{{ x }}") - rv = await t.render_async(x=23) - assert rv == 23 - - run(_test()) - - -def test_native_list_async(async_native_env): - async def _test(): - t = async_native_env.from_string("{{ x }}") - rv = await t.render_async(x=list(range(3))) - assert rv == [0, 1, 2] - - run(_test()) + assert t.render(foo=42) == "[42|23]" + + def test_awaitable_property_slicing(self, test_env_async): + t = test_env_async.from_string("{% for x in a.b[:1] %}{{ x }}{% endfor %}") + assert t.render(a=dict(b=[1, 2, 3])) == "1" + + +def test_namespace_awaitable(test_env_async): + async def _test(): + t = test_env_async.from_string( + '{% set ns = namespace(foo="Bar") %}{{ ns.foo }}' + ) + actual = await t.render_async() + assert actual == "Bar" + + run(_test()) + + +def test_chainable_undefined_aiter(): + async def _test(): + t = Template( + "{% for x in a['b']['c'] %}{{ x }}{% endfor %}", + enable_async=True, + undefined=ChainableUndefined, + ) + rv = await t.render_async(a={}) + assert rv == "" + + run(_test()) + + +@pytest.fixture +def async_native_env(): + return NativeEnvironment(enable_async=True) + + +def test_native_async(async_native_env): + async def _test(): + t = async_native_env.from_string("{{ x }}") + rv = await t.render_async(x=23) + assert rv == 23 + + run(_test()) + + +def test_native_list_async(async_native_env): + async def _test(): + t = async_native_env.from_string("{{ x }}") + rv = await t.render_async(x=list(range(3))) + assert rv == [0, 1, 2] + + run(_test()) diff --git a/contrib/python/Jinja2/py3/tests/test_async_filters.py b/contrib/python/Jinja2/py3/tests/test_async_filters.py index 5d4f332e5d..840e516c81 100644 --- a/contrib/python/Jinja2/py3/tests/test_async_filters.py +++ b/contrib/python/Jinja2/py3/tests/test_async_filters.py @@ -1,253 +1,253 @@ -from collections import namedtuple - -import pytest -from markupsafe import Markup - -from jinja2 import Environment -from jinja2.async_utils import auto_aiter - - -async def make_aiter(iter): - for item in iter: - yield item - - -def mark_dualiter(parameter, factory): - def decorator(f): - return pytest.mark.parametrize( - parameter, [lambda: factory(), lambda: make_aiter(factory())] - )(f) - - return decorator - - -@pytest.fixture -def env_async(): - return Environment(enable_async=True) - - -@mark_dualiter("foo", lambda: range(10)) -def test_first(env_async, foo): - tmpl = env_async.from_string("{{ foo()|first }}") - out = tmpl.render(foo=foo) - assert out == "0" - - -@mark_dualiter( - "items", - lambda: [ - {"foo": 1, "bar": 2}, - {"foo": 2, "bar": 3}, - {"foo": 1, "bar": 1}, - {"foo": 3, "bar": 4}, - ], -) -def test_groupby(env_async, items): - tmpl = env_async.from_string( - """ - {%- for grouper, list in items()|groupby('foo') -%} - {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}| - {%- endfor %}""" - ) - assert tmpl.render(items=items).split("|") == [ - "1: 1, 2: 1, 1", - "2: 2, 3", - "3: 3, 4", - "", - ] - - -@mark_dualiter("items", lambda: [("a", 1), ("a", 2), ("b", 1)]) -def test_groupby_tuple_index(env_async, items): - tmpl = env_async.from_string( - """ - {%- for grouper, list in items()|groupby(0) -%} - {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}| - {%- endfor %}""" - ) - assert tmpl.render(items=items) == "a:1:2|b:1|" - - -def make_articles(): - Date = namedtuple("Date", "day,month,year") - Article = namedtuple("Article", "title,date") - return [ - Article("aha", Date(1, 1, 1970)), - Article("interesting", Date(2, 1, 1970)), - Article("really?", Date(3, 1, 1970)), - Article("totally not", Date(1, 1, 1971)), - ] - - -@mark_dualiter("articles", make_articles) -def test_groupby_multidot(env_async, articles): - tmpl = env_async.from_string( - """ - {%- for year, list in articles()|groupby('date.year') -%} - {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}| - {%- endfor %}""" - ) - assert tmpl.render(articles=articles).split("|") == [ - "1970[aha][interesting][really?]", - "1971[totally not]", - "", - ] - - -@mark_dualiter("int_items", lambda: [1, 2, 3]) -def test_join_env_int(env_async, int_items): - tmpl = env_async.from_string('{{ items()|join("|") }}') - out = tmpl.render(items=int_items) - assert out == "1|2|3" - - -@mark_dualiter("string_items", lambda: ["<foo>", Markup("<span>foo</span>")]) -def test_join_string_list(string_items): - env2 = Environment(autoescape=True, enable_async=True) - tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}') - assert tmpl.render(items=string_items) == "<foo><span>foo</span>" - - -def make_users(): - User = namedtuple("User", "username") - return map(User, ["foo", "bar"]) - - -@mark_dualiter("users", make_users) -def test_join_attribute(env_async, users): - tmpl = env_async.from_string("""{{ users()|join(', ', 'username') }}""") - assert tmpl.render(users=users) == "foo, bar" - - -@mark_dualiter("items", lambda: [1, 2, 3, 4, 5]) -def test_simple_reject(env_async, items): - tmpl = env_async.from_string('{{ items()|reject("odd")|join("|") }}') - assert tmpl.render(items=items) == "2|4" - - -@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5]) -def test_bool_reject(env_async, items): - tmpl = env_async.from_string('{{ items()|reject|join("|") }}') - assert tmpl.render(items=items) == "None|False|0" - - -@mark_dualiter("items", lambda: [1, 2, 3, 4, 5]) -def test_simple_select(env_async, items): - tmpl = env_async.from_string('{{ items()|select("odd")|join("|") }}') - assert tmpl.render(items=items) == "1|3|5" - - -@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5]) -def test_bool_select(env_async, items): - tmpl = env_async.from_string('{{ items()|select|join("|") }}') - assert tmpl.render(items=items) == "1|2|3|4|5" - - -def make_users(): # type: ignore - User = namedtuple("User", "name,is_active") - return [ - User("john", True), - User("jane", True), - User("mike", False), - ] - - -@mark_dualiter("users", make_users) -def test_simple_select_attr(env_async, users): - tmpl = env_async.from_string( - '{{ users()|selectattr("is_active")|map(attribute="name")|join("|") }}' - ) - assert tmpl.render(users=users) == "john|jane" - - -@mark_dualiter("items", lambda: list("123")) -def test_simple_map(env_async, items): - tmpl = env_async.from_string('{{ items()|map("int")|sum }}') - assert tmpl.render(items=items) == "6" - - -def test_map_sum(env_async): # async map + async filter - tmpl = env_async.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}') - assert tmpl.render() == "[3, 3, 15]" - - -@mark_dualiter("users", make_users) -def test_attribute_map(env_async, users): - tmpl = env_async.from_string('{{ users()|map(attribute="name")|join("|") }}') - assert tmpl.render(users=users) == "john|jane|mike" - - -def test_empty_map(env_async): - tmpl = env_async.from_string('{{ none|map("upper")|list }}') - assert tmpl.render() == "[]" - - -@mark_dualiter("items", lambda: [1, 2, 3, 4, 5, 6]) -def test_sum(env_async, items): - tmpl = env_async.from_string("""{{ items()|sum }}""") - assert tmpl.render(items=items) == "21" - - -@mark_dualiter("items", lambda: [{"value": 23}, {"value": 1}, {"value": 18}]) -def test_sum_attributes(env_async, items): - tmpl = env_async.from_string("""{{ items()|sum('value') }}""") - assert tmpl.render(items=items) - - -def test_sum_attributes_nested(env_async): - tmpl = env_async.from_string("""{{ values|sum('real.value') }}""") - assert ( - tmpl.render( - values=[ - {"real": {"value": 23}}, - {"real": {"value": 1}}, - {"real": {"value": 18}}, - ] - ) - == "42" - ) - - -def test_sum_attributes_tuple(env_async): - tmpl = env_async.from_string("""{{ values.items()|sum('1') }}""") - assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42" - - -@mark_dualiter("items", lambda: range(10)) -def test_slice(env_async, items): - tmpl = env_async.from_string( - "{{ items()|slice(3)|list }}|{{ items()|slice(3, 'X')|list }}" - ) - out = tmpl.render(items=items) - assert out == ( - "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|" - "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]" - ) - - -def test_custom_async_filter(env_async): - async def customfilter(val): - return str(val) - - env_async.filters["customfilter"] = customfilter - tmpl = env_async.from_string("{{ 'static'|customfilter }} {{ arg|customfilter }}") - out = tmpl.render(arg="dynamic") - assert out == "static dynamic" - - -@mark_dualiter("items", lambda: range(10)) -def test_custom_async_iteratable_filter(env_async, items): - async def customfilter(iterable): - items = [] - async for item in auto_aiter(iterable): - items.append(str(item)) - if len(items) == 3: - break - return ",".join(items) - - env_async.filters["customfilter"] = customfilter - tmpl = env_async.from_string( - "{{ items()|customfilter }} .. {{ [3, 4, 5, 6]|customfilter }}" - ) - out = tmpl.render(items=items) - assert out == "0,1,2 .. 3,4,5" +from collections import namedtuple + +import pytest +from markupsafe import Markup + +from jinja2 import Environment +from jinja2.async_utils import auto_aiter + + +async def make_aiter(iter): + for item in iter: + yield item + + +def mark_dualiter(parameter, factory): + def decorator(f): + return pytest.mark.parametrize( + parameter, [lambda: factory(), lambda: make_aiter(factory())] + )(f) + + return decorator + + +@pytest.fixture +def env_async(): + return Environment(enable_async=True) + + +@mark_dualiter("foo", lambda: range(10)) +def test_first(env_async, foo): + tmpl = env_async.from_string("{{ foo()|first }}") + out = tmpl.render(foo=foo) + assert out == "0" + + +@mark_dualiter( + "items", + lambda: [ + {"foo": 1, "bar": 2}, + {"foo": 2, "bar": 3}, + {"foo": 1, "bar": 1}, + {"foo": 3, "bar": 4}, + ], +) +def test_groupby(env_async, items): + tmpl = env_async.from_string( + """ + {%- for grouper, list in items()|groupby('foo') -%} + {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}| + {%- endfor %}""" + ) + assert tmpl.render(items=items).split("|") == [ + "1: 1, 2: 1, 1", + "2: 2, 3", + "3: 3, 4", + "", + ] + + +@mark_dualiter("items", lambda: [("a", 1), ("a", 2), ("b", 1)]) +def test_groupby_tuple_index(env_async, items): + tmpl = env_async.from_string( + """ + {%- for grouper, list in items()|groupby(0) -%} + {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}| + {%- endfor %}""" + ) + assert tmpl.render(items=items) == "a:1:2|b:1|" + + +def make_articles(): + Date = namedtuple("Date", "day,month,year") + Article = namedtuple("Article", "title,date") + return [ + Article("aha", Date(1, 1, 1970)), + Article("interesting", Date(2, 1, 1970)), + Article("really?", Date(3, 1, 1970)), + Article("totally not", Date(1, 1, 1971)), + ] + + +@mark_dualiter("articles", make_articles) +def test_groupby_multidot(env_async, articles): + tmpl = env_async.from_string( + """ + {%- for year, list in articles()|groupby('date.year') -%} + {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}| + {%- endfor %}""" + ) + assert tmpl.render(articles=articles).split("|") == [ + "1970[aha][interesting][really?]", + "1971[totally not]", + "", + ] + + +@mark_dualiter("int_items", lambda: [1, 2, 3]) +def test_join_env_int(env_async, int_items): + tmpl = env_async.from_string('{{ items()|join("|") }}') + out = tmpl.render(items=int_items) + assert out == "1|2|3" + + +@mark_dualiter("string_items", lambda: ["<foo>", Markup("<span>foo</span>")]) +def test_join_string_list(string_items): + env2 = Environment(autoescape=True, enable_async=True) + tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}') + assert tmpl.render(items=string_items) == "<foo><span>foo</span>" + + +def make_users(): + User = namedtuple("User", "username") + return map(User, ["foo", "bar"]) + + +@mark_dualiter("users", make_users) +def test_join_attribute(env_async, users): + tmpl = env_async.from_string("""{{ users()|join(', ', 'username') }}""") + assert tmpl.render(users=users) == "foo, bar" + + +@mark_dualiter("items", lambda: [1, 2, 3, 4, 5]) +def test_simple_reject(env_async, items): + tmpl = env_async.from_string('{{ items()|reject("odd")|join("|") }}') + assert tmpl.render(items=items) == "2|4" + + +@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5]) +def test_bool_reject(env_async, items): + tmpl = env_async.from_string('{{ items()|reject|join("|") }}') + assert tmpl.render(items=items) == "None|False|0" + + +@mark_dualiter("items", lambda: [1, 2, 3, 4, 5]) +def test_simple_select(env_async, items): + tmpl = env_async.from_string('{{ items()|select("odd")|join("|") }}') + assert tmpl.render(items=items) == "1|3|5" + + +@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5]) +def test_bool_select(env_async, items): + tmpl = env_async.from_string('{{ items()|select|join("|") }}') + assert tmpl.render(items=items) == "1|2|3|4|5" + + +def make_users(): # type: ignore + User = namedtuple("User", "name,is_active") + return [ + User("john", True), + User("jane", True), + User("mike", False), + ] + + +@mark_dualiter("users", make_users) +def test_simple_select_attr(env_async, users): + tmpl = env_async.from_string( + '{{ users()|selectattr("is_active")|map(attribute="name")|join("|") }}' + ) + assert tmpl.render(users=users) == "john|jane" + + +@mark_dualiter("items", lambda: list("123")) +def test_simple_map(env_async, items): + tmpl = env_async.from_string('{{ items()|map("int")|sum }}') + assert tmpl.render(items=items) == "6" + + +def test_map_sum(env_async): # async map + async filter + tmpl = env_async.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}') + assert tmpl.render() == "[3, 3, 15]" + + +@mark_dualiter("users", make_users) +def test_attribute_map(env_async, users): + tmpl = env_async.from_string('{{ users()|map(attribute="name")|join("|") }}') + assert tmpl.render(users=users) == "john|jane|mike" + + +def test_empty_map(env_async): + tmpl = env_async.from_string('{{ none|map("upper")|list }}') + assert tmpl.render() == "[]" + + +@mark_dualiter("items", lambda: [1, 2, 3, 4, 5, 6]) +def test_sum(env_async, items): + tmpl = env_async.from_string("""{{ items()|sum }}""") + assert tmpl.render(items=items) == "21" + + +@mark_dualiter("items", lambda: [{"value": 23}, {"value": 1}, {"value": 18}]) +def test_sum_attributes(env_async, items): + tmpl = env_async.from_string("""{{ items()|sum('value') }}""") + assert tmpl.render(items=items) + + +def test_sum_attributes_nested(env_async): + tmpl = env_async.from_string("""{{ values|sum('real.value') }}""") + assert ( + tmpl.render( + values=[ + {"real": {"value": 23}}, + {"real": {"value": 1}}, + {"real": {"value": 18}}, + ] + ) + == "42" + ) + + +def test_sum_attributes_tuple(env_async): + tmpl = env_async.from_string("""{{ values.items()|sum('1') }}""") + assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42" + + +@mark_dualiter("items", lambda: range(10)) +def test_slice(env_async, items): + tmpl = env_async.from_string( + "{{ items()|slice(3)|list }}|{{ items()|slice(3, 'X')|list }}" + ) + out = tmpl.render(items=items) + assert out == ( + "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|" + "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]" + ) + + +def test_custom_async_filter(env_async): + async def customfilter(val): + return str(val) + + env_async.filters["customfilter"] = customfilter + tmpl = env_async.from_string("{{ 'static'|customfilter }} {{ arg|customfilter }}") + out = tmpl.render(arg="dynamic") + assert out == "static dynamic" + + +@mark_dualiter("items", lambda: range(10)) +def test_custom_async_iteratable_filter(env_async, items): + async def customfilter(iterable): + items = [] + async for item in auto_aiter(iterable): + items.append(str(item)) + if len(items) == 3: + break + return ",".join(items) + + env_async.filters["customfilter"] = customfilter + tmpl = env_async.from_string( + "{{ items()|customfilter }} .. {{ [3, 4, 5, 6]|customfilter }}" + ) + out = tmpl.render(items=items) + assert out == "0,1,2 .. 3,4,5" diff --git a/contrib/python/Jinja2/py3/tests/test_bytecode_cache.py b/contrib/python/Jinja2/py3/tests/test_bytecode_cache.py index 5b9eb0ff69..6c1009493e 100644 --- a/contrib/python/Jinja2/py3/tests/test_bytecode_cache.py +++ b/contrib/python/Jinja2/py3/tests/test_bytecode_cache.py @@ -1,26 +1,26 @@ import pytest from jinja2 import Environment -from jinja2.bccache import Bucket -from jinja2.bccache import FileSystemBytecodeCache -from jinja2.bccache import MemcachedBytecodeCache +from jinja2.bccache import Bucket +from jinja2.bccache import FileSystemBytecodeCache +from jinja2.bccache import MemcachedBytecodeCache from jinja2.exceptions import TemplateNotFound @pytest.fixture -def env(package_loader, tmp_path): - bytecode_cache = FileSystemBytecodeCache(str(tmp_path)) - return Environment(loader=package_loader, bytecode_cache=bytecode_cache) +def env(package_loader, tmp_path): + bytecode_cache = FileSystemBytecodeCache(str(tmp_path)) + return Environment(loader=package_loader, bytecode_cache=bytecode_cache) -class TestByteCodeCache: +class TestByteCodeCache: def test_simple(self, env): - tmpl = env.get_template("test.html") - assert tmpl.render().strip() == "BAR" - pytest.raises(TemplateNotFound, env.get_template, "missing.html") + tmpl = env.get_template("test.html") + assert tmpl.render().strip() == "BAR" + pytest.raises(TemplateNotFound, env.get_template, "missing.html") -class MockMemcached: +class MockMemcached: class Error(Exception): pass @@ -43,27 +43,27 @@ class MockMemcached: raise self.Error() -class TestMemcachedBytecodeCache: +class TestMemcachedBytecodeCache: def test_dump_load(self): memcached = MockMemcached() m = MemcachedBytecodeCache(memcached) - b = Bucket(None, "key", "") - b.code = "code" + b = Bucket(None, "key", "") + b.code = "code" m.dump_bytecode(b) - assert memcached.key == "jinja2/bytecode/key" + assert memcached.key == "jinja2/bytecode/key" - b = Bucket(None, "key", "") + b = Bucket(None, "key", "") m.load_bytecode(b) - assert b.code == "code" + assert b.code == "code" def test_exception(self): memcached = MockMemcached() memcached.get = memcached.get_side_effect memcached.set = memcached.set_side_effect m = MemcachedBytecodeCache(memcached) - b = Bucket(None, "key", "") - b.code = "code" + b = Bucket(None, "key", "") + b.code = "code" m.dump_bytecode(b) m.load_bytecode(b) diff --git a/contrib/python/Jinja2/py3/tests/test_compile.py b/contrib/python/Jinja2/py3/tests/test_compile.py index 42a773f21c..2f84d00d72 100644 --- a/contrib/python/Jinja2/py3/tests/test_compile.py +++ b/contrib/python/Jinja2/py3/tests/test_compile.py @@ -1,28 +1,28 @@ -import os -import re - -from jinja2.environment import Environment -from jinja2.loaders import DictLoader - - -def test_filters_deterministic(tmp_path): - src = "".join(f"{{{{ {i}|filter{i} }}}}" for i in range(10)) - env = Environment(loader=DictLoader({"foo": src})) - env.filters.update(dict.fromkeys((f"filter{i}" for i in range(10)), lambda: None)) - env.compile_templates(tmp_path, zip=None) - name = os.listdir(tmp_path)[0] - content = (tmp_path / name).read_text("utf8") - expect = [f"filters['filter{i}']" for i in range(10)] - found = re.findall(r"filters\['filter\d']", content) - assert found == expect - - -def test_import_as_with_context_deterministic(tmp_path): - src = "\n".join(f'{{% import "bar" as bar{i} with context %}}' for i in range(10)) - env = Environment(loader=DictLoader({"foo": src})) - env.compile_templates(tmp_path, zip=None) - name = os.listdir(tmp_path)[0] - content = (tmp_path / name).read_text("utf8") - expect = [f"'bar{i}': " for i in range(10)] - found = re.findall(r"'bar\d': ", content)[:10] - assert found == expect +import os +import re + +from jinja2.environment import Environment +from jinja2.loaders import DictLoader + + +def test_filters_deterministic(tmp_path): + src = "".join(f"{{{{ {i}|filter{i} }}}}" for i in range(10)) + env = Environment(loader=DictLoader({"foo": src})) + env.filters.update(dict.fromkeys((f"filter{i}" for i in range(10)), lambda: None)) + env.compile_templates(tmp_path, zip=None) + name = os.listdir(tmp_path)[0] + content = (tmp_path / name).read_text("utf8") + expect = [f"filters['filter{i}']" for i in range(10)] + found = re.findall(r"filters\['filter\d']", content) + assert found == expect + + +def test_import_as_with_context_deterministic(tmp_path): + src = "\n".join(f'{{% import "bar" as bar{i} with context %}}' for i in range(10)) + env = Environment(loader=DictLoader({"foo": src})) + env.compile_templates(tmp_path, zip=None) + name = os.listdir(tmp_path)[0] + content = (tmp_path / name).read_text("utf8") + expect = [f"'bar{i}': " for i in range(10)] + found = re.findall(r"'bar\d': ", content)[:10] + assert found == expect diff --git a/contrib/python/Jinja2/py3/tests/test_core_tags.py b/contrib/python/Jinja2/py3/tests/test_core_tags.py index 4bb95e0240..fac103e2d8 100644 --- a/contrib/python/Jinja2/py3/tests/test_core_tags.py +++ b/contrib/python/Jinja2/py3/tests/test_core_tags.py @@ -1,10 +1,10 @@ -import pytest +import pytest -from jinja2 import DictLoader -from jinja2 import Environment -from jinja2 import TemplateRuntimeError -from jinja2 import TemplateSyntaxError -from jinja2 import UndefinedError +from jinja2 import DictLoader +from jinja2 import Environment +from jinja2 import TemplateRuntimeError +from jinja2 import TemplateSyntaxError +from jinja2 import UndefinedError @pytest.fixture @@ -12,250 +12,250 @@ def env_trim(): return Environment(trim_blocks=True) -class TestForLoop: +class TestForLoop: def test_simple(self, env): - tmpl = env.from_string("{% for item in seq %}{{ item }}{% endfor %}") - assert tmpl.render(seq=list(range(10))) == "0123456789" + tmpl = env.from_string("{% for item in seq %}{{ item }}{% endfor %}") + assert tmpl.render(seq=list(range(10))) == "0123456789" def test_else(self, env): - tmpl = env.from_string("{% for item in seq %}XXX{% else %}...{% endfor %}") - assert tmpl.render() == "..." + tmpl = env.from_string("{% for item in seq %}XXX{% else %}...{% endfor %}") + assert tmpl.render() == "..." def test_else_scoping_item(self, env): - tmpl = env.from_string("{% for item in [] %}{% else %}{{ item }}{% endfor %}") - assert tmpl.render(item=42) == "42" + tmpl = env.from_string("{% for item in [] %}{% else %}{{ item }}{% endfor %}") + assert tmpl.render(item=42) == "42" def test_empty_blocks(self, env): - tmpl = env.from_string("<{% for item in seq %}{% else %}{% endfor %}>") - assert tmpl.render() == "<>" + tmpl = env.from_string("<{% for item in seq %}{% else %}{% endfor %}>") + assert tmpl.render() == "<>" def test_context_vars(self, env): slist = [42, 24] for seq in [slist, iter(slist), reversed(slist), (_ for _ in slist)]: - tmpl = env.from_string( - """{% for item in seq -%} + tmpl = env.from_string( + """{% for item in seq -%} {{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{ loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{ - loop.length }}###{% endfor %}""" - ) - one, two, _ = tmpl.render(seq=seq).split("###") - ( - one_index, - one_index0, - one_revindex, - one_revindex0, - one_first, - one_last, - one_length, - ) = one.split("|") - ( - two_index, - two_index0, - two_revindex, - two_revindex0, - two_first, - two_last, - two_length, - ) = two.split("|") + loop.length }}###{% endfor %}""" + ) + one, two, _ = tmpl.render(seq=seq).split("###") + ( + one_index, + one_index0, + one_revindex, + one_revindex0, + one_first, + one_last, + one_length, + ) = one.split("|") + ( + two_index, + two_index0, + two_revindex, + two_revindex0, + two_first, + two_last, + two_length, + ) = two.split("|") assert int(one_index) == 1 and int(two_index) == 2 assert int(one_index0) == 0 and int(two_index0) == 1 assert int(one_revindex) == 2 and int(two_revindex) == 1 assert int(one_revindex0) == 1 and int(two_revindex0) == 0 - assert one_first == "True" and two_first == "False" - assert one_last == "False" and two_last == "True" - assert one_length == two_length == "2" + assert one_first == "True" and two_first == "False" + assert one_last == "False" and two_last == "True" + assert one_length == two_length == "2" def test_cycling(self, env): - tmpl = env.from_string( - """{% for item in seq %}{{ + tmpl = env.from_string( + """{% for item in seq %}{{ loop.cycle('<1>', '<2>') }}{% endfor %}{% - for item in seq %}{{ loop.cycle(*through) }}{% endfor %}""" - ) - output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>")) - assert output == "<1><2>" * 4 + for item in seq %}{{ loop.cycle(*through) }}{% endfor %}""" + ) + output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>")) + assert output == "<1><2>" * 4 def test_lookaround(self, env): - tmpl = env.from_string( - """{% for item in seq -%} + tmpl = env.from_string( + """{% for item in seq -%} {{ loop.previtem|default('x') }}-{{ item }}-{{ loop.nextitem|default('x') }}| - {%- endfor %}""" - ) + {%- endfor %}""" + ) output = tmpl.render(seq=list(range(4))) - assert output == "x-0-1|0-1-2|1-2-3|2-3-x|" + assert output == "x-0-1|0-1-2|1-2-3|2-3-x|" def test_changed(self, env): - tmpl = env.from_string( - """{% for item in seq -%} + tmpl = env.from_string( + """{% for item in seq -%} {{ loop.changed(item) }}, - {%- endfor %}""" - ) + {%- endfor %}""" + ) output = tmpl.render(seq=[None, None, 1, 2, 2, 3, 4, 4, 4]) - assert output == "True,False,True,True,False,True,True,False,False," + assert output == "True,False,True,True,False,True,True,False,False," def test_scope(self, env): - tmpl = env.from_string("{% for item in seq %}{% endfor %}{{ item }}") + tmpl = env.from_string("{% for item in seq %}{% endfor %}{{ item }}") output = tmpl.render(seq=list(range(10))) assert not output def test_varlen(self, env): - tmpl = env.from_string("{% for item in iter %}{{ item }}{% endfor %}") - output = tmpl.render(iter=range(5)) - assert output == "01234" + tmpl = env.from_string("{% for item in iter %}{{ item }}{% endfor %}") + output = tmpl.render(iter=range(5)) + assert output == "01234" def test_noniter(self, env): - tmpl = env.from_string("{% for item in none %}...{% endfor %}") + tmpl = env.from_string("{% for item in none %}...{% endfor %}") pytest.raises(TypeError, tmpl.render) def test_recursive(self, env): - tmpl = env.from_string( - """{% for item in seq recursive -%} + tmpl = env.from_string( + """{% for item in seq recursive -%} [{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] - {%- endfor %}""" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[1<[1][2]>][2<[1][2]>][3<[a]>]" - ) + {%- endfor %}""" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[1<[1][2]>][2<[1][2]>][3<[a]>]" + ) def test_recursive_lookaround(self, env): - tmpl = env.from_string( - """{% for item in seq recursive -%} + tmpl = env.from_string( + """{% for item in seq recursive -%} [{{ loop.previtem.a if loop.previtem is defined else 'x' }}.{{ item.a }}.{{ loop.nextitem.a if loop.nextitem is defined else 'x' }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] - {%- endfor %}""" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]" - ) + {%- endfor %}""" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]" + ) def test_recursive_depth0(self, env): - tmpl = env.from_string( - """{% for item in seq recursive -%} - [{{ loop.depth0 }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] - {%- endfor %}""" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]" - ) + tmpl = env.from_string( + """{% for item in seq recursive -%} + [{{ loop.depth0 }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] + {%- endfor %}""" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]" + ) def test_recursive_depth(self, env): - tmpl = env.from_string( - """{% for item in seq recursive -%} - [{{ loop.depth }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] - {%- endfor %}""" - ) - assert ( - tmpl.render( - seq=[ - dict(a=1, b=[dict(a=1), dict(a=2)]), - dict(a=2, b=[dict(a=1), dict(a=2)]), - dict(a=3, b=[dict(a="a")]), - ] - ) - == "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]" - ) + tmpl = env.from_string( + """{% for item in seq recursive -%} + [{{ loop.depth }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}] + {%- endfor %}""" + ) + assert ( + tmpl.render( + seq=[ + dict(a=1, b=[dict(a=1), dict(a=2)]), + dict(a=2, b=[dict(a=1), dict(a=2)]), + dict(a=3, b=[dict(a="a")]), + ] + ) + == "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]" + ) def test_looploop(self, env): - tmpl = env.from_string( - """{% for row in table %} + tmpl = env.from_string( + """{% for row in table %} {%- set rowloop = loop -%} {% for cell in row -%} [{{ rowloop.index }}|{{ loop.index }}] {%- endfor %} - {%- endfor %}""" - ) - assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]" + {%- endfor %}""" + ) + assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]" def test_reversed_bug(self, env): - tmpl = env.from_string( - "{% for i in items %}{{ i }}" - "{% if not loop.last %}" - ",{% endif %}{% endfor %}" - ) - assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3" + tmpl = env.from_string( + "{% for i in items %}{{ i }}" + "{% if not loop.last %}" + ",{% endif %}{% endfor %}" + ) + assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3" def test_loop_errors(self, env): - tmpl = env.from_string( - """{% for item in [1] if loop.index - == 0 %}...{% endfor %}""" - ) + tmpl = env.from_string( + """{% for item in [1] if loop.index + == 0 %}...{% endfor %}""" + ) pytest.raises(UndefinedError, tmpl.render) - tmpl = env.from_string( - """{% for item in [] %}...{% else - %}{{ loop }}{% endfor %}""" - ) - assert tmpl.render() == "" + tmpl = env.from_string( + """{% for item in [] %}...{% else + %}{{ loop }}{% endfor %}""" + ) + assert tmpl.render() == "" def test_loop_filter(self, env): - tmpl = env.from_string( - "{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}" - ) - assert tmpl.render() == "[0][2][4][6][8]" - tmpl = env.from_string( - """ + tmpl = env.from_string( + "{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}" + ) + assert tmpl.render() == "[0][2][4][6][8]" + tmpl = env.from_string( + """ {%- for item in range(10) if item is even %}[{{ - loop.index }}:{{ item }}]{% endfor %}""" - ) - assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]" + loop.index }}:{{ item }}]{% endfor %}""" + ) + assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]" def test_loop_unassignable(self, env): - pytest.raises( - TemplateSyntaxError, env.from_string, "{% for loop in seq %}...{% endfor %}" - ) + pytest.raises( + TemplateSyntaxError, env.from_string, "{% for loop in seq %}...{% endfor %}" + ) def test_scoped_special_var(self, env): t = env.from_string( - "{% for s in seq %}[{{ loop.first }}{% for c in s %}" - "|{{ loop.first }}{% endfor %}]{% endfor %}" - ) - assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]" + "{% for s in seq %}[{{ loop.first }}{% for c in s %}" + "|{{ loop.first }}{% endfor %}]{% endfor %}" + ) + assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]" def test_scoped_loop_var(self, env): - t = env.from_string( - "{% for x in seq %}{{ loop.first }}" - "{% for y in seq %}{% endfor %}{% endfor %}" - ) - assert t.render(seq="ab") == "TrueFalse" - t = env.from_string( - "{% for x in seq %}{% for y in seq %}" - "{{ loop.first }}{% endfor %}{% endfor %}" - ) - assert t.render(seq="ab") == "TrueFalseTrueFalse" + t = env.from_string( + "{% for x in seq %}{{ loop.first }}" + "{% for y in seq %}{% endfor %}{% endfor %}" + ) + assert t.render(seq="ab") == "TrueFalse" + t = env.from_string( + "{% for x in seq %}{% for y in seq %}" + "{{ loop.first }}{% endfor %}{% endfor %}" + ) + assert t.render(seq="ab") == "TrueFalseTrueFalse" def test_recursive_empty_loop_iter(self, env): - t = env.from_string( - """ + t = env.from_string( + """ {%- for item in foo recursive -%}{%- endfor -%} - """ - ) - assert t.render(dict(foo=[])) == "" + """ + ) + assert t.render(dict(foo=[])) == "" def test_call_in_loop(self, env): - t = env.from_string( - """ + t = env.from_string( + """ {%- macro do_something() -%} [{{ caller() }}] {%- endmacro %} @@ -265,173 +265,173 @@ class TestForLoop: {{ i }} {%- endcall %} {%- endfor -%} - """ - ) - assert t.render() == "[1][2][3]" + """ + ) + assert t.render() == "[1][2][3]" def test_scoping_bug(self, env): - t = env.from_string( - """ + t = env.from_string( + """ {%- for item in foo %}...{{ item }}...{% endfor %} {%- macro item(a) %}...{{ a }}...{% endmacro %} {{- item(2) -}} - """ - ) - assert t.render(foo=(1,)) == "...1......2..." + """ + ) + assert t.render(foo=(1,)) == "...1......2..." def test_unpacking(self, env): - tmpl = env.from_string( - "{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}" - ) - assert tmpl.render() == "1|2|3" + tmpl = env.from_string( + "{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}" + ) + assert tmpl.render() == "1|2|3" def test_intended_scoping_with_set(self, env): - tmpl = env.from_string( - "{% for item in seq %}{{ x }}{% set x = item %}{{ x }}{% endfor %}" - ) - assert tmpl.render(x=0, seq=[1, 2, 3]) == "010203" + tmpl = env.from_string( + "{% for item in seq %}{{ x }}{% set x = item %}{{ x }}{% endfor %}" + ) + assert tmpl.render(x=0, seq=[1, 2, 3]) == "010203" - tmpl = env.from_string( - "{% set x = 9 %}{% for item in seq %}{{ x }}" - "{% set x = item %}{{ x }}{% endfor %}" - ) - assert tmpl.render(x=0, seq=[1, 2, 3]) == "919293" + tmpl = env.from_string( + "{% set x = 9 %}{% for item in seq %}{{ x }}" + "{% set x = item %}{{ x }}{% endfor %}" + ) + assert tmpl.render(x=0, seq=[1, 2, 3]) == "919293" -class TestIfCondition: +class TestIfCondition: def test_simple(self, env): - tmpl = env.from_string("""{% if true %}...{% endif %}""") - assert tmpl.render() == "..." + tmpl = env.from_string("""{% if true %}...{% endif %}""") + assert tmpl.render() == "..." def test_elif(self, env): - tmpl = env.from_string( - """{% if false %}XXX{% elif true - %}...{% else %}XXX{% endif %}""" - ) - assert tmpl.render() == "..." + tmpl = env.from_string( + """{% if false %}XXX{% elif true + %}...{% else %}XXX{% endif %}""" + ) + assert tmpl.render() == "..." def test_elif_deep(self, env): - elifs = "\n".join(f"{{% elif a == {i} %}}{i}" for i in range(1, 1000)) - tmpl = env.from_string(f"{{% if a == 0 %}}0{elifs}{{% else %}}x{{% endif %}}") + elifs = "\n".join(f"{{% elif a == {i} %}}{i}" for i in range(1, 1000)) + tmpl = env.from_string(f"{{% if a == 0 %}}0{elifs}{{% else %}}x{{% endif %}}") for x in (0, 10, 999): assert tmpl.render(a=x).strip() == str(x) - assert tmpl.render(a=1000).strip() == "x" + assert tmpl.render(a=1000).strip() == "x" def test_else(self, env): - tmpl = env.from_string("{% if false %}XXX{% else %}...{% endif %}") - assert tmpl.render() == "..." + tmpl = env.from_string("{% if false %}XXX{% else %}...{% endif %}") + assert tmpl.render() == "..." def test_empty(self, env): - tmpl = env.from_string("[{% if true %}{% else %}{% endif %}]") - assert tmpl.render() == "[]" + tmpl = env.from_string("[{% if true %}{% else %}{% endif %}]") + assert tmpl.render() == "[]" def test_complete(self, env): - tmpl = env.from_string( - "{% if a %}A{% elif b %}B{% elif c == d %}C{% else %}D{% endif %}" - ) - assert tmpl.render(a=0, b=False, c=42, d=42.0) == "C" + tmpl = env.from_string( + "{% if a %}A{% elif b %}B{% elif c == d %}C{% else %}D{% endif %}" + ) + assert tmpl.render(a=0, b=False, c=42, d=42.0) == "C" def test_no_scope(self, env): - tmpl = env.from_string("{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}") - assert tmpl.render(a=True) == "1" - tmpl = env.from_string("{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}") - assert tmpl.render() == "1" + tmpl = env.from_string("{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}") + assert tmpl.render(a=True) == "1" + tmpl = env.from_string("{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}") + assert tmpl.render() == "1" -class TestMacros: +class TestMacros: def test_simple(self, env_trim): - tmpl = env_trim.from_string( - """\ + tmpl = env_trim.from_string( + """\ {% macro say_hello(name) %}Hello {{ name }}!{% endmacro %} -{{ say_hello('Peter') }}""" - ) - assert tmpl.render() == "Hello Peter!" +{{ say_hello('Peter') }}""" + ) + assert tmpl.render() == "Hello Peter!" def test_scoping(self, env_trim): - tmpl = env_trim.from_string( - """\ + tmpl = env_trim.from_string( + """\ {% macro level1(data1) %} {% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %} {{ level2('bar') }}{% endmacro %} -{{ level1('foo') }}""" - ) - assert tmpl.render() == "foo|bar" +{{ level1('foo') }}""" + ) + assert tmpl.render() == "foo|bar" def test_arguments(self, env_trim): - tmpl = env_trim.from_string( - """\ + tmpl = env_trim.from_string( + """\ {% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %} -{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}""" - ) - assert tmpl.render() == "||c|d|a||c|d|a|b|c|d|1|2|3|d" +{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}""" + ) + assert tmpl.render() == "||c|d|a||c|d|a|b|c|d|1|2|3|d" def test_arguments_defaults_nonsense(self, env_trim): - pytest.raises( - TemplateSyntaxError, - env_trim.from_string, - """\ -{% macro m(a, b=1, c) %}a={{ a }}, b={{ b }}, c={{ c }}{% endmacro %}""", - ) + pytest.raises( + TemplateSyntaxError, + env_trim.from_string, + """\ +{% macro m(a, b=1, c) %}a={{ a }}, b={{ b }}, c={{ c }}{% endmacro %}""", + ) def test_caller_defaults_nonsense(self, env_trim): - pytest.raises( - TemplateSyntaxError, - env_trim.from_string, - """\ + pytest.raises( + TemplateSyntaxError, + env_trim.from_string, + """\ {% macro a() %}{{ caller() }}{% endmacro %} -{% call(x, y=1, z) a() %}{% endcall %}""", - ) +{% call(x, y=1, z) a() %}{% endcall %}""", + ) def test_varargs(self, env_trim): - tmpl = env_trim.from_string( - """\ + tmpl = env_trim.from_string( + """\ {% macro test() %}{{ varargs|join('|') }}{% endmacro %}\ -{{ test(1, 2, 3) }}""" - ) - assert tmpl.render() == "1|2|3" +{{ test(1, 2, 3) }}""" + ) + assert tmpl.render() == "1|2|3" def test_simple_call(self, env_trim): - tmpl = env_trim.from_string( - """\ + tmpl = env_trim.from_string( + """\ {% macro test() %}[[{{ caller() }}]]{% endmacro %}\ -{% call test() %}data{% endcall %}""" - ) - assert tmpl.render() == "[[data]]" +{% call test() %}data{% endcall %}""" + ) + assert tmpl.render() == "[[data]]" def test_complex_call(self, env_trim): - tmpl = env_trim.from_string( - """\ + tmpl = env_trim.from_string( + """\ {% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\ -{% call(data) test() %}{{ data }}{% endcall %}""" - ) - assert tmpl.render() == "[[data]]" +{% call(data) test() %}{{ data }}{% endcall %}""" + ) + assert tmpl.render() == "[[data]]" def test_caller_undefined(self, env_trim): - tmpl = env_trim.from_string( - """\ + tmpl = env_trim.from_string( + """\ {% set caller = 42 %}\ {% macro test() %}{{ caller is not defined }}{% endmacro %}\ -{{ test() }}""" - ) - assert tmpl.render() == "True" +{{ test() }}""" + ) + assert tmpl.render() == "True" def test_include(self, env_trim): env_trim = Environment( - loader=DictLoader( - {"include": "{% macro test(foo) %}[{{ foo }}]{% endmacro %}"} - ) + loader=DictLoader( + {"include": "{% macro test(foo) %}[{{ foo }}]{% endmacro %}"} + ) ) - tmpl = env_trim.from_string('{% from "include" import test %}{{ test("foo") }}') - assert tmpl.render() == "[foo]" + tmpl = env_trim.from_string('{% from "include" import test %}{{ test("foo") }}') + assert tmpl.render() == "[foo]" def test_macro_api(self, env_trim): tmpl = env_trim.from_string( - "{% macro foo(a, b) %}{% endmacro %}" - "{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}" - "{% macro baz() %}{{ caller() }}{% endmacro %}" - ) - assert tmpl.module.foo.arguments == ("a", "b") - assert tmpl.module.foo.name == "foo" + "{% macro foo(a, b) %}{% endmacro %}" + "{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}" + "{% macro baz() %}{{ caller() }}{% endmacro %}" + ) + assert tmpl.module.foo.arguments == ("a", "b") + assert tmpl.module.foo.name == "foo" assert not tmpl.module.foo.caller assert not tmpl.module.foo.catch_kwargs assert not tmpl.module.foo.catch_varargs @@ -442,154 +442,154 @@ class TestMacros: assert tmpl.module.baz.caller def test_callself(self, env_trim): - tmpl = env_trim.from_string( - "{% macro foo(x) %}{{ x }}{% if x > 1 %}|" - "{{ foo(x - 1) }}{% endif %}{% endmacro %}" - "{{ foo(5) }}" - ) - assert tmpl.render() == "5|4|3|2|1" + tmpl = env_trim.from_string( + "{% macro foo(x) %}{{ x }}{% if x > 1 %}|" + "{{ foo(x - 1) }}{% endif %}{% endmacro %}" + "{{ foo(5) }}" + ) + assert tmpl.render() == "5|4|3|2|1" def test_macro_defaults_self_ref(self, env): - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {%- set x = 42 %} {%- macro m(a, b=x, x=23) %}{{ a }}|{{ b }}|{{ x }}{% endmacro -%} - """ - ) - assert tmpl.module.m(1) == "1||23" - assert tmpl.module.m(1, 2) == "1|2|23" - assert tmpl.module.m(1, 2, 3) == "1|2|3" - assert tmpl.module.m(1, x=7) == "1|7|7" + """ + ) + assert tmpl.module.m(1) == "1||23" + assert tmpl.module.m(1, 2) == "1|2|23" + assert tmpl.module.m(1, 2, 3) == "1|2|3" + assert tmpl.module.m(1, x=7) == "1|7|7" -class TestSet: +class TestSet: def test_normal(self, env_trim): - tmpl = env_trim.from_string("{% set foo = 1 %}{{ foo }}") - assert tmpl.render() == "1" + tmpl = env_trim.from_string("{% set foo = 1 %}{{ foo }}") + assert tmpl.render() == "1" assert tmpl.module.foo == 1 def test_block(self, env_trim): - tmpl = env_trim.from_string("{% set foo %}42{% endset %}{{ foo }}") - assert tmpl.render() == "42" - assert tmpl.module.foo == "42" + tmpl = env_trim.from_string("{% set foo %}42{% endset %}{{ foo }}") + assert tmpl.render() == "42" + assert tmpl.module.foo == "42" def test_block_escaping(self): env = Environment(autoescape=True) - tmpl = env.from_string( - "{% set foo %}<em>{{ test }}</em>{% endset %}foo: {{ foo }}" - ) - assert tmpl.render(test="<unsafe>") == "foo: <em><unsafe></em>" + tmpl = env.from_string( + "{% set foo %}<em>{{ test }}</em>{% endset %}foo: {{ foo }}" + ) + assert tmpl.render(test="<unsafe>") == "foo: <em><unsafe></em>" def test_set_invalid(self, env_trim): - pytest.raises( - TemplateSyntaxError, env_trim.from_string, "{% set foo['bar'] = 1 %}" - ) - tmpl = env_trim.from_string("{% set foo.bar = 1 %}") + pytest.raises( + TemplateSyntaxError, env_trim.from_string, "{% set foo['bar'] = 1 %}" + ) + tmpl = env_trim.from_string("{% set foo.bar = 1 %}") exc_info = pytest.raises(TemplateRuntimeError, tmpl.render, foo={}) - assert "non-namespace object" in exc_info.value.message + assert "non-namespace object" in exc_info.value.message def test_namespace_redefined(self, env_trim): - tmpl = env_trim.from_string("{% set ns = namespace() %}{% set ns.bar = 'hi' %}") - exc_info = pytest.raises(TemplateRuntimeError, tmpl.render, namespace=dict) - assert "non-namespace object" in exc_info.value.message + tmpl = env_trim.from_string("{% set ns = namespace() %}{% set ns.bar = 'hi' %}") + exc_info = pytest.raises(TemplateRuntimeError, tmpl.render, namespace=dict) + assert "non-namespace object" in exc_info.value.message def test_namespace(self, env_trim): - tmpl = env_trim.from_string( - "{% set ns = namespace() %}{% set ns.bar = '42' %}{{ ns.bar }}" - ) - assert tmpl.render() == "42" + tmpl = env_trim.from_string( + "{% set ns = namespace() %}{% set ns.bar = '42' %}{{ ns.bar }}" + ) + assert tmpl.render() == "42" def test_namespace_block(self, env_trim): - tmpl = env_trim.from_string( - "{% set ns = namespace() %}{% set ns.bar %}42{% endset %}{{ ns.bar }}" - ) - assert tmpl.render() == "42" + tmpl = env_trim.from_string( + "{% set ns = namespace() %}{% set ns.bar %}42{% endset %}{{ ns.bar }}" + ) + assert tmpl.render() == "42" def test_init_namespace(self, env_trim): - tmpl = env_trim.from_string( - "{% set ns = namespace(d, self=37) %}" - "{% set ns.b = 42 %}" - "{{ ns.a }}|{{ ns.self }}|{{ ns.b }}" - ) - assert tmpl.render(d={"a": 13}) == "13|37|42" + tmpl = env_trim.from_string( + "{% set ns = namespace(d, self=37) %}" + "{% set ns.b = 42 %}" + "{{ ns.a }}|{{ ns.self }}|{{ ns.b }}" + ) + assert tmpl.render(d={"a": 13}) == "13|37|42" def test_namespace_loop(self, env_trim): - tmpl = env_trim.from_string( - "{% set ns = namespace(found=false) %}" - "{% for x in range(4) %}" - "{% if x == v %}" - "{% set ns.found = true %}" - "{% endif %}" - "{% endfor %}" - "{{ ns.found }}" - ) - assert tmpl.render(v=3) == "True" - assert tmpl.render(v=4) == "False" + tmpl = env_trim.from_string( + "{% set ns = namespace(found=false) %}" + "{% for x in range(4) %}" + "{% if x == v %}" + "{% set ns.found = true %}" + "{% endif %}" + "{% endfor %}" + "{{ ns.found }}" + ) + assert tmpl.render(v=3) == "True" + assert tmpl.render(v=4) == "False" def test_namespace_macro(self, env_trim): - tmpl = env_trim.from_string( - "{% set ns = namespace() %}" - "{% set ns.a = 13 %}" - "{% macro magic(x) %}" - "{% set x.b = 37 %}" - "{% endmacro %}" - "{{ magic(ns) }}" - "{{ ns.a }}|{{ ns.b }}" - ) - assert tmpl.render() == "13|37" + tmpl = env_trim.from_string( + "{% set ns = namespace() %}" + "{% set ns.a = 13 %}" + "{% macro magic(x) %}" + "{% set x.b = 37 %}" + "{% endmacro %}" + "{{ magic(ns) }}" + "{{ ns.a }}|{{ ns.b }}" + ) + assert tmpl.render() == "13|37" def test_block_escaping_filtered(self): env = Environment(autoescape=True) - tmpl = env.from_string( - "{% set foo | trim %}<em>{{ test }}</em> {% endset %}foo: {{ foo }}" - ) - assert tmpl.render(test="<unsafe>") == "foo: <em><unsafe></em>" + tmpl = env.from_string( + "{% set foo | trim %}<em>{{ test }}</em> {% endset %}foo: {{ foo }}" + ) + assert tmpl.render(test="<unsafe>") == "foo: <em><unsafe></em>" def test_block_filtered(self, env_trim): tmpl = env_trim.from_string( - "{% set foo | trim | length | string %} 42 {% endset %}{{ foo }}" - ) - assert tmpl.render() == "2" - assert tmpl.module.foo == "2" + "{% set foo | trim | length | string %} 42 {% endset %}{{ foo }}" + ) + assert tmpl.render() == "2" + assert tmpl.module.foo == "2" def test_block_filtered_set(self, env_trim): def _myfilter(val, arg): - assert arg == " xxx " + assert arg == " xxx " return val - - env_trim.filters["myfilter"] = _myfilter + + env_trim.filters["myfilter"] = _myfilter tmpl = env_trim.from_string( '{% set a = " xxx " %}' - "{% set foo | myfilter(a) | trim | length | string %}" + "{% set foo | myfilter(a) | trim | length | string %}" ' {% set b = " yy " %} 42 {{ a }}{{ b }} ' - "{% endset %}" - "{{ foo }}" - ) - assert tmpl.render() == "11" - assert tmpl.module.foo == "11" + "{% endset %}" + "{{ foo }}" + ) + assert tmpl.render() == "11" + assert tmpl.module.foo == "11" -class TestWith: +class TestWith: def test_with(self, env): - tmpl = env.from_string( - """\ + tmpl = env.from_string( + """\ {% with a=42, b=23 -%} {{ a }} = {{ b }} {% endwith -%} {{ a }} = {{ b }}\ - """ - ) - assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] == [ - "42 = 23", - "1 = 2", - ] + """ + ) + assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] == [ + "42 = 23", + "1 = 2", + ] def test_with_argument_scoping(self, env): - tmpl = env.from_string( - """\ + tmpl = env.from_string( + """\ {%- with a=1, b=2, c=b, d=e, e=5 -%} {{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }} {%- endwith -%} - """ - ) - assert tmpl.render(b=3, e=4) == "1|2|3|4|5" + """ + ) + assert tmpl.render(b=3, e=4) == "1|2|3|4|5" diff --git a/contrib/python/Jinja2/py3/tests/test_debug.py b/contrib/python/Jinja2/py3/tests/test_debug.py index 1cb931cfc1..901bb525c2 100644 --- a/contrib/python/Jinja2/py3/tests/test_debug.py +++ b/contrib/python/Jinja2/py3/tests/test_debug.py @@ -1,40 +1,40 @@ -import pickle -import re -from traceback import format_exception +import pickle +import re +from traceback import format_exception import pytest -from jinja2 import ChoiceLoader -from jinja2 import DictLoader -from jinja2 import Environment -from jinja2 import TemplateSyntaxError +from jinja2 import ChoiceLoader +from jinja2 import DictLoader +from jinja2 import Environment +from jinja2 import TemplateSyntaxError @pytest.fixture def fs_env(filesystem_loader): - """returns a new environment.""" + """returns a new environment.""" return Environment(loader=filesystem_loader) -class TestDebug: +class TestDebug: def assert_traceback_matches(self, callback, expected_tb): - with pytest.raises(Exception) as exc_info: + with pytest.raises(Exception) as exc_info: callback() - tb = format_exception(exc_info.type, exc_info.value, exc_info.tb) - 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}" - + tb = format_exception(exc_info.type, exc_info.value, exc_info.tb) + 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}" + def test_runtime_error(self, fs_env): def test(): tmpl.render(fail=lambda: 1 / 0) - - tmpl = fs_env.get_template("broken.html") - self.assert_traceback_matches( - test, - r""" + + tmpl = fs_env.get_template("broken.html") + self.assert_traceback_matches( + test, + r""" File ".*?broken.html", line 2, in (top-level template code|<module>) \{\{ fail\(\) \}\}( \^{12})? @@ -42,76 +42,76 @@ class TestDebug: tmpl\.render\(fail=lambda: 1 / 0\)( ~~\^~~)? ZeroDivisionError: (int(eger)? )?division (or modulo )?by zero -""", - ) +""", + ) def test_syntax_error(self, fs_env): - # The trailing .*? is for PyPy 2 and 3, which don't seem to - # clear the exception's original traceback, leaving the syntax - # error in the middle of other compiler frames. - self.assert_traceback_matches( - lambda: fs_env.get_template("syntaxerror.html"), - """(?sm) + # The trailing .*? is for PyPy 2 and 3, which don't seem to + # clear the exception's original traceback, leaving the syntax + # error in the middle of other compiler frames. + self.assert_traceback_matches( + lambda: fs_env.get_template("syntaxerror.html"), + """(?sm) File ".*?syntaxerror.html", line 4, in (template|<module>) - \\{% endif %\\}.*? -(jinja2\\.exceptions\\.)?TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja \ -was looking for the following tags: 'endfor' or 'else'. The innermost block that needs \ -to be closed is 'for'. - """, - ) + \\{% endif %\\}.*? +(jinja2\\.exceptions\\.)?TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja \ +was looking for the following tags: 'endfor' or 'else'. The innermost block that needs \ +to be closed is 'for'. + """, + ) def test_regular_syntax_error(self, fs_env): def test(): - raise TemplateSyntaxError("wtf", 42) - - self.assert_traceback_matches( - test, - r""" + raise TemplateSyntaxError("wtf", 42) + + self.assert_traceback_matches( + test, + r""" File ".*debug.pyc?", line \d+, in test raise TemplateSyntaxError\("wtf", 42\)( \^{36})? (jinja2\.exceptions\.)?TemplateSyntaxError: wtf - line 42""", - ) - - def test_pickleable_syntax_error(self, fs_env): - original = TemplateSyntaxError("bad template", 42, "test", "test.txt") - unpickled = pickle.loads(pickle.dumps(original)) - assert str(original) == str(unpickled) - assert original.name == unpickled.name - - def test_include_syntax_error_source(self, filesystem_loader): - e = Environment( - loader=ChoiceLoader( - [ - filesystem_loader, - DictLoader({"inc": "a\n{% include 'syntaxerror.html' %}\nb"}), - ] - ) - ) - t = e.get_template("inc") - - with pytest.raises(TemplateSyntaxError) as exc_info: - t.render() - - assert exc_info.value.source is not None - + line 42""", + ) + + def test_pickleable_syntax_error(self, fs_env): + original = TemplateSyntaxError("bad template", 42, "test", "test.txt") + unpickled = pickle.loads(pickle.dumps(original)) + assert str(original) == str(unpickled) + assert original.name == unpickled.name + + def test_include_syntax_error_source(self, filesystem_loader): + e = Environment( + loader=ChoiceLoader( + [ + filesystem_loader, + DictLoader({"inc": "a\n{% include 'syntaxerror.html' %}\nb"}), + ] + ) + ) + t = e.get_template("inc") + + with pytest.raises(TemplateSyntaxError) as exc_info: + t.render() + + assert exc_info.value.source is not None + def test_local_extraction(self): - from jinja2.debug import get_template_locals + from jinja2.debug import get_template_locals from jinja2.runtime import missing - - locals = get_template_locals( - { - "l_0_foo": 42, - "l_1_foo": 23, - "l_2_foo": 13, - "l_0_bar": 99, - "l_1_bar": missing, - "l_0_baz": missing, - } - ) - assert locals == {"foo": 13, "bar": 99} - - def test_get_corresponding_lineno_traceback(self, fs_env): - tmpl = fs_env.get_template("test.html") - assert tmpl.get_corresponding_lineno(1) == 1 + + locals = get_template_locals( + { + "l_0_foo": 42, + "l_1_foo": 23, + "l_2_foo": 13, + "l_0_bar": 99, + "l_1_bar": missing, + "l_0_baz": missing, + } + ) + assert locals == {"foo": 13, "bar": 99} + + def test_get_corresponding_lineno_traceback(self, fs_env): + tmpl = fs_env.get_template("test.html") + assert tmpl.get_corresponding_lineno(1) == 1 diff --git a/contrib/python/Jinja2/py3/tests/test_ext.py b/contrib/python/Jinja2/py3/tests/test_ext.py index b54e905ffd..9d4369ea5e 100644 --- a/contrib/python/Jinja2/py3/tests/test_ext.py +++ b/contrib/python/Jinja2/py3/tests/test_ext.py @@ -1,191 +1,191 @@ -import re -from io import BytesIO +import re +from io import BytesIO import pytest -from jinja2 import DictLoader -from jinja2 import Environment -from jinja2 import nodes -from jinja2 import pass_context +from jinja2 import DictLoader +from jinja2 import Environment +from jinja2 import nodes +from jinja2 import pass_context from jinja2.exceptions import TemplateAssertionError from jinja2.ext import Extension -from jinja2.lexer import count_newlines -from jinja2.lexer import Token +from jinja2.lexer import count_newlines +from jinja2.lexer import Token importable_object = 23 -_gettext_re = re.compile(r"_\((.*?)\)", re.DOTALL) +_gettext_re = re.compile(r"_\((.*?)\)", re.DOTALL) i18n_templates = { - "default.html": '<title>{{ page_title|default(_("missing")) }}</title>' - "{% block body %}{% endblock %}", - "child.html": '{% extends "default.html" %}{% block body %}' - "{% trans %}watch out{% endtrans %}{% endblock %}", - "plural.html": "{% trans user_count %}One user online{% pluralize %}" - "{{ user_count }} users online{% endtrans %}", - "plural2.html": "{% trans user_count=get_user_count() %}{{ user_count }}s" - "{% pluralize %}{{ user_count }}p{% endtrans %}", - "stringformat.html": '{{ _("User: %(num)s")|format(num=user_count) }}', + "default.html": '<title>{{ page_title|default(_("missing")) }}</title>' + "{% block body %}{% endblock %}", + "child.html": '{% extends "default.html" %}{% block body %}' + "{% trans %}watch out{% endtrans %}{% endblock %}", + "plural.html": "{% trans user_count %}One user online{% pluralize %}" + "{{ user_count }} users online{% endtrans %}", + "plural2.html": "{% trans user_count=get_user_count() %}{{ user_count }}s" + "{% pluralize %}{{ user_count }}p{% endtrans %}", + "stringformat.html": '{{ _("User: %(num)s")|format(num=user_count) }}', } newstyle_i18n_templates = { - "default.html": '<title>{{ page_title|default(_("missing")) }}</title>' - "{% block body %}{% endblock %}", - "child.html": '{% extends "default.html" %}{% block body %}' - "{% trans %}watch out{% endtrans %}{% endblock %}", - "plural.html": "{% trans user_count %}One user online{% pluralize %}" - "{{ user_count }} users online{% endtrans %}", - "stringformat.html": '{{ _("User: %(num)s", num=user_count) }}', - "ngettext.html": '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}', - "ngettext_long.html": "{% trans num=apples %}{{ num }} apple{% pluralize %}" - "{{ num }} apples{% endtrans %}", - "pgettext.html": '{{ pgettext("fruit", "Apple") }}', - "npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s apples",' - " apples) }}", - "transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}", - "transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}", - "transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}", - "novars.html": "{% trans %}%(hello)s{% endtrans %}", - "vars.html": "{% trans %}{{ foo }}%(foo)s{% endtrans %}", - "explicitvars.html": '{% trans foo="42" %}%(foo)s{% endtrans %}', + "default.html": '<title>{{ page_title|default(_("missing")) }}</title>' + "{% block body %}{% endblock %}", + "child.html": '{% extends "default.html" %}{% block body %}' + "{% trans %}watch out{% endtrans %}{% endblock %}", + "plural.html": "{% trans user_count %}One user online{% pluralize %}" + "{{ user_count }} users online{% endtrans %}", + "stringformat.html": '{{ _("User: %(num)s", num=user_count) }}', + "ngettext.html": '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}', + "ngettext_long.html": "{% trans num=apples %}{{ num }} apple{% pluralize %}" + "{{ num }} apples{% endtrans %}", + "pgettext.html": '{{ pgettext("fruit", "Apple") }}', + "npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s apples",' + " apples) }}", + "transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}", + "transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}", + "transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}", + "novars.html": "{% trans %}%(hello)s{% endtrans %}", + "vars.html": "{% trans %}{{ foo }}%(foo)s{% endtrans %}", + "explicitvars.html": '{% trans foo="42" %}%(foo)s{% endtrans %}', } languages = { - "de": { - "missing": "fehlend", - "watch out": "pass auf", - "One user online": "Ein Benutzer online", - "%(user_count)s users online": "%(user_count)s Benutzer online", - "User: %(num)s": "Benutzer: %(num)s", - "User: %(count)s": "Benutzer: %(count)s", - "Apple": {None: "Apfel", "fruit": "Apple"}, - "%(num)s apple": {None: "%(num)s Apfel", "fruit": "%(num)s Apple"}, - "%(num)s apples": {None: "%(num)s Äpfel", "fruit": "%(num)s Apples"}, + "de": { + "missing": "fehlend", + "watch out": "pass auf", + "One user online": "Ein Benutzer online", + "%(user_count)s users online": "%(user_count)s Benutzer online", + "User: %(num)s": "Benutzer: %(num)s", + "User: %(count)s": "Benutzer: %(count)s", + "Apple": {None: "Apfel", "fruit": "Apple"}, + "%(num)s apple": {None: "%(num)s Apfel", "fruit": "%(num)s Apple"}, + "%(num)s apples": {None: "%(num)s Äpfel", "fruit": "%(num)s Apples"}, } } -def _get_with_context(value, ctx=None): - if isinstance(value, dict): - return value.get(ctx, value) - - return value - - -@pass_context +def _get_with_context(value, ctx=None): + if isinstance(value, dict): + return value.get(ctx, value) + + return value + + +@pass_context def gettext(context, string): - language = context.get("LANGUAGE", "en") - value = languages.get(language, {}).get(string, string) - return _get_with_context(value) + language = context.get("LANGUAGE", "en") + value = languages.get(language, {}).get(string, string) + return _get_with_context(value) -@pass_context +@pass_context def ngettext(context, s, p, n): - language = context.get("LANGUAGE", "en") - - if n != 1: - value = languages.get(language, {}).get(p, p) - return _get_with_context(value) - - value = languages.get(language, {}).get(s, s) - return _get_with_context(value) - - -@pass_context -def pgettext(context, c, s): - language = context.get("LANGUAGE", "en") - value = languages.get(language, {}).get(s, s) - return _get_with_context(value, c) - - -@pass_context -def npgettext(context, c, s, p, n): - language = context.get("LANGUAGE", "en") - + language = context.get("LANGUAGE", "en") + if n != 1: - value = languages.get(language, {}).get(p, p) - return _get_with_context(value, c) - - value = languages.get(language, {}).get(s, s) - return _get_with_context(value, c) - - + value = languages.get(language, {}).get(p, p) + return _get_with_context(value) + + value = languages.get(language, {}).get(s, s) + return _get_with_context(value) + + +@pass_context +def pgettext(context, c, s): + language = context.get("LANGUAGE", "en") + value = languages.get(language, {}).get(s, s) + return _get_with_context(value, c) + + +@pass_context +def npgettext(context, c, s, p, n): + language = context.get("LANGUAGE", "en") + + if n != 1: + value = languages.get(language, {}).get(p, p) + return _get_with_context(value, c) + + value = languages.get(language, {}).get(s, s) + return _get_with_context(value, c) + + i18n_env = Environment( - loader=DictLoader(i18n_templates), extensions=["jinja2.ext.i18n"] -) -i18n_env.globals.update( - { - "_": gettext, - "gettext": gettext, - "ngettext": ngettext, - "pgettext": pgettext, - "npgettext": npgettext, - } -) -i18n_env_trimmed = Environment(extensions=["jinja2.ext.i18n"]) - -i18n_env_trimmed.policies["ext.i18n.trimmed"] = True -i18n_env_trimmed.globals.update( - { - "_": gettext, - "gettext": gettext, - "ngettext": ngettext, - "pgettext": pgettext, - "npgettext": npgettext, - } + loader=DictLoader(i18n_templates), extensions=["jinja2.ext.i18n"] ) +i18n_env.globals.update( + { + "_": gettext, + "gettext": gettext, + "ngettext": ngettext, + "pgettext": pgettext, + "npgettext": npgettext, + } +) +i18n_env_trimmed = Environment(extensions=["jinja2.ext.i18n"]) + +i18n_env_trimmed.policies["ext.i18n.trimmed"] = True +i18n_env_trimmed.globals.update( + { + "_": gettext, + "gettext": gettext, + "ngettext": ngettext, + "pgettext": pgettext, + "npgettext": npgettext, + } +) newstyle_i18n_env = Environment( - loader=DictLoader(newstyle_i18n_templates), extensions=["jinja2.ext.i18n"] -) -newstyle_i18n_env.install_gettext_callables( # type: ignore - gettext, ngettext, newstyle=True, pgettext=pgettext, npgettext=npgettext + loader=DictLoader(newstyle_i18n_templates), extensions=["jinja2.ext.i18n"] ) +newstyle_i18n_env.install_gettext_callables( # type: ignore + gettext, ngettext, newstyle=True, pgettext=pgettext, npgettext=npgettext +) class ExampleExtension(Extension): - tags = {"test"} + tags = {"test"} ext_attr = 42 - context_reference_node_cls = nodes.ContextReference + context_reference_node_cls = nodes.ContextReference def parse(self, parser): - return nodes.Output( - [ - self.call_method( - "_dump", - [ - nodes.EnvironmentAttribute("sandboxed"), - self.attr("ext_attr"), - nodes.ImportedName(__name__ + ".importable_object"), - self.context_reference_node_cls(), - ], - ) - ] - ).set_lineno(next(parser.stream).lineno) + return nodes.Output( + [ + self.call_method( + "_dump", + [ + nodes.EnvironmentAttribute("sandboxed"), + self.attr("ext_attr"), + nodes.ImportedName(__name__ + ".importable_object"), + self.context_reference_node_cls(), + ], + ) + ] + ).set_lineno(next(parser.stream).lineno) def _dump(self, sandboxed, ext_attr, imported_object, context): - return ( - f"{sandboxed}|{ext_attr}|{imported_object}|{context.blocks}" - f"|{context.get('test_var')}" + return ( + f"{sandboxed}|{ext_attr}|{imported_object}|{context.blocks}" + f"|{context.get('test_var')}" ) -class DerivedExampleExtension(ExampleExtension): - context_reference_node_cls = nodes.DerivedContextReference # type: ignore - - +class DerivedExampleExtension(ExampleExtension): + context_reference_node_cls = nodes.DerivedContextReference # type: ignore + + class PreprocessorExtension(Extension): def preprocess(self, source, name, filename=None): - return source.replace("[[TEST]]", "({{ foo }})") + return source.replace("[[TEST]]", "({{ foo }})") class StreamFilterExtension(Extension): def filter_stream(self, stream): for token in stream: - if token.type == "data": - yield from self.interpolate(token) + if token.type == "data": + yield from self.interpolate(token) else: yield token @@ -193,101 +193,101 @@ class StreamFilterExtension(Extension): pos = 0 end = len(token.value) lineno = token.lineno - while True: + while True: match = _gettext_re.search(token.value, pos) if match is None: break - value = token.value[pos : match.start()] + value = token.value[pos : match.start()] if value: - yield Token(lineno, "data", value) + yield Token(lineno, "data", value) lineno += count_newlines(token.value) - yield Token(lineno, "variable_begin", None) - yield Token(lineno, "name", "gettext") - yield Token(lineno, "lparen", None) - yield Token(lineno, "string", match.group(1)) - yield Token(lineno, "rparen", None) - yield Token(lineno, "variable_end", None) + yield Token(lineno, "variable_begin", None) + yield Token(lineno, "name", "gettext") + yield Token(lineno, "lparen", None) + yield Token(lineno, "string", match.group(1)) + yield Token(lineno, "rparen", None) + yield Token(lineno, "variable_end", None) pos = match.end() if pos < end: - yield Token(lineno, "data", token.value[pos:]) + yield Token(lineno, "data", token.value[pos:]) -class TestExtensions: +class TestExtensions: def test_extend_late(self): env = Environment() - t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}') - assert t.render() == "<test>" + t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}') + assert t.render() == "<test>" def test_loop_controls(self): - env = Environment(extensions=["jinja2.ext.loopcontrols"]) + env = Environment(extensions=["jinja2.ext.loopcontrols"]) - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {%- for item in [1, 2, 3, 4] %} {%- if item % 2 == 0 %}{% continue %}{% endif -%} {{ item }} - {%- endfor %}""" - ) - assert tmpl.render() == "13" + {%- endfor %}""" + ) + assert tmpl.render() == "13" - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {%- for item in [1, 2, 3, 4] %} {%- if item > 2 %}{% break %}{% endif -%} {{ item }} - {%- endfor %}""" - ) - assert tmpl.render() == "12" + {%- endfor %}""" + ) + assert tmpl.render() == "12" def test_do(self): - env = Environment(extensions=["jinja2.ext.do"]) - tmpl = env.from_string( - """ + env = Environment(extensions=["jinja2.ext.do"]) + tmpl = env.from_string( + """ {%- set items = [] %} {%- for char in "foo" %} {%- do items.append(loop.index0 ~ char) %} - {%- endfor %}{{ items|join(', ') }}""" - ) - assert tmpl.render() == "0f, 1o, 2o" + {%- endfor %}{{ items|join(', ') }}""" + ) + assert tmpl.render() == "0f, 1o, 2o" def test_extension_nodes(self): env = Environment(extensions=[ExampleExtension]) - tmpl = env.from_string("{% test %}") - assert tmpl.render() == "False|42|23|{}|None" - - def test_contextreference_node_passes_context(self): - env = Environment(extensions=[ExampleExtension]) - tmpl = env.from_string('{% set test_var="test_content" %}{% test %}') - assert tmpl.render() == "False|42|23|{}|test_content" - - def test_contextreference_node_can_pass_locals(self): - env = Environment(extensions=[DerivedExampleExtension]) - tmpl = env.from_string( - '{% for test_var in ["test_content"] %}{% test %}{% endfor %}' - ) - assert tmpl.render() == "False|42|23|{}|test_content" - + tmpl = env.from_string("{% test %}") + assert tmpl.render() == "False|42|23|{}|None" + + def test_contextreference_node_passes_context(self): + env = Environment(extensions=[ExampleExtension]) + tmpl = env.from_string('{% set test_var="test_content" %}{% test %}') + assert tmpl.render() == "False|42|23|{}|test_content" + + def test_contextreference_node_can_pass_locals(self): + env = Environment(extensions=[DerivedExampleExtension]) + tmpl = env.from_string( + '{% for test_var in ["test_content"] %}{% test %}{% endfor %}' + ) + assert tmpl.render() == "False|42|23|{}|test_content" + def test_identifier(self): - assert ExampleExtension.identifier == __name__ + ".ExampleExtension" + assert ExampleExtension.identifier == __name__ + ".ExampleExtension" def test_rebinding(self): original = Environment(extensions=[ExampleExtension]) overlay = original.overlay() for env in original, overlay: - for ext in env.extensions.values(): + for ext in env.extensions.values(): assert ext.environment is env def test_preprocessor_extension(self): env = Environment(extensions=[PreprocessorExtension]) - tmpl = env.from_string("{[[TEST]]}") - assert tmpl.render(foo=42) == "{(42)}" + tmpl = env.from_string("{[[TEST]]}") + assert tmpl.render(foo=42) == "{(42)}" def test_streamfilter_extension(self): env = Environment(extensions=[StreamFilterExtension]) - env.globals["gettext"] = lambda x: x.upper() - tmpl = env.from_string("Foo _(bar) Baz") + env.globals["gettext"] = lambda x: x.upper() + tmpl = env.from_string("Foo _(bar) Baz") out = tmpl.render() - assert out == "Foo BAR Baz" + assert out == "Foo BAR Baz" def test_extension_ordering(self): class T1(Extension): @@ -295,374 +295,374 @@ class TestExtensions: class T2(Extension): priority = 2 - + env = Environment(extensions=[T1, T2]) ext = list(env.iter_extensions()) assert ext[0].__class__ is T1 assert ext[1].__class__ is T2 - def test_debug(self): - env = Environment(extensions=["jinja2.ext.debug"]) - t = env.from_string("Hello\n{% debug %}\nGoodbye") - out = t.render() - - for value in ("context", "cycler", "filters", "abs", "tests", "!="): - assert f"'{value}'" in out - + def test_debug(self): + env = Environment(extensions=["jinja2.ext.debug"]) + t = env.from_string("Hello\n{% debug %}\nGoodbye") + out = t.render() -class TestInternationalization: + for value in ("context", "cycler", "filters", "abs", "tests", "!="): + assert f"'{value}'" in out + + +class TestInternationalization: def test_trans(self): - tmpl = i18n_env.get_template("child.html") - assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf" + tmpl = i18n_env.get_template("child.html") + assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf" def test_trans_plural(self): - tmpl = i18n_env.get_template("plural.html") - assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online" - assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online" + tmpl = i18n_env.get_template("plural.html") + assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online" + assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online" def test_trans_plural_with_functions(self): - tmpl = i18n_env.get_template("plural2.html") + tmpl = i18n_env.get_template("plural2.html") def get_user_count(): get_user_count.called += 1 return 1 - + get_user_count.called = 0 - assert tmpl.render(LANGUAGE="de", get_user_count=get_user_count) == "1s" + assert tmpl.render(LANGUAGE="de", get_user_count=get_user_count) == "1s" assert get_user_count.called == 1 def test_complex_plural(self): tmpl = i18n_env.from_string( - "{% trans foo=42, count=2 %}{{ count }} item{% " - "pluralize count %}{{ count }} items{% endtrans %}" - ) - assert tmpl.render() == "2 items" - pytest.raises( - TemplateAssertionError, - i18n_env.from_string, - "{% trans foo %}...{% pluralize bar %}...{% endtrans %}", - ) + "{% trans foo=42, count=2 %}{{ count }} item{% " + "pluralize count %}{{ count }} items{% endtrans %}" + ) + assert tmpl.render() == "2 items" + pytest.raises( + TemplateAssertionError, + i18n_env.from_string, + "{% trans foo %}...{% pluralize bar %}...{% endtrans %}", + ) def test_trans_stringformatting(self): - tmpl = i18n_env.get_template("stringformat.html") - assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5" + tmpl = i18n_env.get_template("stringformat.html") + assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5" def test_trimmed(self): tmpl = i18n_env.from_string( - "{%- trans trimmed %} hello\n world {% endtrans -%}" - ) - assert tmpl.render() == "hello world" + "{%- trans trimmed %} hello\n world {% endtrans -%}" + ) + assert tmpl.render() == "hello world" def test_trimmed_policy(self): - s = "{%- trans %} hello\n world {% endtrans -%}" + s = "{%- trans %} hello\n world {% endtrans -%}" tmpl = i18n_env.from_string(s) trimmed_tmpl = i18n_env_trimmed.from_string(s) - assert tmpl.render() == " hello\n world " - assert trimmed_tmpl.render() == "hello world" + assert tmpl.render() == " hello\n world " + assert trimmed_tmpl.render() == "hello world" def test_trimmed_policy_override(self): tmpl = i18n_env_trimmed.from_string( - "{%- trans notrimmed %} hello\n world {% endtrans -%}" - ) - assert tmpl.render() == " hello\n world " + "{%- trans notrimmed %} hello\n world {% endtrans -%}" + ) + assert tmpl.render() == " hello\n world " def test_trimmed_vars(self): tmpl = i18n_env.from_string( - '{%- trans trimmed x="world" %} hello\n {{ x }} {% endtrans -%}' - ) - assert tmpl.render() == "hello world" + '{%- trans trimmed x="world" %} hello\n {{ x }} {% endtrans -%}' + ) + assert tmpl.render() == "hello world" def test_trimmed_varname_trimmed(self): # unlikely variable name, but when used as a variable # it should not enable trimming tmpl = i18n_env.from_string( - "{%- trans trimmed = 'world' %} hello\n {{ trimmed }} {% endtrans -%}" - ) - assert tmpl.render() == " hello\n world " + "{%- trans trimmed = 'world' %} hello\n {{ trimmed }} {% endtrans -%}" + ) + assert tmpl.render() == " hello\n world " def test_extract(self): from jinja2.ext import babel_extract - - source = BytesIO( - b""" - {{ gettext('Hello World') }} - {% trans %}Hello World{% endtrans %} - {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %} - """ - ) - assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [ - (2, "gettext", "Hello World", []), - (3, "gettext", "Hello World", []), - (4, "ngettext", ("%(users)s user", "%(users)s users", None), []), + + source = BytesIO( + b""" + {{ gettext('Hello World') }} + {% trans %}Hello World{% endtrans %} + {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %} + """ + ) + assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [ + (2, "gettext", "Hello World", []), + (3, "gettext", "Hello World", []), + (4, "ngettext", ("%(users)s user", "%(users)s users", None), []), ] def test_extract_trimmed(self): from jinja2.ext import babel_extract - - source = BytesIO( - b""" - {{ gettext(' Hello \n World') }} - {% trans trimmed %} Hello \n World{% endtrans %} - {% trans trimmed %}{{ users }} \n user - {%- pluralize %}{{ users }} \n users{% endtrans %} - """ - ) - assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [ - (2, "gettext", " Hello \n World", []), - (4, "gettext", "Hello World", []), - (6, "ngettext", ("%(users)s user", "%(users)s users", None), []), + + source = BytesIO( + b""" + {{ gettext(' Hello \n World') }} + {% trans trimmed %} Hello \n World{% endtrans %} + {% trans trimmed %}{{ users }} \n user + {%- pluralize %}{{ users }} \n users{% endtrans %} + """ + ) + assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [ + (2, "gettext", " Hello \n World", []), + (4, "gettext", "Hello World", []), + (6, "ngettext", ("%(users)s user", "%(users)s users", None), []), ] def test_extract_trimmed_option(self): from jinja2.ext import babel_extract - - source = BytesIO( - b""" - {{ gettext(' Hello \n World') }} - {% trans %} Hello \n World{% endtrans %} - {% trans %}{{ users }} \n user - {%- pluralize %}{{ users }} \n users{% endtrans %} - """ - ) - opts = {"trimmed": "true"} - assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], opts)) == [ - (2, "gettext", " Hello \n World", []), - (4, "gettext", "Hello World", []), - (6, "ngettext", ("%(users)s user", "%(users)s users", None), []), + + source = BytesIO( + b""" + {{ gettext(' Hello \n World') }} + {% trans %} Hello \n World{% endtrans %} + {% trans %}{{ users }} \n user + {%- pluralize %}{{ users }} \n users{% endtrans %} + """ + ) + opts = {"trimmed": "true"} + assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], opts)) == [ + (2, "gettext", " Hello \n World", []), + (4, "gettext", "Hello World", []), + (6, "ngettext", ("%(users)s user", "%(users)s users", None), []), ] def test_comment_extract(self): from jinja2.ext import babel_extract - - source = BytesIO( - b""" - {# trans first #} - {{ gettext('Hello World') }} - {% trans %}Hello World{% endtrans %}{# trans second #} - {#: third #} - {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %} - """ - ) - assert list( - babel_extract(source, ("gettext", "ngettext", "_"), ["trans", ":"], {}) - ) == [ - (3, "gettext", "Hello World", ["first"]), - (4, "gettext", "Hello World", ["second"]), - (6, "ngettext", ("%(users)s user", "%(users)s users", None), ["third"]), - ] - - def test_extract_context(self): - from jinja2.ext import babel_extract - - source = BytesIO( - b""" - {{ pgettext("babel", "Hello World") }} - {{ npgettext("babel", "%(users)s user", "%(users)s users", users) }} - """ - ) - assert list(babel_extract(source, ("pgettext", "npgettext", "_"), [], {})) == [ - (2, "pgettext", ("babel", "Hello World"), []), - (3, "npgettext", ("babel", "%(users)s user", "%(users)s users", None), []), + + source = BytesIO( + b""" + {# trans first #} + {{ gettext('Hello World') }} + {% trans %}Hello World{% endtrans %}{# trans second #} + {#: third #} + {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %} + """ + ) + assert list( + babel_extract(source, ("gettext", "ngettext", "_"), ["trans", ":"], {}) + ) == [ + (3, "gettext", "Hello World", ["first"]), + (4, "gettext", "Hello World", ["second"]), + (6, "ngettext", ("%(users)s user", "%(users)s users", None), ["third"]), ] - -class TestScope: + def test_extract_context(self): + from jinja2.ext import babel_extract + + source = BytesIO( + b""" + {{ pgettext("babel", "Hello World") }} + {{ npgettext("babel", "%(users)s user", "%(users)s users", users) }} + """ + ) + assert list(babel_extract(source, ("pgettext", "npgettext", "_"), [], {})) == [ + (2, "pgettext", ("babel", "Hello World"), []), + (3, "npgettext", ("babel", "%(users)s user", "%(users)s users", None), []), + ] + + +class TestScope: def test_basic_scope_behavior(self): # This is what the old with statement compiled down to class ScopeExt(Extension): - tags = {"scope"} + tags = {"scope"} def parse(self, parser): node = nodes.Scope(lineno=next(parser.stream).lineno) assignments = [] - while parser.stream.current.type != "block_end": + while parser.stream.current.type != "block_end": lineno = parser.stream.current.lineno if assignments: - parser.stream.expect("comma") + parser.stream.expect("comma") target = parser.parse_assign_target() - parser.stream.expect("assign") + parser.stream.expect("assign") expr = parser.parse_expression() assignments.append(nodes.Assign(target, expr, lineno=lineno)) - node.body = assignments + list( - parser.parse_statements(("name:endscope",), drop_needle=True) - ) + node.body = assignments + list( + parser.parse_statements(("name:endscope",), drop_needle=True) + ) return node env = Environment(extensions=[ScopeExt]) - tmpl = env.from_string( - """\ + tmpl = env.from_string( + """\ {%- scope a=1, b=2, c=b, d=e, e=5 -%} {{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }} {%- endscope -%} - """ - ) - assert tmpl.render(b=3, e=4) == "1|2|2|4|5" + """ + ) + assert tmpl.render(b=3, e=4) == "1|2|2|4|5" -class TestNewstyleInternationalization: +class TestNewstyleInternationalization: def test_trans(self): - tmpl = newstyle_i18n_env.get_template("child.html") - assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf" + tmpl = newstyle_i18n_env.get_template("child.html") + assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf" def test_trans_plural(self): - tmpl = newstyle_i18n_env.get_template("plural.html") - assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online" - assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online" + tmpl = newstyle_i18n_env.get_template("plural.html") + assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online" + assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online" def test_complex_plural(self): tmpl = newstyle_i18n_env.from_string( - "{% trans foo=42, count=2 %}{{ count }} item{% " - "pluralize count %}{{ count }} items{% endtrans %}" - ) - assert tmpl.render() == "2 items" - pytest.raises( - TemplateAssertionError, - i18n_env.from_string, - "{% trans foo %}...{% pluralize bar %}...{% endtrans %}", - ) + "{% trans foo=42, count=2 %}{{ count }} item{% " + "pluralize count %}{{ count }} items{% endtrans %}" + ) + assert tmpl.render() == "2 items" + pytest.raises( + TemplateAssertionError, + i18n_env.from_string, + "{% trans foo %}...{% pluralize bar %}...{% endtrans %}", + ) def test_trans_stringformatting(self): - tmpl = newstyle_i18n_env.get_template("stringformat.html") - assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5" + tmpl = newstyle_i18n_env.get_template("stringformat.html") + assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5" def test_newstyle_plural(self): - tmpl = newstyle_i18n_env.get_template("ngettext.html") - assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apfel" - assert tmpl.render(LANGUAGE="de", apples=5) == "5 Äpfel" + tmpl = newstyle_i18n_env.get_template("ngettext.html") + assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apfel" + assert tmpl.render(LANGUAGE="de", apples=5) == "5 Äpfel" def test_autoescape_support(self): - env = Environment(extensions=["jinja2.ext.i18n"]) + env = Environment(extensions=["jinja2.ext.i18n"]) env.install_gettext_callables( - lambda x: "<strong>Wert: %(name)s</strong>", - lambda s, p, n: s, - newstyle=True, - ) - t = env.from_string( - '{% autoescape ae %}{{ gettext("foo", name=' - '"<test>") }}{% endautoescape %}' - ) - assert t.render(ae=True) == "<strong>Wert: <test></strong>" - assert t.render(ae=False) == "<strong>Wert: <test></strong>" + lambda x: "<strong>Wert: %(name)s</strong>", + lambda s, p, n: s, + newstyle=True, + ) + t = env.from_string( + '{% autoescape ae %}{{ gettext("foo", name=' + '"<test>") }}{% endautoescape %}' + ) + assert t.render(ae=True) == "<strong>Wert: <test></strong>" + assert t.render(ae=False) == "<strong>Wert: <test></strong>" def test_autoescape_macros(self): - env = Environment(autoescape=False) + env = Environment(autoescape=False) template = ( - "{% macro m() %}<html>{% endmacro %}" - "{% autoescape true %}{{ m() }}{% endautoescape %}" + "{% macro m() %}<html>{% endmacro %}" + "{% autoescape true %}{{ m() }}{% endautoescape %}" ) - assert env.from_string(template).render() == "<html>" + assert env.from_string(template).render() == "<html>" def test_num_used_twice(self): - tmpl = newstyle_i18n_env.get_template("ngettext_long.html") - assert tmpl.render(apples=5, LANGUAGE="de") == "5 Äpfel" + tmpl = newstyle_i18n_env.get_template("ngettext_long.html") + assert tmpl.render(apples=5, LANGUAGE="de") == "5 Äpfel" def test_num_called_num(self): - source = newstyle_i18n_env.compile( - """ + source = newstyle_i18n_env.compile( + """ {% trans num=3 %}{{ num }} apple{% pluralize %}{{ num }} apples{% endtrans %} - """, - raw=True, - ) + """, + raw=True, + ) # quite hacky, but the only way to properly test that. The idea is # that the generated code does not pass num twice (although that # would work) for better performance. This only works on the # newstyle gettext of course - assert ( - re.search(r"u?'%\(num\)s apple', u?'%\(num\)s apples', 3", source) - is not None - ) + assert ( + re.search(r"u?'%\(num\)s apple', u?'%\(num\)s apples', 3", source) + is not None + ) def test_trans_vars(self): - t1 = newstyle_i18n_env.get_template("transvars1.html") - t2 = newstyle_i18n_env.get_template("transvars2.html") - t3 = newstyle_i18n_env.get_template("transvars3.html") - assert t1.render(num=1, LANGUAGE="de") == "Benutzer: 1" - assert t2.render(count=23, LANGUAGE="de") == "Benutzer: 23" - assert t3.render(num=42, LANGUAGE="de") == "Benutzer: 42" + t1 = newstyle_i18n_env.get_template("transvars1.html") + t2 = newstyle_i18n_env.get_template("transvars2.html") + t3 = newstyle_i18n_env.get_template("transvars3.html") + assert t1.render(num=1, LANGUAGE="de") == "Benutzer: 1" + assert t2.render(count=23, LANGUAGE="de") == "Benutzer: 23" + assert t3.render(num=42, LANGUAGE="de") == "Benutzer: 42" def test_novars_vars_escaping(self): - t = newstyle_i18n_env.get_template("novars.html") - assert t.render() == "%(hello)s" - t = newstyle_i18n_env.get_template("vars.html") - assert t.render(foo="42") == "42%(foo)s" - t = newstyle_i18n_env.get_template("explicitvars.html") - assert t.render() == "%(foo)s" - - def test_context(self): - tmpl = newstyle_i18n_env.get_template("pgettext.html") - assert tmpl.render(LANGUAGE="de") == "Apple" - - def test_context_newstyle_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" - - -class TestAutoEscape: + t = newstyle_i18n_env.get_template("novars.html") + assert t.render() == "%(hello)s" + t = newstyle_i18n_env.get_template("vars.html") + assert t.render(foo="42") == "42%(foo)s" + t = newstyle_i18n_env.get_template("explicitvars.html") + assert t.render() == "%(foo)s" + + def test_context(self): + tmpl = newstyle_i18n_env.get_template("pgettext.html") + assert tmpl.render(LANGUAGE="de") == "Apple" + + def test_context_newstyle_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" + + +class TestAutoEscape: def test_scoped_setting(self): - env = Environment(autoescape=True) - tmpl = env.from_string( - """ + env = Environment(autoescape=True) + tmpl = env.from_string( + """ {{ "<HelloWorld>" }} {% autoescape false %} {{ "<HelloWorld>" }} {% endautoescape %} {{ "<HelloWorld>" }} - """ - ) - assert tmpl.render().split() == [ - "<HelloWorld>", - "<HelloWorld>", - "<HelloWorld>", - ] - - env = Environment(autoescape=False) - tmpl = env.from_string( - """ + """ + ) + assert tmpl.render().split() == [ + "<HelloWorld>", + "<HelloWorld>", + "<HelloWorld>", + ] + + env = Environment(autoescape=False) + tmpl = env.from_string( + """ {{ "<HelloWorld>" }} {% autoescape true %} {{ "<HelloWorld>" }} {% endautoescape %} {{ "<HelloWorld>" }} - """ - ) - assert tmpl.render().split() == [ - "<HelloWorld>", - "<HelloWorld>", - "<HelloWorld>", - ] + """ + ) + assert tmpl.render().split() == [ + "<HelloWorld>", + "<HelloWorld>", + "<HelloWorld>", + ] def test_nonvolatile(self): - env = Environment(autoescape=True) + env = Environment(autoescape=True) tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}') assert tmpl.render() == ' foo="<test>"' - tmpl = env.from_string( - '{% autoescape false %}{{ {"foo": "<test>"}' - "|xmlattr|escape }}{% endautoescape %}" - ) - assert tmpl.render() == " foo="&lt;test&gt;"" + tmpl = env.from_string( + '{% autoescape false %}{{ {"foo": "<test>"}' + "|xmlattr|escape }}{% endautoescape %}" + ) + assert tmpl.render() == " foo="&lt;test&gt;"" def test_volatile(self): - env = Environment(autoescape=True) - tmpl = env.from_string( - '{% autoescape foo %}{{ {"foo": "<test>"}' - "|xmlattr|escape }}{% endautoescape %}" - ) - assert tmpl.render(foo=False) == " foo="&lt;test&gt;"" + env = Environment(autoescape=True) + tmpl = env.from_string( + '{% autoescape foo %}{{ {"foo": "<test>"}' + "|xmlattr|escape }}{% endautoescape %}" + ) + assert tmpl.render(foo=False) == " foo="&lt;test&gt;"" assert tmpl.render(foo=True) == ' foo="<test>"' def test_scoping(self): - env = Environment() + env = Environment() tmpl = env.from_string( '{% autoescape true %}{% set x = "<x>" %}{{ x }}' - '{% endautoescape %}{{ x }}{{ "<y>" }}' - ) - assert tmpl.render(x=1) == "<x>1<y>" + '{% endautoescape %}{{ x }}{{ "<y>" }}' + ) + assert tmpl.render(x=1) == "<x>1<y>" def test_volatile_scoping(self): - env = Environment() - tmplsource = """ + env = Environment() + tmplsource = """ {% autoescape val %} {% macro foo(x) %} [{{ x }}] @@ -670,45 +670,45 @@ class TestAutoEscape: {{ foo().__class__.__name__ }} {% endautoescape %} {{ '<testing>' }} - """ + """ tmpl = env.from_string(tmplsource) - assert tmpl.render(val=True).split()[0] == "Markup" - assert tmpl.render(val=False).split()[0] == "str" + assert tmpl.render(val=True).split()[0] == "Markup" + assert tmpl.render(val=False).split()[0] == "str" # looking at the source we should see <testing> there in raw # (and then escaped as well) - env = Environment() + env = Environment() pysource = env.compile(tmplsource, raw=True) - assert "<testing>\\n" in pysource + assert "<testing>\\n" in pysource - env = Environment(autoescape=True) + env = Environment(autoescape=True) pysource = env.compile(tmplsource, raw=True) - assert "<testing>\\n" in pysource + assert "<testing>\\n" in pysource def test_overlay_scopes(self): class MagicScopeExtension(Extension): - tags = {"overlay"} - + tags = {"overlay"} + def parse(self, parser): node = nodes.OverlayScope(lineno=next(parser.stream).lineno) - node.body = list( - parser.parse_statements(("name:endoverlay",), drop_needle=True) - ) - node.context = self.call_method("get_scope") + node.body = list( + parser.parse_statements(("name:endoverlay",), drop_needle=True) + ) + node.context = self.call_method("get_scope") return node - + def get_scope(self): - return {"x": [1, 2, 3]} + return {"x": [1, 2, 3]} env = Environment(extensions=[MagicScopeExtension]) - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {{- x }}|{% set z = 99 %} {%- overlay %} {{- y }}|{{ z }}|{% for item in x %}[{{ item }}]{% endfor %} {%- endoverlay %}| {{- x -}} - """ - ) - assert tmpl.render(x=42, y=23) == "42|23|99|[1][2][3]|42" + """ + ) + assert tmpl.render(x=42, y=23) == "42|23|99|[1][2][3]|42" diff --git a/contrib/python/Jinja2/py3/tests/test_features.py b/contrib/python/Jinja2/py3/tests/test_features.py index 4f36458a7f..c8184c1592 100644 --- a/contrib/python/Jinja2/py3/tests/test_features.py +++ b/contrib/python/Jinja2/py3/tests/test_features.py @@ -1,14 +1,14 @@ import pytest -from jinja2 import Template +from jinja2 import Template -# Python < 3.7 +# Python < 3.7 def test_generator_stop(): - class X: + class X: def __getattr__(self, name): raise StopIteration() - t = Template("a{{ bad.bar() }}b") + t = Template("a{{ bad.bar() }}b") with pytest.raises(RuntimeError): t.render(bad=X()) diff --git a/contrib/python/Jinja2/py3/tests/test_filters.py b/contrib/python/Jinja2/py3/tests/test_filters.py index 2195157c4f..25f0aee892 100644 --- a/contrib/python/Jinja2/py3/tests/test_filters.py +++ b/contrib/python/Jinja2/py3/tests/test_filters.py @@ -1,260 +1,260 @@ -import random -from collections import namedtuple +import random +from collections import namedtuple import pytest -from markupsafe import Markup +from markupsafe import Markup -from jinja2 import Environment -from jinja2 import StrictUndefined -from jinja2 import TemplateRuntimeError -from jinja2 import UndefinedError -from jinja2.exceptions import TemplateAssertionError +from jinja2 import Environment +from jinja2 import StrictUndefined +from jinja2 import TemplateRuntimeError +from jinja2 import UndefinedError +from jinja2.exceptions import TemplateAssertionError - -class Magic: + +class Magic: def __init__(self, value): self.value = value def __str__(self): - return str(self.value) - - -class Magic2: - def __init__(self, value1, value2): - self.value1 = value1 - self.value2 = value2 - - def __str__(self): - return f"({self.value1},{self.value2})" - - -class TestFilter: + return str(self.value) + + +class Magic2: + def __init__(self, value1, value2): + self.value1 = value1 + self.value2 = value2 + + def __str__(self): + return f"({self.value1},{self.value2})" + + +class TestFilter: def test_filter_calling(self, env): - rv = env.call_filter("sum", [1, 2, 3]) + rv = env.call_filter("sum", [1, 2, 3]) assert rv == 6 def test_capitalize(self, env): tmpl = env.from_string('{{ "foo bar"|capitalize }}') - assert tmpl.render() == "Foo bar" + assert tmpl.render() == "Foo bar" def test_center(self, env): tmpl = env.from_string('{{ "foo"|center(9) }}') - assert tmpl.render() == " foo " + assert tmpl.render() == " foo " def test_default(self, env): tmpl = env.from_string( "{{ missing|default('no') }}|{{ false|default('no') }}|" "{{ false|default('no', true) }}|{{ given|default('no') }}" ) - assert tmpl.render(given="yes") == "no|False|no|yes" - - @pytest.mark.parametrize( - "args,expect", - ( - ("", "[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]"), - ("true", "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]"), - ('by="value"', "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]"), - ("reverse=true", "[('c', 2), ('b', 1), ('AB', 3), ('aa', 0)]"), - ), - ) + assert tmpl.render(given="yes") == "no|False|no|yes" + + @pytest.mark.parametrize( + "args,expect", + ( + ("", "[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]"), + ("true", "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]"), + ('by="value"', "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]"), + ("reverse=true", "[('c', 2), ('b', 1), ('AB', 3), ('aa', 0)]"), + ), + ) def test_dictsort(self, env, args, expect): - t = env.from_string(f"{{{{ foo|dictsort({args}) }}}}") + t = env.from_string(f"{{{{ foo|dictsort({args}) }}}}") out = t.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3}) assert out == expect def test_batch(self, env): - tmpl = env.from_string("{{ foo|batch(3)|list }}|{{ foo|batch(3, 'X')|list }}") + tmpl = env.from_string("{{ foo|batch(3)|list }}|{{ foo|batch(3, 'X')|list }}") out = tmpl.render(foo=list(range(10))) - assert out == ( - "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|" - "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]" - ) + assert out == ( + "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|" + "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]" + ) def test_slice(self, env): - tmpl = env.from_string("{{ foo|slice(3)|list }}|{{ foo|slice(3, 'X')|list }}") + tmpl = env.from_string("{{ foo|slice(3)|list }}|{{ foo|slice(3, 'X')|list }}") out = tmpl.render(foo=list(range(10))) - assert out == ( - "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|" - "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]" - ) + assert out == ( + "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|" + "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]" + ) def test_escape(self, env): - tmpl = env.from_string("""{{ '<">&'|escape }}""") + tmpl = env.from_string("""{{ '<">&'|escape }}""") out = tmpl.render() - assert out == "<">&" - - @pytest.mark.parametrize( - ("chars", "expect"), [(None, "..stays.."), (".", " ..stays"), (" .", "stays")] - ) - def test_trim(self, env, chars, expect): - tmpl = env.from_string("{{ foo|trim(chars) }}") - out = tmpl.render(foo=" ..stays..", chars=chars) - assert out == expect - + assert out == "<">&" + + @pytest.mark.parametrize( + ("chars", "expect"), [(None, "..stays.."), (".", " ..stays"), (" .", "stays")] + ) + def test_trim(self, env, chars, expect): + tmpl = env.from_string("{{ foo|trim(chars) }}") + out = tmpl.render(foo=" ..stays..", chars=chars) + assert out == expect + def test_striptags(self, env): - tmpl = env.from_string("""{{ foo|striptags }}""") - out = tmpl.render( - foo=' <p>just a small \n <a href="#">' - "example</a> link</p>\n<p>to a webpage</p> " - "<!-- <p>and some commented stuff</p> -->" - ) - assert out == "just a small example link to a webpage" + tmpl = env.from_string("""{{ foo|striptags }}""") + out = tmpl.render( + foo=' <p>just a small \n <a href="#">' + "example</a> link</p>\n<p>to a webpage</p> " + "<!-- <p>and some commented stuff</p> -->" + ) + assert out == "just a small example link to a webpage" def test_filesizeformat(self, env): tmpl = env.from_string( - "{{ 100|filesizeformat }}|" - "{{ 1000|filesizeformat }}|" - "{{ 1000000|filesizeformat }}|" - "{{ 1000000000|filesizeformat }}|" - "{{ 1000000000000|filesizeformat }}|" - "{{ 100|filesizeformat(true) }}|" - "{{ 1000|filesizeformat(true) }}|" - "{{ 1000000|filesizeformat(true) }}|" - "{{ 1000000000|filesizeformat(true) }}|" - "{{ 1000000000000|filesizeformat(true) }}" + "{{ 100|filesizeformat }}|" + "{{ 1000|filesizeformat }}|" + "{{ 1000000|filesizeformat }}|" + "{{ 1000000000|filesizeformat }}|" + "{{ 1000000000000|filesizeformat }}|" + "{{ 100|filesizeformat(true) }}|" + "{{ 1000|filesizeformat(true) }}|" + "{{ 1000000|filesizeformat(true) }}|" + "{{ 1000000000|filesizeformat(true) }}|" + "{{ 1000000000000|filesizeformat(true) }}" ) out = tmpl.render() assert out == ( - "100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|" - "1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB" + "100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|" + "1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB" ) def test_filesizeformat_issue59(self, env): tmpl = env.from_string( - "{{ 300|filesizeformat }}|" - "{{ 3000|filesizeformat }}|" - "{{ 3000000|filesizeformat }}|" - "{{ 3000000000|filesizeformat }}|" - "{{ 3000000000000|filesizeformat }}|" - "{{ 300|filesizeformat(true) }}|" - "{{ 3000|filesizeformat(true) }}|" - "{{ 3000000|filesizeformat(true) }}" + "{{ 300|filesizeformat }}|" + "{{ 3000|filesizeformat }}|" + "{{ 3000000|filesizeformat }}|" + "{{ 3000000000|filesizeformat }}|" + "{{ 3000000000000|filesizeformat }}|" + "{{ 300|filesizeformat(true) }}|" + "{{ 3000|filesizeformat(true) }}|" + "{{ 3000000|filesizeformat(true) }}" ) out = tmpl.render() assert out == ( - "300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|2.9 KiB|2.9 MiB" + "300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|2.9 KiB|2.9 MiB" ) def test_first(self, env): - tmpl = env.from_string("{{ foo|first }}") + tmpl = env.from_string("{{ foo|first }}") out = tmpl.render(foo=list(range(10))) - assert out == "0" - - @pytest.mark.parametrize( - ("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32")) - ) - def test_float(self, env, value, expect): - t = env.from_string("{{ value|float }}") - assert t.render(value=value) == expect - - def test_float_default(self, env): - t = env.from_string("{{ value|float(default=1.0) }}") - assert t.render(value="abc") == "1.0" - + assert out == "0" + + @pytest.mark.parametrize( + ("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32")) + ) + def test_float(self, env, value, expect): + t = env.from_string("{{ value|float }}") + assert t.render(value=value) == expect + + def test_float_default(self, env): + t = env.from_string("{{ value|float(default=1.0) }}") + assert t.render(value="abc") == "1.0" + def test_format(self, env): - tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}") + tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}") out = tmpl.render() - assert out == "a|b" - - @staticmethod - def _test_indent_multiline_template(env, markup=False): - text = "\n".join(["", "foo bar", '"baz"', ""]) - if markup: - text = Markup(text) - t = env.from_string("{{ foo|indent(2, false, false) }}") - assert t.render(foo=text) == '\n foo bar\n "baz"\n' - t = env.from_string("{{ foo|indent(2, false, true) }}") - assert t.render(foo=text) == '\n foo bar\n "baz"\n ' - t = env.from_string("{{ foo|indent(2, true, false) }}") - assert t.render(foo=text) == ' \n foo bar\n "baz"\n' - t = env.from_string("{{ foo|indent(2, true, true) }}") - assert t.render(foo=text) == ' \n foo bar\n "baz"\n ' - + assert out == "a|b" + + @staticmethod + def _test_indent_multiline_template(env, markup=False): + text = "\n".join(["", "foo bar", '"baz"', ""]) + if markup: + text = Markup(text) + t = env.from_string("{{ foo|indent(2, false, false) }}") + assert t.render(foo=text) == '\n foo bar\n "baz"\n' + t = env.from_string("{{ foo|indent(2, false, true) }}") + assert t.render(foo=text) == '\n foo bar\n "baz"\n ' + t = env.from_string("{{ foo|indent(2, true, false) }}") + assert t.render(foo=text) == ' \n foo bar\n "baz"\n' + t = env.from_string("{{ foo|indent(2, true, true) }}") + assert t.render(foo=text) == ' \n foo bar\n "baz"\n ' + def test_indent(self, env): - self._test_indent_multiline_template(env) + self._test_indent_multiline_template(env) t = env.from_string('{{ "jinja"|indent }}') - assert t.render() == "jinja" + assert t.render() == "jinja" t = env.from_string('{{ "jinja"|indent(first=true) }}') - assert t.render() == " jinja" + assert t.render() == " jinja" t = env.from_string('{{ "jinja"|indent(blank=true) }}') - assert t.render() == "jinja" - - def test_indent_markup_input(self, env): - """ - Tests cases where the filter input is a Markup type - """ - self._test_indent_multiline_template(env, markup=True) - - def test_indent_width_string(self, env): - t = env.from_string("{{ 'jinja\nflask'|indent(width='>>> ', first=True) }}") - assert t.render() == ">>> jinja\n>>> flask" - - @pytest.mark.parametrize( - ("value", "expect"), - ( - ("42", "42"), - ("abc", "0"), - ("32.32", "32"), - ("12345678901234567890", "12345678901234567890"), - ), - ) - def test_int(self, env, value, expect): - t = env.from_string("{{ value|int }}") - assert t.render(value=value) == expect - - @pytest.mark.parametrize( - ("value", "base", "expect"), - (("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0")), - ) - def test_int_base(self, env, value, base, expect): - t = env.from_string("{{ value|int(base=base) }}") - assert t.render(value=value, base=base) == expect - - def test_int_default(self, env): - t = env.from_string("{{ value|int(default=1) }}") - assert t.render(value="abc") == "1" - - def test_int_special_method(self, env): - class IntIsh: + assert t.render() == "jinja" + + def test_indent_markup_input(self, env): + """ + Tests cases where the filter input is a Markup type + """ + self._test_indent_multiline_template(env, markup=True) + + def test_indent_width_string(self, env): + t = env.from_string("{{ 'jinja\nflask'|indent(width='>>> ', first=True) }}") + assert t.render() == ">>> jinja\n>>> flask" + + @pytest.mark.parametrize( + ("value", "expect"), + ( + ("42", "42"), + ("abc", "0"), + ("32.32", "32"), + ("12345678901234567890", "12345678901234567890"), + ), + ) + def test_int(self, env, value, expect): + t = env.from_string("{{ value|int }}") + assert t.render(value=value) == expect + + @pytest.mark.parametrize( + ("value", "base", "expect"), + (("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0")), + ) + def test_int_base(self, env, value, base, expect): + t = env.from_string("{{ value|int(base=base) }}") + assert t.render(value=value, base=base) == expect + + def test_int_default(self, env): + t = env.from_string("{{ value|int(default=1) }}") + assert t.render(value="abc") == "1" + + def test_int_special_method(self, env): + class IntIsh: def __int__(self): return 42 - t = env.from_string("{{ value|int }}") - assert t.render(value=IntIsh()) == "42" + t = env.from_string("{{ value|int }}") + assert t.render(value=IntIsh()) == "42" def test_join(self, env): tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}') out = tmpl.render() - assert out == "1|2|3" + assert out == "1|2|3" env2 = Environment(autoescape=True) - tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}') - assert tmpl.render() == "<foo><span>foo</span>" + tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}') + assert tmpl.render() == "<foo><span>foo</span>" def test_join_attribute(self, env): - User = namedtuple("User", "username") - tmpl = env.from_string("""{{ users|join(', ', 'username') }}""") - assert tmpl.render(users=map(User, ["foo", "bar"])) == "foo, bar" + User = namedtuple("User", "username") + tmpl = env.from_string("""{{ users|join(', ', 'username') }}""") + assert tmpl.render(users=map(User, ["foo", "bar"])) == "foo, bar" def test_last(self, env): - tmpl = env.from_string("""{{ foo|last }}""") + tmpl = env.from_string("""{{ foo|last }}""") out = tmpl.render(foo=list(range(10))) - assert out == "9" + assert out == "9" def test_length(self, env): - tmpl = env.from_string("""{{ "hello world"|length }}""") + tmpl = env.from_string("""{{ "hello world"|length }}""") out = tmpl.render() - assert out == "11" + assert out == "11" def test_lower(self, env): - tmpl = env.from_string("""{{ "FOO"|lower }}""") + tmpl = env.from_string("""{{ "FOO"|lower }}""") out = tmpl.render() - assert out == "foo" + assert out == "foo" def test_pprint(self, env): from pprint import pformat - - tmpl = env.from_string("""{{ data|pprint }}""") + + tmpl = env.from_string("""{{ data|pprint }}""") data = list(range(1000)) assert tmpl.render(data=data) == pformat(data) @@ -263,200 +263,200 @@ class TestFilter: state = random.getstate() request.addfinalizer(lambda: random.setstate(state)) # generate the random values from a known seed - random.seed("jinja") - expected = [random.choice("1234567890") for _ in range(10)] + random.seed("jinja") + expected = [random.choice("1234567890") for _ in range(10)] # check that the random sequence is generated again by a template # ensures that filter result is not constant folded - random.seed("jinja") + random.seed("jinja") t = env.from_string('{{ "1234567890"|random }}') for value in expected: assert t.render() == value def test_reverse(self, env): - tmpl = env.from_string( - "{{ 'foobar'|reverse|join }}|{{ [1, 2, 3]|reverse|list }}" - ) - assert tmpl.render() == "raboof|[3, 2, 1]" + tmpl = env.from_string( + "{{ 'foobar'|reverse|join }}|{{ [1, 2, 3]|reverse|list }}" + ) + assert tmpl.render() == "raboof|[3, 2, 1]" def test_string(self, env): x = [1, 2, 3, 4, 5] - tmpl = env.from_string("""{{ obj|string }}""") - assert tmpl.render(obj=x) == str(x) + tmpl = env.from_string("""{{ obj|string }}""") + assert tmpl.render(obj=x) == str(x) def test_title(self, env): - tmpl = env.from_string("""{{ "foo bar"|title }}""") + tmpl = env.from_string("""{{ "foo bar"|title }}""") assert tmpl.render() == "Foo Bar" - tmpl = env.from_string("""{{ "foo's bar"|title }}""") + tmpl = env.from_string("""{{ "foo's bar"|title }}""") assert tmpl.render() == "Foo's Bar" - tmpl = env.from_string("""{{ "foo bar"|title }}""") + tmpl = env.from_string("""{{ "foo bar"|title }}""") assert tmpl.render() == "Foo Bar" - tmpl = env.from_string("""{{ "f bar f"|title }}""") + tmpl = env.from_string("""{{ "f bar f"|title }}""") assert tmpl.render() == "F Bar F" - tmpl = env.from_string("""{{ "foo-bar"|title }}""") + tmpl = env.from_string("""{{ "foo-bar"|title }}""") assert tmpl.render() == "Foo-Bar" - tmpl = env.from_string("""{{ "foo\tbar"|title }}""") + tmpl = env.from_string("""{{ "foo\tbar"|title }}""") assert tmpl.render() == "Foo\tBar" - tmpl = env.from_string("""{{ "FOO\tBAR"|title }}""") + tmpl = env.from_string("""{{ "FOO\tBAR"|title }}""") assert tmpl.render() == "Foo\tBar" - tmpl = env.from_string("""{{ "foo (bar)"|title }}""") + tmpl = env.from_string("""{{ "foo (bar)"|title }}""") assert tmpl.render() == "Foo (Bar)" - tmpl = env.from_string("""{{ "foo {bar}"|title }}""") + tmpl = env.from_string("""{{ "foo {bar}"|title }}""") assert tmpl.render() == "Foo {Bar}" - tmpl = env.from_string("""{{ "foo [bar]"|title }}""") + tmpl = env.from_string("""{{ "foo [bar]"|title }}""") assert tmpl.render() == "Foo [Bar]" - tmpl = env.from_string("""{{ "foo <bar>"|title }}""") + tmpl = env.from_string("""{{ "foo <bar>"|title }}""") assert tmpl.render() == "Foo <Bar>" class Foo: def __str__(self): - return "foo-bar" + return "foo-bar" - tmpl = env.from_string("""{{ data|title }}""") + tmpl = env.from_string("""{{ data|title }}""") out = tmpl.render(data=Foo()) - assert out == "Foo-Bar" + assert out == "Foo-Bar" def test_truncate(self, env): tmpl = env.from_string( '{{ data|truncate(15, true, ">>>") }}|' '{{ data|truncate(15, false, ">>>") }}|' - "{{ smalldata|truncate(15) }}" + "{{ smalldata|truncate(15) }}" ) - out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar") - assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar" + out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar") + assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar" def test_truncate_very_short(self, env): tmpl = env.from_string( - '{{ "foo bar baz"|truncate(9) }}|{{ "foo bar baz"|truncate(9, true) }}' + '{{ "foo bar baz"|truncate(9) }}|{{ "foo bar baz"|truncate(9, true) }}' ) out = tmpl.render() - assert out == "foo bar baz|foo bar baz" + assert out == "foo bar baz|foo bar baz" def test_truncate_end_length(self, env): tmpl = env.from_string('{{ "Joel is a slug"|truncate(7, true) }}') out = tmpl.render() - assert out == "Joel..." + assert out == "Joel..." def test_upper(self, env): tmpl = env.from_string('{{ "foo"|upper }}') - assert tmpl.render() == "FOO" + assert tmpl.render() == "FOO" def test_urlize(self, env): - tmpl = env.from_string('{{ "foo example.org bar"|urlize }}') - assert tmpl.render() == ( - 'foo <a href="https://example.org" rel="noopener">' "example.org</a> bar" - ) - tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}') + tmpl = env.from_string('{{ "foo example.org bar"|urlize }}') + assert tmpl.render() == ( + 'foo <a href="https://example.org" rel="noopener">' "example.org</a> bar" + ) + tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}') assert tmpl.render() == ( 'foo <a href="http://www.example.com/" rel="noopener">' - "http://www.example.com/</a> bar" - ) - tmpl = env.from_string('{{ "foo mailto:email@example.com bar"|urlize }}') - assert tmpl.render() == ( - 'foo <a href="mailto:email@example.com">email@example.com</a> bar' - ) - tmpl = env.from_string('{{ "foo email@example.com bar"|urlize }}') - assert tmpl.render() == ( - 'foo <a href="mailto:email@example.com">email@example.com</a> bar' + "http://www.example.com/</a> bar" ) + tmpl = env.from_string('{{ "foo mailto:email@example.com bar"|urlize }}') + assert tmpl.render() == ( + 'foo <a href="mailto:email@example.com">email@example.com</a> bar' + ) + tmpl = env.from_string('{{ "foo email@example.com bar"|urlize }}') + assert tmpl.render() == ( + 'foo <a href="mailto:email@example.com">email@example.com</a> bar' + ) def test_urlize_rel_policy(self): env = Environment() - env.policies["urlize.rel"] = None - tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}') + env.policies["urlize.rel"] = None + tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}') assert tmpl.render() == ( - 'foo <a href="http://www.example.com/">http://www.example.com/</a> bar' + 'foo <a href="http://www.example.com/">http://www.example.com/</a> bar' ) def test_urlize_target_parameter(self, env): tmpl = env.from_string( '{{ "foo http://www.example.com/ bar"|urlize(target="_blank") }}' ) - assert ( - tmpl.render() - == 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">' - "http://www.example.com/</a> bar" - ) - - def test_urlize_extra_schemes_parameter(self, env): - tmpl = env.from_string( - '{{ "foo tel:+1-514-555-1234 ftp://localhost bar"|' - 'urlize(extra_schemes=["tel:", "ftp:"]) }}' - ) - assert tmpl.render() == ( - 'foo <a href="tel:+1-514-555-1234" rel="noopener">' - 'tel:+1-514-555-1234</a> <a href="ftp://localhost" rel="noopener">' - "ftp://localhost</a> bar" - ) - + assert ( + tmpl.render() + == 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">' + "http://www.example.com/</a> bar" + ) + + def test_urlize_extra_schemes_parameter(self, env): + tmpl = env.from_string( + '{{ "foo tel:+1-514-555-1234 ftp://localhost bar"|' + 'urlize(extra_schemes=["tel:", "ftp:"]) }}' + ) + assert tmpl.render() == ( + 'foo <a href="tel:+1-514-555-1234" rel="noopener">' + 'tel:+1-514-555-1234</a> <a href="ftp://localhost" rel="noopener">' + "ftp://localhost</a> bar" + ) + def test_wordcount(self, env): tmpl = env.from_string('{{ "foo bar baz"|wordcount }}') - assert tmpl.render() == "3" - - strict_env = Environment(undefined=StrictUndefined) - t = strict_env.from_string("{{ s|wordcount }}") - with pytest.raises(UndefinedError): - t.render() + assert tmpl.render() == "3" + strict_env = Environment(undefined=StrictUndefined) + t = strict_env.from_string("{{ s|wordcount }}") + with pytest.raises(UndefinedError): + t.render() + def test_block(self, env): - tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}") - assert tmpl.render() == "<hehe>" + tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}") + assert tmpl.render() == "<hehe>" def test_chaining(self, env): - tmpl = env.from_string("""{{ ['<foo>', '<bar>']|first|upper|escape }}""") - assert tmpl.render() == "<FOO>" + tmpl = env.from_string("""{{ ['<foo>', '<bar>']|first|upper|escape }}""") + assert tmpl.render() == "<FOO>" def test_sum(self, env): - tmpl = env.from_string("""{{ [1, 2, 3, 4, 5, 6]|sum }}""") - assert tmpl.render() == "21" + tmpl = env.from_string("""{{ [1, 2, 3, 4, 5, 6]|sum }}""") + assert tmpl.render() == "21" def test_sum_attributes(self, env): - tmpl = env.from_string("""{{ values|sum('value') }}""") - assert tmpl.render(values=[{"value": 23}, {"value": 1}, {"value": 18}]) == "42" + tmpl = env.from_string("""{{ values|sum('value') }}""") + assert tmpl.render(values=[{"value": 23}, {"value": 1}, {"value": 18}]) == "42" def test_sum_attributes_nested(self, env): - tmpl = env.from_string("""{{ values|sum('real.value') }}""") - assert ( - tmpl.render( - values=[ - {"real": {"value": 23}}, - {"real": {"value": 1}}, - {"real": {"value": 18}}, - ] - ) - == "42" - ) + tmpl = env.from_string("""{{ values|sum('real.value') }}""") + assert ( + tmpl.render( + values=[ + {"real": {"value": 23}}, + {"real": {"value": 1}}, + {"real": {"value": 18}}, + ] + ) + == "42" + ) def test_sum_attributes_tuple(self, env): - tmpl = env.from_string("""{{ values.items()|sum('1') }}""") - assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42" + tmpl = env.from_string("""{{ values.items()|sum('1') }}""") + assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42" def test_abs(self, env): - tmpl = env.from_string("""{{ -1|abs }}|{{ 1|abs }}""") - assert tmpl.render() == "1|1", tmpl.render() + tmpl = env.from_string("""{{ -1|abs }}|{{ 1|abs }}""") + assert tmpl.render() == "1|1", tmpl.render() def test_round_positive(self, env): - tmpl = env.from_string( - "{{ 2.7|round }}|{{ 2.1|round }}|" - "{{ 2.1234|round(3, 'floor') }}|" - "{{ 2.1|round(0, 'ceil') }}" - ) - assert tmpl.render() == "3.0|2.0|2.123|3.0", tmpl.render() + tmpl = env.from_string( + "{{ 2.7|round }}|{{ 2.1|round }}|" + "{{ 2.1234|round(3, 'floor') }}|" + "{{ 2.1|round(0, 'ceil') }}" + ) + assert tmpl.render() == "3.0|2.0|2.123|3.0", tmpl.render() def test_round_negative(self, env): - tmpl = env.from_string( - "{{ 21.3|round(-1)}}|" - "{{ 21.3|round(-1, 'ceil')}}|" - "{{ 21.3|round(-1, 'floor')}}" - ) - assert tmpl.render() == "20.0|30.0|20.0", tmpl.render() + tmpl = env.from_string( + "{{ 21.3|round(-1)}}|" + "{{ 21.3|round(-1, 'ceil')}}|" + "{{ 21.3|round(-1, 'floor')}}" + ) + assert tmpl.render() == "20.0|30.0|20.0", tmpl.render() def test_xmlattr(self, env): tmpl = env.from_string( "{{ {'foo': 42, 'bar': 23, 'fish': none, " - "'spam': missing, 'blub:blub': '<?>'}|xmlattr }}" - ) + "'spam': missing, 'blub:blub': '<?>'}|xmlattr }}" + ) out = tmpl.render().split() assert len(out) == 3 assert 'foo="42"' in out @@ -464,61 +464,61 @@ class TestFilter: assert 'blub:blub="<?>"' in out def test_sort1(self, env): - tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}") - assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]" + tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}") + assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]" def test_sort2(self, env): tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}') - assert tmpl.render() == "AbcD" + assert tmpl.render() == "AbcD" def test_sort3(self, env): - tmpl = env.from_string("""{{ ['foo', 'Bar', 'blah']|sort }}""") + tmpl = env.from_string("""{{ ['foo', 'Bar', 'blah']|sort }}""") assert tmpl.render() == "['Bar', 'blah', 'foo']" def test_sort4(self, env): - 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])" - ) - + 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" @@ -529,315 +529,315 @@ class TestFilter: def test_unique_attribute(self, env): t = env.from_string("{{ items|unique(attribute='value')|join }}") - assert t.render(items=map(Magic, [3, 2, 4, 1, 2])) == "3241" - - @pytest.mark.parametrize( - "source,expect", - ( - ('{{ ["a", "B"]|min }}', "a"), - ('{{ ["a", "B"]|min(case_sensitive=true) }}', "B"), - ("{{ []|min }}", ""), - ('{{ ["a", "B"]|max }}', "B"), - ('{{ ["a", "B"]|max(case_sensitive=true) }}', "a"), - ("{{ []|max }}", ""), - ), - ) + assert t.render(items=map(Magic, [3, 2, 4, 1, 2])) == "3241" + + @pytest.mark.parametrize( + "source,expect", + ( + ('{{ ["a", "B"]|min }}', "a"), + ('{{ ["a", "B"]|min(case_sensitive=true) }}', "B"), + ("{{ []|min }}", ""), + ('{{ ["a", "B"]|max }}', "B"), + ('{{ ["a", "B"]|max(case_sensitive=true) }}', "a"), + ("{{ []|max }}", ""), + ), + ) def test_min_max(self, env, source, expect): t = env.from_string(source) assert t.render() == expect - @pytest.mark.parametrize(("name", "expect"), [("min", "1"), ("max", "9")]) + @pytest.mark.parametrize(("name", "expect"), [("min", "1"), ("max", "9")]) def test_min_max_attribute(self, env, name, expect): - t = env.from_string("{{ items|" + name + '(attribute="value") }}') + t = env.from_string("{{ items|" + name + '(attribute="value") }}') assert t.render(items=map(Magic, [5, 1, 9])) == expect def test_groupby(self, env): - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {%- for grouper, list in [{'foo': 1, 'bar': 2}, {'foo': 2, 'bar': 3}, {'foo': 1, 'bar': 1}, {'foo': 3, 'bar': 4}]|groupby('foo') -%} {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}| - {%- endfor %}""" - ) - assert tmpl.render().split("|") == ["1: 1, 2: 1, 1", "2: 2, 3", "3: 3, 4", ""] + {%- endfor %}""" + ) + assert tmpl.render().split("|") == ["1: 1, 2: 1, 1", "2: 2, 3", "3: 3, 4", ""] def test_groupby_tuple_index(self, env): - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%} {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}| - {%- endfor %}""" - ) - assert tmpl.render() == "a:1:2|b:1|" + {%- endfor %}""" + ) + assert tmpl.render() == "a:1:2|b:1|" def test_groupby_multidot(self, env): - Date = namedtuple("Date", "day,month,year") - Article = namedtuple("Article", "title,date") + Date = namedtuple("Date", "day,month,year") + Article = namedtuple("Article", "title,date") articles = [ - Article("aha", Date(1, 1, 1970)), - Article("interesting", Date(2, 1, 1970)), - Article("really?", Date(3, 1, 1970)), - Article("totally not", Date(1, 1, 1971)), + Article("aha", Date(1, 1, 1970)), + Article("interesting", Date(2, 1, 1970)), + Article("really?", Date(3, 1, 1970)), + Article("totally not", Date(1, 1, 1971)), ] - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {%- for year, list in articles|groupby('date.year') -%} {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}| - {%- endfor %}""" - ) - assert tmpl.render(articles=articles).split("|") == [ - "1970[aha][interesting][really?]", - "1971[totally not]", - "", + {%- endfor %}""" + ) + assert tmpl.render(articles=articles).split("|") == [ + "1970[aha][interesting][really?]", + "1971[totally not]", + "", ] - def test_groupby_default(self, env): - tmpl = env.from_string( - "{% for city, items in users|groupby('city', default='NY') %}" - "{{ city }}: {{ items|map(attribute='name')|join(', ') }}\n" - "{% endfor %}" - ) - out = tmpl.render( - users=[ - {"name": "emma", "city": "NY"}, - {"name": "smith", "city": "WA"}, - {"name": "john"}, - ] - ) - assert out == "NY: emma, john\nWA: smith\n" - + def test_groupby_default(self, env): + tmpl = env.from_string( + "{% for city, items in users|groupby('city', default='NY') %}" + "{{ city }}: {{ items|map(attribute='name')|join(', ') }}\n" + "{% endfor %}" + ) + out = tmpl.render( + users=[ + {"name": "emma", "city": "NY"}, + {"name": "smith", "city": "WA"}, + {"name": "john"}, + ] + ) + assert out == "NY: emma, john\nWA: smith\n" + def test_filtertag(self, env): - tmpl = env.from_string( - "{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}" - ) - assert tmpl.render() == "fooBAR" + tmpl = env.from_string( + "{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}" + ) + assert tmpl.render() == "fooBAR" def test_replace(self, env): env = Environment() tmpl = env.from_string('{{ string|replace("o", 42) }}') - assert tmpl.render(string="<foo>") == "<f4242>" + assert tmpl.render(string="<foo>") == "<f4242>" env = Environment(autoescape=True) tmpl = env.from_string('{{ string|replace("o", 42) }}') - assert tmpl.render(string="<foo>") == "<f4242>" + assert tmpl.render(string="<foo>") == "<f4242>" tmpl = env.from_string('{{ string|replace("<", 42) }}') - assert tmpl.render(string="<foo>") == "42foo>" + assert tmpl.render(string="<foo>") == "42foo>" tmpl = env.from_string('{{ string|replace("o", ">x<") }}') - assert tmpl.render(string=Markup("foo")) == "f>x<>x<" + assert tmpl.render(string=Markup("foo")) == "f>x<>x<" def test_forceescape(self, env): - tmpl = env.from_string("{{ x|forceescape }}") - assert tmpl.render(x=Markup("<div />")) == "<div />" + tmpl = env.from_string("{{ x|forceescape }}") + assert tmpl.render(x=Markup("<div />")) == "<div />" def test_safe(self, env): env = Environment(autoescape=True) tmpl = env.from_string('{{ "<div>foo</div>"|safe }}') - assert tmpl.render() == "<div>foo</div>" + assert tmpl.render() == "<div>foo</div>" tmpl = env.from_string('{{ "<div>foo</div>" }}') - assert tmpl.render() == "<div>foo</div>" - - @pytest.mark.parametrize( - ("value", "expect"), - [ - ("Hello, world!", "Hello%2C%20world%21"), - ("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"), - ({"f": 1}, "f=1"), - ([("f", 1), ("z", 2)], "f=1&z=2"), - ({"\u203d": 1}, "%E2%80%BD=1"), - ({0: 1}, "0=1"), - ([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"), - ("a b/c", "a%20b/c"), - ], - ) - def test_urlencode(self, value, expect): - e = Environment(autoescape=True) - t = e.from_string("{{ value|urlencode }}") - assert t.render(value=value) == expect + assert tmpl.render() == "<div>foo</div>" + + @pytest.mark.parametrize( + ("value", "expect"), + [ + ("Hello, world!", "Hello%2C%20world%21"), + ("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"), + ({"f": 1}, "f=1"), + ([("f", 1), ("z", 2)], "f=1&z=2"), + ({"\u203d": 1}, "%E2%80%BD=1"), + ({0: 1}, "0=1"), + ([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"), + ("a b/c", "a%20b/c"), + ], + ) + def test_urlencode(self, value, expect): + e = Environment(autoescape=True) + t = e.from_string("{{ value|urlencode }}") + assert t.render(value=value) == expect def test_simple_map(self, env): env = Environment() tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}') - assert tmpl.render() == "6" - - def test_map_sum(self, env): - tmpl = env.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}') - assert tmpl.render() == "[3, 3, 15]" + assert tmpl.render() == "6" + def test_map_sum(self, env): + tmpl = env.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}') + assert tmpl.render() == "[3, 3, 15]" + def test_attribute_map(self, env): - User = namedtuple("User", "name") + User = namedtuple("User", "name") env = Environment() users = [ - User("john"), - User("jane"), - User("mike"), + User("john"), + User("jane"), + User("mike"), ] tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}') - assert tmpl.render(users=users) == "john|jane|mike" + assert tmpl.render(users=users) == "john|jane|mike" 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): - Fullname = namedtuple("Fullname", "firstname,lastname") - Firstname = namedtuple("Firstname", "firstname") - env = Environment() - tmpl = env.from_string( - '{{ users|map(attribute="lastname", default="smith")|join(", ") }}' - ) - test_list = env.from_string( - '{{ users|map(attribute="lastname", default=["smith","x"])|join(", ") }}' - ) - test_str = env.from_string( - '{{ users|map(attribute="lastname", default="")|join(", ") }}' - ) - users = [ - Fullname("john", "lennon"), - Fullname("jane", "edwards"), - Fullname("jon", None), - Firstname("mike"), - ] - assert tmpl.render(users=users) == "lennon, edwards, None, smith" - assert test_list.render(users=users) == "lennon, edwards, None, ['smith', 'x']" - assert test_str.render(users=users) == "lennon, edwards, None, " - + assert tmpl.render() == "[]" + + def test_map_default(self, env): + Fullname = namedtuple("Fullname", "firstname,lastname") + Firstname = namedtuple("Firstname", "firstname") + env = Environment() + tmpl = env.from_string( + '{{ users|map(attribute="lastname", default="smith")|join(", ") }}' + ) + test_list = env.from_string( + '{{ users|map(attribute="lastname", default=["smith","x"])|join(", ") }}' + ) + test_str = env.from_string( + '{{ users|map(attribute="lastname", default="")|join(", ") }}' + ) + users = [ + Fullname("john", "lennon"), + Fullname("jane", "edwards"), + Fullname("jon", None), + Firstname("mike"), + ] + assert tmpl.render(users=users) == "lennon, edwards, None, smith" + assert test_list.render(users=users) == "lennon, edwards, None, ['smith', 'x']" + assert test_str.render(users=users) == "lennon, edwards, None, " + def test_simple_select(self, env): env = Environment() tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}') - assert tmpl.render() == "1|3|5" + assert tmpl.render() == "1|3|5" def test_bool_select(self, env): env = Environment() - tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}') - assert tmpl.render() == "1|2|3|4|5" + tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}') + assert tmpl.render() == "1|2|3|4|5" def test_simple_reject(self, env): env = Environment() tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}') - assert tmpl.render() == "2|4" + assert tmpl.render() == "2|4" def test_bool_reject(self, env): env = Environment() - tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}') - assert tmpl.render() == "None|False|0" + tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}') + assert tmpl.render() == "None|False|0" def test_simple_select_attr(self, env): - User = namedtuple("User", "name,is_active") + User = namedtuple("User", "name,is_active") env = Environment() users = [ - User("john", True), - User("jane", True), - User("mike", False), + User("john", True), + User("jane", True), + User("mike", False), ] tmpl = env.from_string( - '{{ users|selectattr("is_active")|map(attribute="name")|join("|") }}' + '{{ users|selectattr("is_active")|map(attribute="name")|join("|") }}' ) - assert tmpl.render(users=users) == "john|jane" + assert tmpl.render(users=users) == "john|jane" def test_simple_reject_attr(self, env): - User = namedtuple("User", "name,is_active") + User = namedtuple("User", "name,is_active") env = Environment() users = [ - User("john", True), - User("jane", True), - User("mike", False), + User("john", True), + User("jane", True), + User("mike", False), ] - tmpl = env.from_string( - '{{ users|rejectattr("is_active")|map(attribute="name")|join("|") }}' - ) - assert tmpl.render(users=users) == "mike" + tmpl = env.from_string( + '{{ users|rejectattr("is_active")|map(attribute="name")|join("|") }}' + ) + assert tmpl.render(users=users) == "mike" def test_func_select_attr(self, env): - User = namedtuple("User", "id,name") + User = namedtuple("User", "id,name") env = Environment() users = [ - User(1, "john"), - User(2, "jane"), - User(3, "mike"), + User(1, "john"), + User(2, "jane"), + User(3, "mike"), ] - tmpl = env.from_string( - '{{ users|selectattr("id", "odd")|map(attribute="name")|join("|") }}' - ) - assert tmpl.render(users=users) == "john|mike" + tmpl = env.from_string( + '{{ users|selectattr("id", "odd")|map(attribute="name")|join("|") }}' + ) + assert tmpl.render(users=users) == "john|mike" def test_func_reject_attr(self, env): - User = namedtuple("User", "id,name") + User = namedtuple("User", "id,name") env = Environment() users = [ - User(1, "john"), - User(2, "jane"), - User(3, "mike"), + User(1, "john"), + User(2, "jane"), + User(3, "mike"), ] - tmpl = env.from_string( - '{{ users|rejectattr("id", "odd")|map(attribute="name")|join("|") }}' - ) - assert tmpl.render(users=users) == "jane" + tmpl = env.from_string( + '{{ users|rejectattr("id", "odd")|map(attribute="name")|join("|") }}' + ) + assert tmpl.render(users=users) == "jane" def test_json_dump(self): env = Environment(autoescape=True) - t = env.from_string("{{ x|tojson }}") - assert t.render(x={"foo": "bar"}) == '{"foo": "bar"}' - assert t.render(x="\"ba&r'") == r'"\"ba\u0026r\u0027"' - assert t.render(x="<bar>") == r'"\u003cbar\u003e"' + t = env.from_string("{{ x|tojson }}") + assert t.render(x={"foo": "bar"}) == '{"foo": "bar"}' + assert t.render(x="\"ba&r'") == r'"\"ba\u0026r\u0027"' + assert t.render(x="<bar>") == r'"\u003cbar\u003e"' def my_dumps(value, **options): - assert options == {"foo": "bar"} - return "42" - - env.policies["json.dumps_function"] = my_dumps - env.policies["json.dumps_kwargs"] = {"foo": "bar"} - assert t.render(x=23) == "42" - - def test_wordwrap(self, env): - env.newline_sequence = "\n" - t = env.from_string("{{ s|wordwrap(20) }}") - result = t.render(s="Hello!\nThis is Jinja saying something.") - assert result == "Hello!\nThis is Jinja saying\nsomething." - - def test_filter_undefined(self, env): - with pytest.raises(TemplateAssertionError, match="No filter named 'f'"): - env.from_string("{{ var|f }}") - - def test_filter_undefined_in_if(self, env): - t = env.from_string("{%- if x is defined -%}{{ x|f }}{%- else -%}x{% endif %}") - assert t.render() == "x" - with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): - t.render(x=42) - - def test_filter_undefined_in_elif(self, env): - t = env.from_string( - "{%- if x is defined -%}{{ x }}{%- elif y is defined -%}" - "{{ y|f }}{%- else -%}foo{%- endif -%}" - ) - assert t.render() == "foo" - with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): - t.render(y=42) - - def test_filter_undefined_in_else(self, env): - t = env.from_string( - "{%- if x is not defined -%}foo{%- else -%}{{ x|f }}{%- endif -%}" - ) - assert t.render() == "foo" - with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): - t.render(x=42) - - def test_filter_undefined_in_nested_if(self, env): - t = env.from_string( - "{%- if x is not defined -%}foo{%- else -%}{%- if y " - "is defined -%}{{ y|f }}{%- endif -%}{{ x }}{%- endif -%}" - ) - assert t.render() == "foo" - assert t.render(x=42) == "42" - with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): - t.render(x=24, y=42) - - def test_filter_undefined_in_condexpr(self, env): - t1 = env.from_string("{{ x|f if x is defined else 'foo' }}") - t2 = env.from_string("{{ 'foo' if x is not defined else x|f }}") - assert t1.render() == t2.render() == "foo" - - with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): - t1.render(x=42) - t2.render(x=42) + assert options == {"foo": "bar"} + return "42" + + env.policies["json.dumps_function"] = my_dumps + env.policies["json.dumps_kwargs"] = {"foo": "bar"} + assert t.render(x=23) == "42" + + def test_wordwrap(self, env): + env.newline_sequence = "\n" + t = env.from_string("{{ s|wordwrap(20) }}") + result = t.render(s="Hello!\nThis is Jinja saying something.") + assert result == "Hello!\nThis is Jinja saying\nsomething." + + def test_filter_undefined(self, env): + with pytest.raises(TemplateAssertionError, match="No filter named 'f'"): + env.from_string("{{ var|f }}") + + def test_filter_undefined_in_if(self, env): + t = env.from_string("{%- if x is defined -%}{{ x|f }}{%- else -%}x{% endif %}") + assert t.render() == "x" + with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): + t.render(x=42) + + def test_filter_undefined_in_elif(self, env): + t = env.from_string( + "{%- if x is defined -%}{{ x }}{%- elif y is defined -%}" + "{{ y|f }}{%- else -%}foo{%- endif -%}" + ) + assert t.render() == "foo" + with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): + t.render(y=42) + + def test_filter_undefined_in_else(self, env): + t = env.from_string( + "{%- if x is not defined -%}foo{%- else -%}{{ x|f }}{%- endif -%}" + ) + assert t.render() == "foo" + with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): + t.render(x=42) + + def test_filter_undefined_in_nested_if(self, env): + t = env.from_string( + "{%- if x is not defined -%}foo{%- else -%}{%- if y " + "is defined -%}{{ y|f }}{%- endif -%}{{ x }}{%- endif -%}" + ) + assert t.render() == "foo" + assert t.render(x=42) == "42" + with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): + t.render(x=24, y=42) + + def test_filter_undefined_in_condexpr(self, env): + t1 = env.from_string("{{ x|f if x is defined else 'foo' }}") + t2 = env.from_string("{{ 'foo' if x is not defined else x|f }}") + assert t1.render() == t2.render() == "foo" + + with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"): + t1.render(x=42) + t2.render(x=42) diff --git a/contrib/python/Jinja2/py3/tests/test_idtracking.py b/contrib/python/Jinja2/py3/tests/test_idtracking.py index 4e1d2c3d45..5573375cd4 100644 --- a/contrib/python/Jinja2/py3/tests/test_idtracking.py +++ b/contrib/python/Jinja2/py3/tests/test_idtracking.py @@ -4,287 +4,287 @@ from jinja2.idtracking import symbols_for_node def test_basics(): for_loop = nodes.For( - nodes.Name("foo", "store"), - nodes.Name("seq", "load"), - [nodes.Output([nodes.Name("foo", "load")])], - [], - None, - False, - ) - tmpl = nodes.Template( - [nodes.Assign(nodes.Name("foo", "store"), nodes.Name("bar", "load")), for_loop] - ) + nodes.Name("foo", "store"), + nodes.Name("seq", "load"), + [nodes.Output([nodes.Name("foo", "load")])], + [], + None, + False, + ) + tmpl = nodes.Template( + [nodes.Assign(nodes.Name("foo", "store"), nodes.Name("bar", "load")), for_loop] + ) sym = symbols_for_node(tmpl) assert sym.refs == { - "foo": "l_0_foo", - "bar": "l_0_bar", - "seq": "l_0_seq", + "foo": "l_0_foo", + "bar": "l_0_bar", + "seq": "l_0_seq", } assert sym.loads == { - "l_0_foo": ("undefined", None), - "l_0_bar": ("resolve", "bar"), - "l_0_seq": ("resolve", "seq"), + "l_0_foo": ("undefined", None), + "l_0_bar": ("resolve", "bar"), + "l_0_seq": ("resolve", "seq"), } sym = symbols_for_node(for_loop, sym) assert sym.refs == { - "foo": "l_1_foo", + "foo": "l_1_foo", } assert sym.loads == { - "l_1_foo": ("param", None), + "l_1_foo": ("param", None), } def test_complex(): - title_block = nodes.Block( - "title", [nodes.Output([nodes.TemplateData("Page Title")])], False, False - ) + title_block = nodes.Block( + "title", [nodes.Output([nodes.TemplateData("Page Title")])], False, False + ) - render_title_macro = nodes.Macro( - "render_title", - [nodes.Name("title", "param")], - [], - [ - nodes.Output( - [ - nodes.TemplateData('\n <div class="title">\n <h1>'), - nodes.Name("title", "load"), - nodes.TemplateData("</h1>\n <p>"), - nodes.Name("subtitle", "load"), - nodes.TemplateData("</p>\n "), - ] - ), - nodes.Assign( - nodes.Name("subtitle", "store"), nodes.Const("something else") - ), - nodes.Output( - [ - nodes.TemplateData("\n <p>"), - nodes.Name("subtitle", "load"), - nodes.TemplateData("</p>\n </div>\n"), - nodes.If( - nodes.Name("something", "load"), - [ - nodes.Assign( - nodes.Name("title_upper", "store"), - nodes.Filter( - nodes.Name("title", "load"), - "upper", - [], - [], - None, - None, - ), - ), - nodes.Output( - [ - nodes.Name("title_upper", "load"), - nodes.Call( - nodes.Name("render_title", "load"), - [nodes.Const("Aha")], - [], - None, - None, - ), - ] - ), - ], - [], - [], - ), - ] - ), - ], - ) + render_title_macro = nodes.Macro( + "render_title", + [nodes.Name("title", "param")], + [], + [ + nodes.Output( + [ + nodes.TemplateData('\n <div class="title">\n <h1>'), + nodes.Name("title", "load"), + nodes.TemplateData("</h1>\n <p>"), + nodes.Name("subtitle", "load"), + nodes.TemplateData("</p>\n "), + ] + ), + nodes.Assign( + nodes.Name("subtitle", "store"), nodes.Const("something else") + ), + nodes.Output( + [ + nodes.TemplateData("\n <p>"), + nodes.Name("subtitle", "load"), + nodes.TemplateData("</p>\n </div>\n"), + nodes.If( + nodes.Name("something", "load"), + [ + nodes.Assign( + nodes.Name("title_upper", "store"), + nodes.Filter( + nodes.Name("title", "load"), + "upper", + [], + [], + None, + None, + ), + ), + nodes.Output( + [ + nodes.Name("title_upper", "load"), + nodes.Call( + nodes.Name("render_title", "load"), + [nodes.Const("Aha")], + [], + None, + None, + ), + ] + ), + ], + [], + [], + ), + ] + ), + ], + ) for_loop = nodes.For( - nodes.Name("item", "store"), - nodes.Name("seq", "load"), - [ - nodes.Output( - [ - nodes.TemplateData("\n <li>"), - nodes.Name("item", "load"), - nodes.TemplateData("</li>\n <span>"), - ] - ), - nodes.Include(nodes.Const("helper.html"), True, False), - nodes.Output([nodes.TemplateData("</span>\n ")]), - ], - [], - None, - False, - ) + nodes.Name("item", "store"), + nodes.Name("seq", "load"), + [ + nodes.Output( + [ + nodes.TemplateData("\n <li>"), + nodes.Name("item", "load"), + nodes.TemplateData("</li>\n <span>"), + ] + ), + nodes.Include(nodes.Const("helper.html"), True, False), + nodes.Output([nodes.TemplateData("</span>\n ")]), + ], + [], + None, + False, + ) - body_block = nodes.Block( - "body", - [ - nodes.Output( - [ - nodes.TemplateData("\n "), - nodes.Call( - nodes.Name("render_title", "load"), - [nodes.Name("item", "load")], - [], - None, - None, - ), - nodes.TemplateData("\n <ul>\n "), - ] - ), - for_loop, - nodes.Output([nodes.TemplateData("\n </ul>\n")]), - ], - False, - False, - ) + body_block = nodes.Block( + "body", + [ + nodes.Output( + [ + nodes.TemplateData("\n "), + nodes.Call( + nodes.Name("render_title", "load"), + [nodes.Name("item", "load")], + [], + None, + None, + ), + nodes.TemplateData("\n <ul>\n "), + ] + ), + for_loop, + nodes.Output([nodes.TemplateData("\n </ul>\n")]), + ], + False, + False, + ) - tmpl = nodes.Template( - [ - nodes.Extends(nodes.Const("layout.html")), - title_block, - render_title_macro, - body_block, - ] - ) + tmpl = nodes.Template( + [ + nodes.Extends(nodes.Const("layout.html")), + title_block, + render_title_macro, + body_block, + ] + ) tmpl_sym = symbols_for_node(tmpl) assert tmpl_sym.refs == { - "render_title": "l_0_render_title", + "render_title": "l_0_render_title", } assert tmpl_sym.loads == { - "l_0_render_title": ("undefined", None), + "l_0_render_title": ("undefined", None), } - assert tmpl_sym.stores == {"render_title"} + assert tmpl_sym.stores == {"render_title"} assert tmpl_sym.dump_stores() == { - "render_title": "l_0_render_title", + "render_title": "l_0_render_title", } macro_sym = symbols_for_node(render_title_macro, tmpl_sym) assert macro_sym.refs == { - "subtitle": "l_1_subtitle", - "something": "l_1_something", - "title": "l_1_title", - "title_upper": "l_1_title_upper", + "subtitle": "l_1_subtitle", + "something": "l_1_something", + "title": "l_1_title", + "title_upper": "l_1_title_upper", } assert macro_sym.loads == { - "l_1_subtitle": ("resolve", "subtitle"), - "l_1_something": ("resolve", "something"), - "l_1_title": ("param", None), - "l_1_title_upper": ("resolve", "title_upper"), + "l_1_subtitle": ("resolve", "subtitle"), + "l_1_something": ("resolve", "something"), + "l_1_title": ("param", None), + "l_1_title_upper": ("resolve", "title_upper"), } - assert macro_sym.stores == {"title", "title_upper", "subtitle"} - assert macro_sym.find_ref("render_title") == "l_0_render_title" + assert macro_sym.stores == {"title", "title_upper", "subtitle"} + assert macro_sym.find_ref("render_title") == "l_0_render_title" assert macro_sym.dump_stores() == { - "title": "l_1_title", - "title_upper": "l_1_title_upper", - "subtitle": "l_1_subtitle", - "render_title": "l_0_render_title", + "title": "l_1_title", + "title_upper": "l_1_title_upper", + "subtitle": "l_1_subtitle", + "render_title": "l_0_render_title", } body_sym = symbols_for_node(body_block) assert body_sym.refs == { - "item": "l_0_item", - "seq": "l_0_seq", - "render_title": "l_0_render_title", + "item": "l_0_item", + "seq": "l_0_seq", + "render_title": "l_0_render_title", } assert body_sym.loads == { - "l_0_item": ("resolve", "item"), - "l_0_seq": ("resolve", "seq"), - "l_0_render_title": ("resolve", "render_title"), + "l_0_item": ("resolve", "item"), + "l_0_seq": ("resolve", "seq"), + "l_0_render_title": ("resolve", "render_title"), } - assert body_sym.stores == set() + assert body_sym.stores == set() for_sym = symbols_for_node(for_loop, body_sym) assert for_sym.refs == { - "item": "l_1_item", + "item": "l_1_item", } assert for_sym.loads == { - "l_1_item": ("param", None), + "l_1_item": ("param", None), } - assert for_sym.stores == {"item"} + assert for_sym.stores == {"item"} assert for_sym.dump_stores() == { - "item": "l_1_item", + "item": "l_1_item", } def test_if_branching_stores(): - tmpl = nodes.Template( - [ - nodes.If( - nodes.Name("expression", "load"), - [nodes.Assign(nodes.Name("variable", "store"), nodes.Const(42))], - [], - [], - ) - ] - ) + tmpl = nodes.Template( + [ + nodes.If( + nodes.Name("expression", "load"), + [nodes.Assign(nodes.Name("variable", "store"), nodes.Const(42))], + [], + [], + ) + ] + ) sym = symbols_for_node(tmpl) - assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"} - assert sym.stores == {"variable"} + assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"} + assert sym.stores == {"variable"} assert sym.loads == { - "l_0_variable": ("resolve", "variable"), - "l_0_expression": ("resolve", "expression"), + "l_0_variable": ("resolve", "variable"), + "l_0_expression": ("resolve", "expression"), } assert sym.dump_stores() == { - "variable": "l_0_variable", + "variable": "l_0_variable", } def test_if_branching_stores_undefined(): - tmpl = nodes.Template( - [ - nodes.Assign(nodes.Name("variable", "store"), nodes.Const(23)), - nodes.If( - nodes.Name("expression", "load"), - [nodes.Assign(nodes.Name("variable", "store"), nodes.Const(42))], - [], - [], - ), - ] - ) + tmpl = nodes.Template( + [ + nodes.Assign(nodes.Name("variable", "store"), nodes.Const(23)), + nodes.If( + nodes.Name("expression", "load"), + [nodes.Assign(nodes.Name("variable", "store"), nodes.Const(42))], + [], + [], + ), + ] + ) sym = symbols_for_node(tmpl) - assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"} - assert sym.stores == {"variable"} + assert sym.refs == {"variable": "l_0_variable", "expression": "l_0_expression"} + assert sym.stores == {"variable"} assert sym.loads == { - "l_0_variable": ("undefined", None), - "l_0_expression": ("resolve", "expression"), + "l_0_variable": ("undefined", None), + "l_0_expression": ("resolve", "expression"), } assert sym.dump_stores() == { - "variable": "l_0_variable", + "variable": "l_0_variable", } def test_if_branching_multi_scope(): - for_loop = nodes.For( - nodes.Name("item", "store"), - nodes.Name("seq", "load"), - [ - nodes.If( - nodes.Name("expression", "load"), - [nodes.Assign(nodes.Name("x", "store"), nodes.Const(42))], - [], - [], - ), - nodes.Include(nodes.Const("helper.html"), True, False), - ], - [], - None, - False, - ) + for_loop = nodes.For( + nodes.Name("item", "store"), + nodes.Name("seq", "load"), + [ + nodes.If( + nodes.Name("expression", "load"), + [nodes.Assign(nodes.Name("x", "store"), nodes.Const(42))], + [], + [], + ), + nodes.Include(nodes.Const("helper.html"), True, False), + ], + [], + None, + False, + ) - tmpl = nodes.Template( - [nodes.Assign(nodes.Name("x", "store"), nodes.Const(23)), for_loop] - ) + tmpl = nodes.Template( + [nodes.Assign(nodes.Name("x", "store"), nodes.Const(23)), for_loop] + ) tmpl_sym = symbols_for_node(tmpl) for_sym = symbols_for_node(for_loop, tmpl_sym) - assert for_sym.stores == {"item", "x"} + assert for_sym.stores == {"item", "x"} assert for_sym.loads == { - "l_1_x": ("alias", "l_0_x"), - "l_1_item": ("param", None), - "l_1_expression": ("resolve", "expression"), + "l_1_x": ("alias", "l_0_x"), + "l_1_item": ("param", None), + "l_1_expression": ("resolve", "expression"), } diff --git a/contrib/python/Jinja2/py3/tests/test_imports.py b/contrib/python/Jinja2/py3/tests/test_imports.py index b59fb49dde..601ee6e743 100644 --- a/contrib/python/Jinja2/py3/tests/test_imports.py +++ b/contrib/python/Jinja2/py3/tests/test_imports.py @@ -1,50 +1,50 @@ import pytest -from jinja2.environment import Environment -from jinja2.exceptions import TemplateNotFound -from jinja2.exceptions import TemplatesNotFound -from jinja2.exceptions import TemplateSyntaxError -from jinja2.exceptions import UndefinedError -from jinja2.loaders import DictLoader +from jinja2.environment import Environment +from jinja2.exceptions import TemplateNotFound +from jinja2.exceptions import TemplatesNotFound +from jinja2.exceptions import TemplateSyntaxError +from jinja2.exceptions import UndefinedError +from jinja2.loaders import DictLoader @pytest.fixture def test_env(): - env = Environment( - loader=DictLoader( - dict( - module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}", - header="[{{ foo }}|{{ 23 }}]", - o_printer="({{ o }})", - ) - ) - ) - env.globals["bar"] = 23 + env = Environment( + loader=DictLoader( + dict( + module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}", + header="[{{ foo }}|{{ 23 }}]", + o_printer="({{ o }})", + ) + ) + ) + env.globals["bar"] = 23 return env -class TestImports: +class TestImports: def test_context_imports(self, test_env): t = test_env.from_string('{% import "module" as m %}{{ m.test() }}') - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env.from_string( '{% import "module" as m without context %}{{ m.test() }}' ) - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env.from_string( '{% import "module" as m with context %}{{ m.test() }}' ) - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" t = test_env.from_string('{% from "module" import test %}{{ test() }}') - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env.from_string( '{% from "module" import test without context %}{{ test() }}' ) - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" t = test_env.from_string( '{% from "module" import test with context %}{{ test() }}' ) - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" def test_import_needs_name(self, test_env): test_env.from_string('{% from "foo" import bar %}') @@ -77,113 +77,113 @@ class TestImports: test_env.from_string('{% from "foo" import bar with context, %}') def test_exports(self, test_env): - m = test_env.from_string( - """ + m = test_env.from_string( + """ {% macro toplevel() %}...{% endmacro %} {% macro __private() %}...{% endmacro %} {% set variable = 42 %} {% for item in [1] %} {% macro notthere() %}{% endmacro %} {% endfor %} - """ - ).module - assert m.toplevel() == "..." - assert not hasattr(m, "__missing") + """ + ).module + assert m.toplevel() == "..." + assert not hasattr(m, "__missing") assert m.variable == 42 - assert not hasattr(m, "notthere") - - def test_not_exported(self, test_env): - t = test_env.from_string("{% from 'module' import nothing %}{{ nothing() }}") - - with pytest.raises(UndefinedError, match="does not export the requested name"): - t.render() - - def test_import_with_globals(self, test_env): - t = test_env.from_string( - '{% import "module" as m %}{{ m.test() }}', globals={"foo": 42} - ) - assert t.render() == "[42|23]" - - t = test_env.from_string('{% import "module" as m %}{{ m.test() }}') - assert t.render() == "[|23]" - - def test_import_with_globals_override(self, test_env): - t = test_env.from_string( - '{% set foo = 41 %}{% import "module" as m %}{{ m.test() }}', - globals={"foo": 42}, - ) - assert t.render() == "[42|23]" - - def test_from_import_with_globals(self, test_env): - t = test_env.from_string( - '{% from "module" import test %}{{ test() }}', - globals={"foo": 42}, - ) - assert t.render() == "[42|23]" - - -class TestIncludes: + assert not hasattr(m, "notthere") + + def test_not_exported(self, test_env): + t = test_env.from_string("{% from 'module' import nothing %}{{ nothing() }}") + + with pytest.raises(UndefinedError, match="does not export the requested name"): + t.render() + + def test_import_with_globals(self, test_env): + t = test_env.from_string( + '{% import "module" as m %}{{ m.test() }}', globals={"foo": 42} + ) + assert t.render() == "[42|23]" + + t = test_env.from_string('{% import "module" as m %}{{ m.test() }}') + assert t.render() == "[|23]" + + def test_import_with_globals_override(self, test_env): + t = test_env.from_string( + '{% set foo = 41 %}{% import "module" as m %}{{ m.test() }}', + globals={"foo": 42}, + ) + assert t.render() == "[42|23]" + + def test_from_import_with_globals(self, test_env): + t = test_env.from_string( + '{% from "module" import test %}{{ test() }}', + globals={"foo": 42}, + ) + assert t.render() == "[42|23]" + + +class TestIncludes: def test_context_include(self, test_env): t = test_env.from_string('{% include "header" %}') - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" t = test_env.from_string('{% include "header" with context %}') - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" t = test_env.from_string('{% include "header" without context %}') - assert t.render(foo=42) == "[|23]" + assert t.render(foo=42) == "[|23]" def test_choice_includes(self, test_env): t = test_env.from_string('{% include ["missing", "header"] %}') - assert t.render(foo=42) == "[42|23]" + assert t.render(foo=42) == "[42|23]" - t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}') - assert t.render(foo=42) == "" + t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}') + assert t.render(foo=42) == "" t = test_env.from_string('{% include ["missing", "missing2"] %}') pytest.raises(TemplateNotFound, t.render) - with pytest.raises(TemplatesNotFound) as e: + with pytest.raises(TemplatesNotFound) as e: t.render() - assert e.value.templates == ["missing", "missing2"] - assert e.value.name == "missing2" - + assert e.value.templates == ["missing", "missing2"] + assert e.value.name == "missing2" + def test_includes(t, **ctx): - ctx["foo"] = 42 - assert t.render(ctx) == "[42|23]" + ctx["foo"] = 42 + assert t.render(ctx) == "[42|23]" t = test_env.from_string('{% include ["missing", "header"] %}') test_includes(t) - t = test_env.from_string("{% include x %}") - test_includes(t, x=["missing", "header"]) + t = test_env.from_string("{% include x %}") + test_includes(t, x=["missing", "header"]) t = test_env.from_string('{% include [x, "header"] %}') - 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") + 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") def test_include_ignoring_missing(self, test_env): t = test_env.from_string('{% include "missing" %}') pytest.raises(TemplateNotFound, t.render) - for extra in "", "with context", "without context": - t = test_env.from_string( - '{% include "missing" ignore missing ' + extra + " %}" - ) - assert t.render() == "" + for extra in "", "with context", "without context": + t = test_env.from_string( + '{% include "missing" ignore missing ' + extra + " %}" + ) + assert t.render() == "" def test_context_include_with_overrides(self, test_env): - env = Environment( - loader=DictLoader( - dict( - main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}", - item="{{ item }}", - ) - ) - ) + env = Environment( + loader=DictLoader( + dict( + main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}", + item="{{ item }}", + ) + ) + ) assert env.get_template("main").render() == "123" def test_unoptimized_scopes(self, test_env): - t = test_env.from_string( - """ + t = test_env.from_string( + """ {% macro outer(o) %} {% macro inner() %} {% include "o_printer" %} @@ -191,15 +191,15 @@ class TestIncludes: {{ inner() }} {% endmacro %} {{ outer("FOO") }} - """ - ) - assert t.render().strip() == "(FOO)" + """ + ) + assert t.render().strip() == "(FOO)" def test_import_from_with_context(self): - env = Environment( - loader=DictLoader({"a": "{% macro x() %}{{ foobar }}{% endmacro %}"}) - ) - t = env.from_string( - "{% set foobar = 42 %}{% from 'a' import x with context %}{{ x() }}" - ) - assert t.render() == "42" + env = Environment( + loader=DictLoader({"a": "{% macro x() %}{{ foobar }}{% endmacro %}"}) + ) + t = env.from_string( + "{% set foobar = 42 %}{% from 'a' import x with context %}{{ x() }}" + ) + assert t.render() == "42" diff --git a/contrib/python/Jinja2/py3/tests/test_inheritance.py b/contrib/python/Jinja2/py3/tests/test_inheritance.py index 0c20d4da7d..b230646463 100644 --- a/contrib/python/Jinja2/py3/tests/test_inheritance.py +++ b/contrib/python/Jinja2/py3/tests/test_inheritance.py @@ -1,38 +1,38 @@ import pytest -from jinja2 import DictLoader -from jinja2 import Environment -from jinja2 import TemplateRuntimeError -from jinja2 import TemplateSyntaxError +from jinja2 import DictLoader +from jinja2 import Environment +from jinja2 import TemplateRuntimeError +from jinja2 import TemplateSyntaxError -LAYOUTTEMPLATE = """\ +LAYOUTTEMPLATE = """\ |{% block block1 %}block 1 from layout{% endblock %} |{% block block2 %}block 2 from layout{% endblock %} |{% block block3 %} {% block block4 %}nested block 4 from layout{% endblock %} -{% endblock %}|""" +{% endblock %}|""" -LEVEL1TEMPLATE = """\ +LEVEL1TEMPLATE = """\ {% extends "layout" %} -{% block block1 %}block 1 from level1{% endblock %}""" +{% block block1 %}block 1 from level1{% endblock %}""" -LEVEL2TEMPLATE = """\ +LEVEL2TEMPLATE = """\ {% extends "level1" %} {% block block2 %}{% block block5 %}nested block 5 from level2{% -endblock %}{% endblock %}""" +endblock %}{% endblock %}""" -LEVEL3TEMPLATE = """\ +LEVEL3TEMPLATE = """\ {% extends "level2" %} {% block block5 %}block 5 from level3{% endblock %} {% block block4 %}block 4 from level3{% endblock %} -""" +""" -LEVEL4TEMPLATE = """\ +LEVEL4TEMPLATE = """\ {% extends "level3" %} {% block block3 %}block 3 from level4{% endblock %} -""" +""" -WORKINGTEMPLATE = """\ +WORKINGTEMPLATE = """\ {% extends "layout" %} {% block block1 %} {% if false %} @@ -41,9 +41,9 @@ WORKINGTEMPLATE = """\ {% endblock %} {% endif %} {% endblock %} -""" +""" -DOUBLEEXTENDS = """\ +DOUBLEEXTENDS = """\ {% extends "layout" %} {% extends "layout" %} {% block block1 %} @@ -53,168 +53,168 @@ DOUBLEEXTENDS = """\ {% endblock %} {% endif %} {% endblock %} -""" +""" @pytest.fixture def env(): - return Environment( - loader=DictLoader( - { - "layout": LAYOUTTEMPLATE, - "level1": LEVEL1TEMPLATE, - "level2": LEVEL2TEMPLATE, - "level3": LEVEL3TEMPLATE, - "level4": LEVEL4TEMPLATE, - "working": WORKINGTEMPLATE, - "doublee": DOUBLEEXTENDS, - } - ), - trim_blocks=True, - ) - - -class TestInheritance: + return Environment( + loader=DictLoader( + { + "layout": LAYOUTTEMPLATE, + "level1": LEVEL1TEMPLATE, + "level2": LEVEL2TEMPLATE, + "level3": LEVEL3TEMPLATE, + "level4": LEVEL4TEMPLATE, + "working": WORKINGTEMPLATE, + "doublee": DOUBLEEXTENDS, + } + ), + trim_blocks=True, + ) + + +class TestInheritance: def test_layout(self, env): - tmpl = env.get_template("layout") - assert tmpl.render() == ( - "|block 1 from layout|block 2 from layout|nested block 4 from layout|" - ) + tmpl = env.get_template("layout") + assert tmpl.render() == ( + "|block 1 from layout|block 2 from layout|nested block 4 from layout|" + ) def test_level1(self, env): - tmpl = env.get_template("level1") - assert tmpl.render() == ( - "|block 1 from level1|block 2 from layout|nested block 4 from layout|" - ) + tmpl = env.get_template("level1") + assert tmpl.render() == ( + "|block 1 from level1|block 2 from layout|nested block 4 from layout|" + ) def test_level2(self, env): - tmpl = env.get_template("level2") - assert tmpl.render() == ( - "|block 1 from level1|nested block 5 from " - "level2|nested block 4 from layout|" - ) + tmpl = env.get_template("level2") + assert tmpl.render() == ( + "|block 1 from level1|nested block 5 from " + "level2|nested block 4 from layout|" + ) def test_level3(self, env): - tmpl = env.get_template("level3") - assert tmpl.render() == ( - "|block 1 from level1|block 5 from level3|block 4 from level3|" - ) + tmpl = env.get_template("level3") + assert tmpl.render() == ( + "|block 1 from level1|block 5 from level3|block 4 from level3|" + ) def test_level4(self, env): - tmpl = env.get_template("level4") - assert tmpl.render() == ( - "|block 1 from level1|block 5 from level3|block 3 from level4|" - ) + tmpl = env.get_template("level4") + assert tmpl.render() == ( + "|block 1 from level1|block 5 from level3|block 3 from level4|" + ) def test_super(self, env): - env = Environment( - loader=DictLoader( - { - "a": "{% block intro %}INTRO{% endblock %}|" - "BEFORE|{% block data %}INNER{% endblock %}|AFTER", - "b": '{% extends "a" %}{% block data %}({{ ' - "super() }}){% endblock %}", - "c": '{% extends "b" %}{% block intro %}--{{ ' - "super() }}--{% endblock %}\n{% block data " - "%}[{{ super() }}]{% endblock %}", - } - ) - ) - tmpl = env.get_template("c") - assert tmpl.render() == "--INTRO--|BEFORE|[(INNER)]|AFTER" + env = Environment( + loader=DictLoader( + { + "a": "{% block intro %}INTRO{% endblock %}|" + "BEFORE|{% block data %}INNER{% endblock %}|AFTER", + "b": '{% extends "a" %}{% block data %}({{ ' + "super() }}){% endblock %}", + "c": '{% extends "b" %}{% block intro %}--{{ ' + "super() }}--{% endblock %}\n{% block data " + "%}[{{ super() }}]{% endblock %}", + } + ) + ) + tmpl = env.get_template("c") + assert tmpl.render() == "--INTRO--|BEFORE|[(INNER)]|AFTER" def test_working(self, env): - env.get_template("working") + env.get_template("working") def test_reuse_blocks(self, env): - tmpl = env.from_string( - "{{ self.foo() }}|{% block foo %}42{% endblock %}|{{ self.foo() }}" - ) - assert tmpl.render() == "42|42|42" + tmpl = env.from_string( + "{{ self.foo() }}|{% block foo %}42{% endblock %}|{{ self.foo() }}" + ) + assert tmpl.render() == "42|42|42" def test_preserve_blocks(self, env): - env = Environment( - loader=DictLoader( - { - "a": "{% if false %}{% block x %}A{% endblock %}" - "{% endif %}{{ self.x() }}", - "b": '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}', - } - ) - ) - tmpl = env.get_template("b") - assert tmpl.render() == "BA" + env = Environment( + loader=DictLoader( + { + "a": "{% if false %}{% block x %}A{% endblock %}" + "{% endif %}{{ self.x() }}", + "b": '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}', + } + ) + ) + tmpl = env.get_template("b") + assert tmpl.render() == "BA" def test_dynamic_inheritance(self, env): - env = Environment( - loader=DictLoader( - { - "default1": "DEFAULT1{% block x %}{% endblock %}", - "default2": "DEFAULT2{% block x %}{% endblock %}", - "child": "{% extends default %}{% block x %}CHILD{% endblock %}", - } - ) - ) - tmpl = env.get_template("child") + env = Environment( + loader=DictLoader( + { + "default1": "DEFAULT1{% block x %}{% endblock %}", + "default2": "DEFAULT2{% block x %}{% endblock %}", + "child": "{% extends default %}{% block x %}CHILD{% endblock %}", + } + ) + ) + tmpl = env.get_template("child") for m in range(1, 3): - assert tmpl.render(default=f"default{m}") == f"DEFAULT{m}CHILD" + assert tmpl.render(default=f"default{m}") == f"DEFAULT{m}CHILD" def test_multi_inheritance(self, env): - env = Environment( - loader=DictLoader( - { - "default1": "DEFAULT1{% block x %}{% endblock %}", - "default2": "DEFAULT2{% block x %}{% endblock %}", - "child": ( - "{% if default %}{% extends default %}{% else %}" - "{% extends 'default1' %}{% endif %}" - "{% block x %}CHILD{% endblock %}" - ), - } - ) - ) - tmpl = env.get_template("child") - assert tmpl.render(default="default2") == "DEFAULT2CHILD" - assert tmpl.render(default="default1") == "DEFAULT1CHILD" - assert tmpl.render() == "DEFAULT1CHILD" + env = Environment( + loader=DictLoader( + { + "default1": "DEFAULT1{% block x %}{% endblock %}", + "default2": "DEFAULT2{% block x %}{% endblock %}", + "child": ( + "{% if default %}{% extends default %}{% else %}" + "{% extends 'default1' %}{% endif %}" + "{% block x %}CHILD{% endblock %}" + ), + } + ) + ) + tmpl = env.get_template("child") + assert tmpl.render(default="default2") == "DEFAULT2CHILD" + assert tmpl.render(default="default1") == "DEFAULT1CHILD" + assert tmpl.render() == "DEFAULT1CHILD" def test_scoped_block(self, env): - env = Environment( - loader=DictLoader( - { - "default.html": "{% for item in seq %}[{% block item scoped %}" - "{% endblock %}]{% endfor %}" - } - ) - ) - t = env.from_string( - "{% extends 'default.html' %}{% block item %}{{ item }}{% endblock %}" - ) - assert t.render(seq=list(range(5))) == "[0][1][2][3][4]" + env = Environment( + loader=DictLoader( + { + "default.html": "{% for item in seq %}[{% block item scoped %}" + "{% endblock %}]{% endfor %}" + } + ) + ) + t = env.from_string( + "{% extends 'default.html' %}{% block item %}{{ item }}{% endblock %}" + ) + assert t.render(seq=list(range(5))) == "[0][1][2][3][4]" def test_super_in_scoped_block(self, env): - env = Environment( - loader=DictLoader( - { - "default.html": "{% for item in seq %}[{% block item scoped %}" - "{{ item }}{% endblock %}]{% endfor %}" - } - ) - ) - t = env.from_string( - '{% extends "default.html" %}{% block item %}' - "{{ super() }}|{{ item * 2 }}{% endblock %}" - ) - assert t.render(seq=list(range(5))) == "[0|0][1|2][2|4][3|6][4|8]" + env = Environment( + loader=DictLoader( + { + "default.html": "{% for item in seq %}[{% block item scoped %}" + "{{ item }}{% endblock %}]{% endfor %}" + } + ) + ) + t = env.from_string( + '{% extends "default.html" %}{% block item %}' + "{{ super() }}|{{ item * 2 }}{% endblock %}" + ) + assert t.render(seq=list(range(5))) == "[0|0][1|2][2|4][3|6][4|8]" def test_scoped_block_after_inheritance(self, env): - env = Environment( - loader=DictLoader( - { - "layout.html": """ + env = Environment( + loader=DictLoader( + { + "layout.html": """ {% block useless %}{% endblock %} - """, - "index.html": """ + """, + "index.html": """ {%- extends 'layout.html' %} {% from 'helpers.html' import foo with context %} {% block useless %} @@ -224,141 +224,141 @@ class TestInheritance: {% endblock %} {% endfor %} {% endblock %} - """, - "helpers.html": """ + """, + "helpers.html": """ {% macro foo(x) %}{{ the_foo + x }}{% endmacro %} - """, - } - ) - ) - rv = env.get_template("index.html").render(the_foo=42).split() - assert rv == ["43", "44", "45"] - - def test_level1_required(self, env): - env = Environment( - loader=DictLoader( - { - "default": "{% block x required %}{# comment #}\n {% endblock %}", - "level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}", - } - ) - ) - rv = env.get_template("level1").render() - assert rv == "[1]" - - def test_level2_required(self, env): - env = Environment( - loader=DictLoader( - { - "default": "{% block x required %}{% endblock %}", - "level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}", - "level2": "{% extends 'default' %}{% block x %}[2]{% endblock %}", - } - ) - ) - rv1 = env.get_template("level1").render() - rv2 = env.get_template("level2").render() - - assert rv1 == "[1]" - assert rv2 == "[2]" - - def test_level3_required(self, env): - env = Environment( - loader=DictLoader( - { - "default": "{% block x required %}{% endblock %}", - "level1": "{% extends 'default' %}", - "level2": "{% extends 'level1' %}{% block x %}[2]{% endblock %}", - "level3": "{% extends 'level2' %}", - } - ) - ) - t1 = env.get_template("level1") - t2 = env.get_template("level2") - t3 = env.get_template("level3") - - with pytest.raises(TemplateRuntimeError, match="Required block 'x' not found"): - assert t1.render() - - assert t2.render() == "[2]" - assert t3.render() == "[2]" - - def test_invalid_required(self, env): - env = Environment( - loader=DictLoader( - { - "default": "{% block x required %}data {# #}{% endblock %}", - "default1": "{% block x required %}{% block y %}" - "{% endblock %} {% endblock %}", - "default2": "{% block x required %}{% if true %}" - "{% endif %} {% endblock %}", - "level1": "{% if default %}{% extends default %}" - "{% else %}{% extends 'default' %}{% endif %}" - "{%- block x %}CHILD{% endblock %}", - } - ) - ) - t = env.get_template("level1") - - with pytest.raises( - TemplateSyntaxError, - match="Required blocks can only contain comments or whitespace", - ): - assert t.render(default="default") - assert t.render(default="default2") - assert t.render(default="default3") - - def test_required_with_scope(self, env): - env = Environment( - loader=DictLoader( - { - "default1": "{% for item in seq %}[{% block item scoped required %}" - "{% endblock %}]{% endfor %}", - "child1": "{% extends 'default1' %}{% block item %}" - "{{ item }}{% endblock %}", - "default2": "{% for item in seq %}[{% block item required scoped %}" - "{% endblock %}]{% endfor %}", - "child2": "{% extends 'default2' %}{% block item %}" - "{{ item }}{% endblock %}", - } - ) - ) - t1 = env.get_template("child1") - t2 = env.get_template("child2") - - assert t1.render(seq=list(range(3))) == "[0][1][2]" - - # scoped must come before required - with pytest.raises(TemplateSyntaxError): - t2.render(seq=list(range(3))) - - def test_duplicate_required_or_scoped(self, env): - env = Environment( - loader=DictLoader( - { - "default1": "{% for item in seq %}[{% block item " - "scoped scoped %}}{{% endblock %}}]{{% endfor %}}", - "default2": "{% for item in seq %}[{% block item " - "required required %}}{{% endblock %}}]{{% endfor %}}", - "child": "{% if default %}{% extends default %}{% else %}" - "{% extends 'default1' %}{% endif %}{%- block x %}" - "CHILD{% endblock %}", - } - ) - ) - tmpl = env.get_template("child") - with pytest.raises(TemplateSyntaxError): - tmpl.render(default="default1", seq=list(range(3))) - tmpl.render(default="default2", seq=list(range(3))) - - -class TestBugFix: + """, + } + ) + ) + rv = env.get_template("index.html").render(the_foo=42).split() + assert rv == ["43", "44", "45"] + + def test_level1_required(self, env): + env = Environment( + loader=DictLoader( + { + "default": "{% block x required %}{# comment #}\n {% endblock %}", + "level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}", + } + ) + ) + rv = env.get_template("level1").render() + assert rv == "[1]" + + def test_level2_required(self, env): + env = Environment( + loader=DictLoader( + { + "default": "{% block x required %}{% endblock %}", + "level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}", + "level2": "{% extends 'default' %}{% block x %}[2]{% endblock %}", + } + ) + ) + rv1 = env.get_template("level1").render() + rv2 = env.get_template("level2").render() + + assert rv1 == "[1]" + assert rv2 == "[2]" + + def test_level3_required(self, env): + env = Environment( + loader=DictLoader( + { + "default": "{% block x required %}{% endblock %}", + "level1": "{% extends 'default' %}", + "level2": "{% extends 'level1' %}{% block x %}[2]{% endblock %}", + "level3": "{% extends 'level2' %}", + } + ) + ) + t1 = env.get_template("level1") + t2 = env.get_template("level2") + t3 = env.get_template("level3") + + with pytest.raises(TemplateRuntimeError, match="Required block 'x' not found"): + assert t1.render() + + assert t2.render() == "[2]" + assert t3.render() == "[2]" + + def test_invalid_required(self, env): + env = Environment( + loader=DictLoader( + { + "default": "{% block x required %}data {# #}{% endblock %}", + "default1": "{% block x required %}{% block y %}" + "{% endblock %} {% endblock %}", + "default2": "{% block x required %}{% if true %}" + "{% endif %} {% endblock %}", + "level1": "{% if default %}{% extends default %}" + "{% else %}{% extends 'default' %}{% endif %}" + "{%- block x %}CHILD{% endblock %}", + } + ) + ) + t = env.get_template("level1") + + with pytest.raises( + TemplateSyntaxError, + match="Required blocks can only contain comments or whitespace", + ): + assert t.render(default="default") + assert t.render(default="default2") + assert t.render(default="default3") + + def test_required_with_scope(self, env): + env = Environment( + loader=DictLoader( + { + "default1": "{% for item in seq %}[{% block item scoped required %}" + "{% endblock %}]{% endfor %}", + "child1": "{% extends 'default1' %}{% block item %}" + "{{ item }}{% endblock %}", + "default2": "{% for item in seq %}[{% block item required scoped %}" + "{% endblock %}]{% endfor %}", + "child2": "{% extends 'default2' %}{% block item %}" + "{{ item }}{% endblock %}", + } + ) + ) + t1 = env.get_template("child1") + t2 = env.get_template("child2") + + assert t1.render(seq=list(range(3))) == "[0][1][2]" + + # scoped must come before required + with pytest.raises(TemplateSyntaxError): + t2.render(seq=list(range(3))) + + def test_duplicate_required_or_scoped(self, env): + env = Environment( + loader=DictLoader( + { + "default1": "{% for item in seq %}[{% block item " + "scoped scoped %}}{{% endblock %}}]{{% endfor %}}", + "default2": "{% for item in seq %}[{% block item " + "required required %}}{{% endblock %}}]{{% endfor %}}", + "child": "{% if default %}{% extends default %}{% else %}" + "{% extends 'default1' %}{% endif %}{%- block x %}" + "CHILD{% endblock %}", + } + ) + ) + tmpl = env.get_template("child") + with pytest.raises(TemplateSyntaxError): + tmpl.render(default="default1", seq=list(range(3))) + tmpl.render(default="default2", seq=list(range(3))) + + +class TestBugFix: def test_fixed_macro_scoping_bug(self, env): - assert ( - Environment( - loader=DictLoader( - { - "test.html": """\ + assert ( + Environment( + loader=DictLoader( + { + "test.html": """\ {% extends 'details.html' %} {% macro my_macro() %} @@ -368,8 +368,8 @@ class TestBugFix: {% block inner_box %} {{ my_macro() }} {% endblock %} - """, - "details.html": """\ + """, + "details.html": """\ {% extends 'standard.html' %} {% macro my_macro() %} @@ -384,22 +384,22 @@ class TestBugFix: {% endblock %} {% endblock %} {% endblock %} - """, - "standard.html": """ + """, + "standard.html": """ {% block content %} {% endblock %} - """, - } - ) - ) - .get_template("test.html") - .render() - .split() - == ["outer_box", "my_macro"] - ) + """, + } + ) + ) + .get_template("test.html") + .render() + .split() + == ["outer_box", "my_macro"] + ) def test_double_extends(self, env): """Ensures that a template with more than 1 {% extends ... %} usage raises a ``TemplateError``. """ - with pytest.raises(TemplateRuntimeError, match="extended multiple times"): - env.get_template("doublee").render() + with pytest.raises(TemplateRuntimeError, match="extended multiple times"): + env.get_template("doublee").render() diff --git a/contrib/python/Jinja2/py3/tests/test_lexnparse.py b/contrib/python/Jinja2/py3/tests/test_lexnparse.py index c02adad5a9..2f6c6754ef 100644 --- a/contrib/python/Jinja2/py3/tests/test_lexnparse.py +++ b/contrib/python/Jinja2/py3/tests/test_lexnparse.py @@ -1,22 +1,22 @@ import pytest -from jinja2 import Environment -from jinja2 import nodes -from jinja2 import Template -from jinja2 import TemplateSyntaxError -from jinja2 import UndefinedError -from jinja2.lexer import Token -from jinja2.lexer import TOKEN_BLOCK_BEGIN -from jinja2.lexer import TOKEN_BLOCK_END -from jinja2.lexer import TOKEN_EOF -from jinja2.lexer import TokenStream - - -class TestTokenStream: - test_tokens = [ - Token(1, TOKEN_BLOCK_BEGIN, ""), - Token(2, TOKEN_BLOCK_END, ""), - ] +from jinja2 import Environment +from jinja2 import nodes +from jinja2 import Template +from jinja2 import TemplateSyntaxError +from jinja2 import UndefinedError +from jinja2.lexer import Token +from jinja2.lexer import TOKEN_BLOCK_BEGIN +from jinja2.lexer import TOKEN_BLOCK_END +from jinja2.lexer import TOKEN_EOF +from jinja2.lexer import TokenStream + + +class TestTokenStream: + test_tokens = [ + Token(1, TOKEN_BLOCK_BEGIN, ""), + Token(2, TOKEN_BLOCK_END, ""), + ] def test_simple(self, env): ts = TokenStream(self.test_tokens, "foo", "bar") @@ -33,998 +33,998 @@ class TestTokenStream: assert bool(ts.eos) def test_iter(self, env): - token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")] - assert token_types == [ - "block_begin", - "block_end", + token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")] + assert token_types == [ + "block_begin", + "block_end", ] -class TestLexer: +class TestLexer: def test_raw1(self, env): tmpl = env.from_string( - "{% raw %}foo{% endraw %}|" - "{%raw%}{{ bar }}|{% baz %}{% endraw %}" - ) - assert tmpl.render() == "foo|{{ bar }}|{% baz %}" + "{% raw %}foo{% endraw %}|" + "{%raw%}{{ bar }}|{% baz %}{% endraw %}" + ) + assert tmpl.render() == "foo|{{ bar }}|{% baz %}" def test_raw2(self, env): - tmpl = env.from_string("1 {%- raw -%} 2 {%- endraw -%} 3") - assert tmpl.render() == "123" - - def test_raw3(self, env): - # The second newline after baz exists because it is AFTER the - # {% raw %} and is ignored. - env = Environment(lstrip_blocks=True, trim_blocks=True) - tmpl = env.from_string("bar\n{% raw %}\n {{baz}}2 spaces\n{% endraw %}\nfoo") - assert tmpl.render(baz="test") == "bar\n\n {{baz}}2 spaces\nfoo" - - def test_raw4(self, env): - # The trailing dash of the {% raw -%} cleans both the spaces and - # newlines up to the first character of data. - env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string( - "bar\n{%- raw -%}\n\n \n 2 spaces\n space{%- endraw -%}\nfoo" - ) - assert tmpl.render() == "bar2 spaces\n spacefoo" - + tmpl = env.from_string("1 {%- raw -%} 2 {%- endraw -%} 3") + assert tmpl.render() == "123" + + def test_raw3(self, env): + # The second newline after baz exists because it is AFTER the + # {% raw %} and is ignored. + env = Environment(lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string("bar\n{% raw %}\n {{baz}}2 spaces\n{% endraw %}\nfoo") + assert tmpl.render(baz="test") == "bar\n\n {{baz}}2 spaces\nfoo" + + def test_raw4(self, env): + # The trailing dash of the {% raw -%} cleans both the spaces and + # newlines up to the first character of data. + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string( + "bar\n{%- raw -%}\n\n \n 2 spaces\n space{%- endraw -%}\nfoo" + ) + assert tmpl.render() == "bar2 spaces\n spacefoo" + def test_balancing(self, env): - env = Environment("{%", "%}", "${", "}") - tmpl = env.from_string( - """{% for item in seq - %}${{'foo': item}|upper}{% endfor %}""" - ) - assert tmpl.render(seq=list(range(3))) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}" + env = Environment("{%", "%}", "${", "}") + tmpl = env.from_string( + """{% for item in seq + %}${{'foo': item}|upper}{% endfor %}""" + ) + assert tmpl.render(seq=list(range(3))) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}" def test_comments(self, env): - env = Environment("<!--", "-->", "{", "}") - tmpl = env.from_string( - """\ + env = Environment("<!--", "-->", "{", "}") + tmpl = env.from_string( + """\ <ul> <!--- for item in seq --> <li>{item}</li> <!--- endfor --> -</ul>""" - ) - assert tmpl.render(seq=list(range(3))) == ( - "<ul>\n <li>0</li>\n <li>1</li>\n <li>2</li>\n</ul>" - ) +</ul>""" + ) + assert tmpl.render(seq=list(range(3))) == ( + "<ul>\n <li>0</li>\n <li>1</li>\n <li>2</li>\n</ul>" + ) def test_string_escapes(self, env): - for char in "\0", "\u2668", "\xe4", "\t", "\r", "\n": - tmpl = env.from_string(f"{{{{ {char!r} }}}}") + for char in "\0", "\u2668", "\xe4", "\t", "\r", "\n": + tmpl = env.from_string(f"{{{{ {char!r} }}}}") assert tmpl.render() == char - assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == "\u2668" + assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == "\u2668" def test_bytefallback(self, env): from pprint import pformat - tmpl = env.from_string("""{{ 'foo'|pprint }}|{{ 'bär'|pprint }}""") - assert tmpl.render() == pformat("foo") + "|" + pformat("bär") - + tmpl = env.from_string("""{{ 'foo'|pprint }}|{{ 'bär'|pprint }}""") + assert tmpl.render() == pformat("foo") + "|" + pformat("bär") + def test_operators(self, env): from jinja2.lexer import operators - - for test, expect in operators.items(): - if test in "([{}])": + + for test, expect in operators.items(): + if test in "([{}])": continue - stream = env.lexer.tokenize(f"{{{{ {test} }}}}") + stream = env.lexer.tokenize(f"{{{{ {test} }}}}") next(stream) assert stream.current.type == expect def test_normalizing(self, env): - for seq in "\r", "\r\n", "\n": + for seq in "\r", "\r\n", "\n": env = Environment(newline_sequence=seq) - tmpl = env.from_string("1\n2\r\n3\n4\n") + tmpl = env.from_string("1\n2\r\n3\n4\n") result = tmpl.render() - assert result.replace(seq, "X") == "1X2X3X4" + assert result.replace(seq, "X") == "1X2X3X4" def test_trailing_newline(self, env): for keep in [True, False]: env = Environment(keep_trailing_newline=keep) for template, expected in [ - ("", {}), - ("no\nnewline", {}), - ("with\nnewline\n", {False: "with\nnewline"}), - ("with\nseveral\n\n\n", {False: "with\nseveral\n\n"}), - ]: + ("", {}), + ("no\nnewline", {}), + ("with\nnewline\n", {False: "with\nnewline"}), + ("with\nseveral\n\n\n", {False: "with\nseveral\n\n"}), + ]: tmpl = env.from_string(template) expect = expected.get(keep, template) result = tmpl.render() assert result == expect, (keep, template, result, expect) - @pytest.mark.parametrize( - ("name", "valid"), - [ - ("foo", True), - ("föö", True), - ("き", True), - ("_", True), - ("1a", False), # invalid ascii start - ("a-", False), # invalid ascii continue - ("\U0001f40da", False), # invalid unicode start - ("a🐍\U0001f40d", False), # invalid unicode continue - # start characters not matched by \w - ("\u1885", True), - ("\u1886", True), - ("\u2118", True), - ("\u212e", True), - # continue character not matched by \w - ("\xb7", False), - ("a\xb7", True), - ], - ) - def test_name(self, env, name, valid): - t = "{{ " + name + " }}" - - if valid: + @pytest.mark.parametrize( + ("name", "valid"), + [ + ("foo", True), + ("föö", True), + ("き", True), + ("_", True), + ("1a", False), # invalid ascii start + ("a-", False), # invalid ascii continue + ("\U0001f40da", False), # invalid unicode start + ("a🐍\U0001f40d", False), # invalid unicode continue + # start characters not matched by \w + ("\u1885", True), + ("\u1886", True), + ("\u2118", True), + ("\u212e", True), + # continue character not matched by \w + ("\xb7", False), + ("a\xb7", True), + ], + ) + def test_name(self, env, name, valid): + t = "{{ " + name + " }}" + + if valid: # valid for version being tested, shouldn't raise env.from_string(t) else: pytest.raises(TemplateSyntaxError, env.from_string, t) - def test_lineno_with_strip(self, env): - tokens = env.lex( - """\ -<html> - <body> - {%- block content -%} - <hr> - {{ item }} - {% endblock %} - </body> -</html>""" - ) - for tok in tokens: - lineno, token_type, value = tok - if token_type == "name" and value == "item": - assert lineno == 5 - break - - -class TestParser: + def test_lineno_with_strip(self, env): + tokens = env.lex( + """\ +<html> + <body> + {%- block content -%} + <hr> + {{ item }} + {% endblock %} + </body> +</html>""" + ) + for tok in tokens: + lineno, token_type, value = tok + if token_type == "name" and value == "item": + assert lineno == 5 + break + + +class TestParser: def test_php_syntax(self, env): - env = Environment("<?", "?>", "<?=", "?>", "<!--", "-->") - tmpl = env.from_string( - """\ + env = Environment("<?", "?>", "<?=", "?>", "<!--", "-->") + tmpl = env.from_string( + """\ <!-- I'm a comment, I'm not interesting -->\ <? for item in seq -?> <?= item ?> -<?- endfor ?>""" - ) - assert tmpl.render(seq=list(range(5))) == "01234" +<?- endfor ?>""" + ) + assert tmpl.render(seq=list(range(5))) == "01234" def test_erb_syntax(self, env): - env = Environment("<%", "%>", "<%=", "%>", "<%#", "%>") - tmpl = env.from_string( - """\ + env = Environment("<%", "%>", "<%=", "%>", "<%#", "%>") + tmpl = env.from_string( + """\ <%# I'm a comment, I'm not interesting %>\ <% for item in seq -%> <%= item %> -<%- endfor %>""" - ) - assert tmpl.render(seq=list(range(5))) == "01234" +<%- endfor %>""" + ) + assert tmpl.render(seq=list(range(5))) == "01234" def test_comment_syntax(self, env): - env = Environment("<!--", "-->", "${", "}", "<!--#", "-->") - tmpl = env.from_string( - """\ + env = Environment("<!--", "-->", "${", "}", "<!--#", "-->") + tmpl = env.from_string( + """\ <!--# I'm a comment, I'm not interesting -->\ <!-- for item in seq ---> ${item} -<!--- endfor -->""" - ) - assert tmpl.render(seq=list(range(5))) == "01234" +<!--- endfor -->""" + ) + assert tmpl.render(seq=list(range(5))) == "01234" def test_balancing(self, env): - tmpl = env.from_string("""{{{'foo':'bar'}.foo}}""") - assert tmpl.render() == "bar" + tmpl = env.from_string("""{{{'foo':'bar'}.foo}}""") + assert tmpl.render() == "bar" def test_start_comment(self, env): - tmpl = env.from_string( - """{# foo comment + tmpl = env.from_string( + """{# foo comment and bar comment #} {% macro blub() %}foo{% endmacro %} -{{ blub() }}""" - ) - assert tmpl.render().strip() == "foo" +{{ blub() }}""" + ) + assert tmpl.render().strip() == "foo" def test_line_syntax(self, env): - env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%") - tmpl = env.from_string( - """\ + env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%") + tmpl = env.from_string( + """\ <%# regular comment %> % for item in seq: ${item} -% endfor""" - ) +% endfor""" + ) assert [ int(x.strip()) for x in tmpl.render(seq=list(range(5))).split() ] == list(range(5)) - env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%", "##") - tmpl = env.from_string( - """\ + env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%", "##") + tmpl = env.from_string( + """\ <%# regular comment %> % for item in seq: ${item} ## the rest of the stuff -% endfor""" - ) +% endfor""" + ) assert [ int(x.strip()) for x in tmpl.render(seq=list(range(5))).split() ] == list(range(5)) def test_line_syntax_priority(self, env): # XXX: why is the whitespace there in front of the newline? - env = Environment("{%", "%}", "${", "}", "/*", "*/", "##", "#") - tmpl = env.from_string( - """\ + env = Environment("{%", "%}", "${", "}", "/*", "*/", "##", "#") + tmpl = env.from_string( + """\ /* ignore me. I'm a multiline comment */ ## for item in seq: * ${item} # this is just extra stuff -## endfor""" - ) - assert tmpl.render(seq=[1, 2]).strip() == "* 1\n* 2" - env = Environment("{%", "%}", "${", "}", "/*", "*/", "#", "##") - tmpl = env.from_string( - """\ +## endfor""" + ) + assert tmpl.render(seq=[1, 2]).strip() == "* 1\n* 2" + env = Environment("{%", "%}", "${", "}", "/*", "*/", "#", "##") + tmpl = env.from_string( + """\ /* ignore me. I'm a multiline comment */ # for item in seq: * ${item} ## this is just extra stuff ## extra stuff i just want to ignore -# endfor""" - ) - assert tmpl.render(seq=[1, 2]).strip() == "* 1\n\n* 2" +# endfor""" + ) + assert tmpl.render(seq=[1, 2]).strip() == "* 1\n\n* 2" def test_error_messages(self, env): def assert_error(code, expected): - with pytest.raises(TemplateSyntaxError, match=expected): + with pytest.raises(TemplateSyntaxError, match=expected): Template(code) assert_error( - "{% for item in seq %}...{% endif %}", - "Encountered unknown tag 'endif'. Jinja was looking " - "for the following tags: 'endfor' or 'else'. The " - "innermost block that needs to be closed is 'for'.", - ) - assert_error( - "{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}", + "{% for item in seq %}...{% endif %}", + "Encountered unknown tag 'endif'. Jinja was looking " + "for the following tags: 'endfor' or 'else'. The " + "innermost block that needs to be closed is 'for'.", + ) + assert_error( + "{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}", "Encountered unknown tag 'endfor'. Jinja was looking for " "the following tags: 'elif' or 'else' or 'endif'. The " - "innermost block that needs to be closed is 'if'.", - ) - assert_error( - "{% if foo %}", - "Unexpected end of template. Jinja was looking for the " - "following tags: 'elif' or 'else' or 'endif'. The " - "innermost block that needs to be closed is 'if'.", - ) - assert_error( - "{% for item in seq %}", - "Unexpected end of template. Jinja was looking for the " - "following tags: 'endfor' or 'else'. The innermost block " - "that needs to be closed is 'for'.", - ) + "innermost block that needs to be closed is 'if'.", + ) assert_error( - "{% block foo-bar-baz %}", + "{% if foo %}", + "Unexpected end of template. Jinja was looking for the " + "following tags: 'elif' or 'else' or 'endif'. The " + "innermost block that needs to be closed is 'if'.", + ) + assert_error( + "{% for item in seq %}", + "Unexpected end of template. Jinja was looking for the " + "following tags: 'endfor' or 'else'. The innermost block " + "that needs to be closed is 'for'.", + ) + assert_error( + "{% block foo-bar-baz %}", "Block names in Jinja have to be valid Python identifiers " - "and may not contain hyphens, use an underscore instead.", - ) - assert_error("{% unknown_tag %}", "Encountered unknown tag 'unknown_tag'.") + "and may not contain hyphens, use an underscore instead.", + ) + assert_error("{% unknown_tag %}", "Encountered unknown tag 'unknown_tag'.") -class TestSyntax: +class TestSyntax: def test_call(self, env): env = Environment() - env.globals["foo"] = lambda a, b, c, e, g: a + b + c + e + g - tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}") - assert tmpl.render() == "abdfh" + env.globals["foo"] = lambda a, b, c, e, g: a + b + c + e + g + tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}") + assert tmpl.render() == "abdfh" def test_slicing(self, env): - tmpl = env.from_string("{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}") - assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]" + tmpl = env.from_string("{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}") + assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]" def test_attr(self, env): tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}") - assert tmpl.render(foo={"bar": 42}) == "42|42" + assert tmpl.render(foo={"bar": 42}) == "42|42" def test_subscript(self, env): tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}") - assert tmpl.render(foo=[0, 1, 2]) == "0|2" + assert tmpl.render(foo=[0, 1, 2]) == "0|2" def test_tuple(self, env): - tmpl = env.from_string("{{ () }}|{{ (1,) }}|{{ (1, 2) }}") - assert tmpl.render() == "()|(1,)|(1, 2)" + tmpl = env.from_string("{{ () }}|{{ (1,) }}|{{ (1, 2) }}") + assert tmpl.render() == "()|(1,)|(1, 2)" def test_math(self, env): - tmpl = env.from_string("{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}") - assert tmpl.render() == "1.5|8" + tmpl = env.from_string("{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}") + assert tmpl.render() == "1.5|8" def test_div(self, env): - tmpl = env.from_string("{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}") - assert tmpl.render() == "1|1.5|1" + tmpl = env.from_string("{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}") + assert tmpl.render() == "1|1.5|1" def test_unary(self, env): - tmpl = env.from_string("{{ +3 }}|{{ -3 }}") - assert tmpl.render() == "3|-3" + tmpl = env.from_string("{{ +3 }}|{{ -3 }}") + assert tmpl.render() == "3|-3" def test_concat(self, env): tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}") - assert tmpl.render() == "[1, 2]foo" - - @pytest.mark.parametrize( - ("a", "op", "b"), - [ - (1, ">", 0), - (1, ">=", 1), - (2, "<", 3), - (3, "<=", 4), - (4, "==", 4), - (4, "!=", 5), - ], - ) - def test_compare(self, env, a, op, b): - t = env.from_string(f"{{{{ {a} {op} {b} }}}}") - assert t.render() == "True" - - def test_compare_parens(self, env): - t = env.from_string("{{ i * (j < 5) }}") - assert t.render(i=2, j=3) == "2" - - @pytest.mark.parametrize( - ("src", "expect"), - [ - ("{{ 4 < 2 < 3 }}", "False"), - ("{{ a < b < c }}", "False"), - ("{{ 4 > 2 > 3 }}", "False"), - ("{{ a > b > c }}", "False"), - ("{{ 4 > 2 < 3 }}", "True"), - ("{{ a > b < c }}", "True"), - ], - ) - def test_compare_compound(self, env, src, expect): - t = env.from_string(src) - assert t.render(a=4, b=2, c=3) == expect - + assert tmpl.render() == "[1, 2]foo" + + @pytest.mark.parametrize( + ("a", "op", "b"), + [ + (1, ">", 0), + (1, ">=", 1), + (2, "<", 3), + (3, "<=", 4), + (4, "==", 4), + (4, "!=", 5), + ], + ) + def test_compare(self, env, a, op, b): + t = env.from_string(f"{{{{ {a} {op} {b} }}}}") + assert t.render() == "True" + + def test_compare_parens(self, env): + t = env.from_string("{{ i * (j < 5) }}") + assert t.render(i=2, j=3) == "2" + + @pytest.mark.parametrize( + ("src", "expect"), + [ + ("{{ 4 < 2 < 3 }}", "False"), + ("{{ a < b < c }}", "False"), + ("{{ 4 > 2 > 3 }}", "False"), + ("{{ a > b > c }}", "False"), + ("{{ 4 > 2 < 3 }}", "True"), + ("{{ a > b < c }}", "True"), + ], + ) + def test_compare_compound(self, env, src, expect): + t = env.from_string(src) + assert t.render(a=4, b=2, c=3) == expect + def test_inop(self, env): - tmpl = env.from_string("{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}") - assert tmpl.render() == "True|False" - - @pytest.mark.parametrize("value", ("[]", "{}", "()")) - def test_collection_literal(self, env, value): - t = env.from_string(f"{{{{ {value} }}}}") - assert t.render() == value - - @pytest.mark.parametrize( - ("value", "expect"), - ( - ("1", "1"), - ("123", "123"), - ("12_34_56", "123456"), - ("1.2", "1.2"), - ("34.56", "34.56"), - ("3_4.5_6", "34.56"), - ("1e0", "1.0"), - ("10e1", "100.0"), - ("2.5e100", "2.5e+100"), - ("2.5e+100", "2.5e+100"), - ("25.6e-10", "2.56e-09"), - ("1_2.3_4e5_6", "1.234e+57"), - ("0", "0"), - ("0_00", "0"), - ("0b1001_1111", "159"), - ("0o123", "83"), - ("0o1_23", "83"), - ("0x123abc", "1194684"), - ("0x12_3abc", "1194684"), - ), - ) - def test_numeric_literal(self, env, value, expect): - t = env.from_string(f"{{{{ {value} }}}}") - assert t.render() == expect - + tmpl = env.from_string("{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}") + assert tmpl.render() == "True|False" + + @pytest.mark.parametrize("value", ("[]", "{}", "()")) + def test_collection_literal(self, env, value): + t = env.from_string(f"{{{{ {value} }}}}") + assert t.render() == value + + @pytest.mark.parametrize( + ("value", "expect"), + ( + ("1", "1"), + ("123", "123"), + ("12_34_56", "123456"), + ("1.2", "1.2"), + ("34.56", "34.56"), + ("3_4.5_6", "34.56"), + ("1e0", "1.0"), + ("10e1", "100.0"), + ("2.5e100", "2.5e+100"), + ("2.5e+100", "2.5e+100"), + ("25.6e-10", "2.56e-09"), + ("1_2.3_4e5_6", "1.234e+57"), + ("0", "0"), + ("0_00", "0"), + ("0b1001_1111", "159"), + ("0o123", "83"), + ("0o1_23", "83"), + ("0x123abc", "1194684"), + ("0x12_3abc", "1194684"), + ), + ) + def test_numeric_literal(self, env, value, expect): + t = env.from_string(f"{{{{ {value} }}}}") + assert t.render() == expect + def test_bool(self, env): - tmpl = env.from_string( - "{{ true and false }}|{{ false or true }}|{{ not false }}" - ) - assert tmpl.render() == "False|True|True" + tmpl = env.from_string( + "{{ true and false }}|{{ false or true }}|{{ not false }}" + ) + assert tmpl.render() == "False|True|True" def test_grouping(self, env): tmpl = env.from_string( - "{{ (true and false) or (false and true) and not false }}" - ) - assert tmpl.render() == "False" + "{{ (true and false) or (false and true) and not false }}" + ) + assert tmpl.render() == "False" def test_django_attr(self, env): - tmpl = env.from_string("{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}") - assert tmpl.render() == "1|1" + tmpl = env.from_string("{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}") + assert tmpl.render() == "1|1" def test_conditional_expression(self, env): - tmpl = env.from_string("""{{ 0 if true else 1 }}""") - assert tmpl.render() == "0" + tmpl = env.from_string("""{{ 0 if true else 1 }}""") + assert tmpl.render() == "0" def test_short_conditional_expression(self, env): - tmpl = env.from_string("<{{ 1 if false }}>") - assert tmpl.render() == "<>" + tmpl = env.from_string("<{{ 1 if false }}>") + assert tmpl.render() == "<>" - tmpl = env.from_string("<{{ (1 if false).bar }}>") + tmpl = env.from_string("<{{ (1 if false).bar }}>") pytest.raises(UndefinedError, tmpl.render) def test_filter_priority(self, env): tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}') - assert tmpl.render() == "FOOBAR" + assert tmpl.render() == "FOOBAR" def test_function_calls(self, env): tests = [ - (True, "*foo, bar"), - (True, "*foo, *bar"), - (True, "**foo, *bar"), - (True, "**foo, bar"), - (True, "**foo, **bar"), - (True, "**foo, bar=42"), - (False, "foo, bar"), - (False, "foo, bar=42"), - (False, "foo, bar=23, *args"), - (False, "foo, *args, bar=23"), - (False, "a, b=c, *d, **e"), - (False, "*foo, bar=42"), - (False, "*foo, **bar"), - (False, "*foo, bar=42, **baz"), - (False, "foo, *args, bar=23, **baz"), + (True, "*foo, bar"), + (True, "*foo, *bar"), + (True, "**foo, *bar"), + (True, "**foo, bar"), + (True, "**foo, **bar"), + (True, "**foo, bar=42"), + (False, "foo, bar"), + (False, "foo, bar=42"), + (False, "foo, bar=23, *args"), + (False, "foo, *args, bar=23"), + (False, "a, b=c, *d, **e"), + (False, "*foo, bar=42"), + (False, "*foo, **bar"), + (False, "*foo, bar=42, **baz"), + (False, "foo, *args, bar=23, **baz"), ] for should_fail, sig in tests: if should_fail: - with pytest.raises(TemplateSyntaxError): - env.from_string(f"{{{{ foo({sig}) }}}}") + with pytest.raises(TemplateSyntaxError): + env.from_string(f"{{{{ foo({sig}) }}}}") else: - env.from_string(f"foo({sig})") + env.from_string(f"foo({sig})") def test_tuple_expr(self, env): for tmpl in [ - "{{ () }}", - "{{ (1, 2) }}", - "{{ (1, 2,) }}", - "{{ 1, }}", - "{{ 1, 2 }}", - "{% for foo, bar in seq %}...{% endfor %}", - "{% for x in foo, bar %}...{% endfor %}", - "{% for x in foo, %}...{% endfor %}", + "{{ () }}", + "{{ (1, 2) }}", + "{{ (1, 2,) }}", + "{{ 1, }}", + "{{ 1, 2 }}", + "{% for foo, bar in seq %}...{% endfor %}", + "{% for x in foo, bar %}...{% endfor %}", + "{% for x in foo, %}...{% endfor %}", ]: assert env.from_string(tmpl) def test_trailing_comma(self, env): - tmpl = env.from_string("{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}") - assert tmpl.render().lower() == "(1, 2)|[1, 2]|{1: 2}" + tmpl = env.from_string("{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}") + assert tmpl.render().lower() == "(1, 2)|[1, 2]|{1: 2}" def test_block_end_name(self, env): - env.from_string("{% block foo %}...{% endblock foo %}") - pytest.raises( - TemplateSyntaxError, env.from_string, "{% block x %}{% endblock y %}" - ) + env.from_string("{% block foo %}...{% endblock foo %}") + pytest.raises( + TemplateSyntaxError, env.from_string, "{% block x %}{% endblock y %}" + ) def test_constant_casing(self, env): for const in True, False, None: - const = str(const) - tmpl = env.from_string( - f"{{{{ {const} }}}}|{{{{ {const.lower()} }}}}|{{{{ {const.upper()} }}}}" - ) - assert tmpl.render() == f"{const}|{const}|" + const = str(const) + tmpl = env.from_string( + f"{{{{ {const} }}}}|{{{{ {const.lower()} }}}}|{{{{ {const.upper()} }}}}" + ) + assert tmpl.render() == f"{const}|{const}|" def test_test_chaining(self, env): - pytest.raises( - TemplateSyntaxError, env.from_string, "{{ foo is string is sequence }}" - ) - assert env.from_string("{{ 42 is string or 42 is number }}").render() == "True" + pytest.raises( + TemplateSyntaxError, env.from_string, "{{ foo is string is sequence }}" + ) + assert env.from_string("{{ 42 is string or 42 is number }}").render() == "True" def test_string_concatenation(self, env): tmpl = env.from_string('{{ "foo" "bar" "baz" }}') - assert tmpl.render() == "foobarbaz" + assert tmpl.render() == "foobarbaz" def test_notin(self, env): bar = range(100) - tmpl = env.from_string("""{{ not 42 in bar }}""") - assert tmpl.render(bar=bar) == "False" + tmpl = env.from_string("""{{ not 42 in bar }}""") + assert tmpl.render(bar=bar) == "False" def test_operator_precedence(self, env): - tmpl = env.from_string("""{{ 2 * 3 + 4 % 2 + 1 - 2 }}""") - assert tmpl.render() == "5" + tmpl = env.from_string("""{{ 2 * 3 + 4 % 2 + 1 - 2 }}""") + assert tmpl.render() == "5" def test_implicit_subscribed_tuple(self, env): - class Foo: + class Foo: def __getitem__(self, x): return x - t = env.from_string("{{ foo[1, 2] }}") - assert t.render(foo=Foo()) == "(1, 2)" - + t = env.from_string("{{ foo[1, 2] }}") + assert t.render(foo=Foo()) == "(1, 2)" + def test_raw2(self, env): - tmpl = env.from_string("{% raw %}{{ FOO }} and {% BAR %}{% endraw %}") - assert tmpl.render() == "{{ FOO }} and {% BAR %}" + tmpl = env.from_string("{% raw %}{{ FOO }} and {% BAR %}{% endraw %}") + assert tmpl.render() == "{{ FOO }} and {% BAR %}" def test_const(self, env): tmpl = env.from_string( - "{{ true }}|{{ false }}|{{ none }}|" - "{{ none is defined }}|{{ missing is defined }}" - ) - assert tmpl.render() == "True|False|None|True|False" + "{{ true }}|{{ false }}|{{ none }}|" + "{{ none is defined }}|{{ missing is defined }}" + ) + assert tmpl.render() == "True|False|None|True|False" def test_neg_filter_priority(self, env): - node = env.parse("{{ -1|foo }}") + node = env.parse("{{ -1|foo }}") assert isinstance(node.body[0].nodes[0], nodes.Filter) assert isinstance(node.body[0].nodes[0].node, nodes.Neg) def test_const_assign(self, env): - constass1 = """{% set true = 42 %}""" - constass2 = """{% for none in seq %}{% endfor %}""" + constass1 = """{% set true = 42 %}""" + constass2 = """{% for none in seq %}{% endfor %}""" for tmpl in constass1, constass2: pytest.raises(TemplateSyntaxError, env.from_string, tmpl) def test_localset(self, env): - tmpl = env.from_string( - """{% set foo = 0 %}\ + tmpl = env.from_string( + """{% set foo = 0 %}\ {% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\ -{{ foo }}""" - ) - assert tmpl.render() == "0" +{{ foo }}""" + ) + assert tmpl.render() == "0" def test_parse_unary(self, env): tmpl = env.from_string('{{ -foo["bar"] }}') - assert tmpl.render(foo={"bar": 42}) == "-42" + assert tmpl.render(foo={"bar": 42}) == "-42" tmpl = env.from_string('{{ -foo["bar"]|abs }}') - assert tmpl.render(foo={"bar": 42}) == "42" + assert tmpl.render(foo={"bar": 42}) == "42" -class TestLstripBlocks: +class TestLstripBlocks: def test_lstrip(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string(""" {% if True %}\n {% endif %}""") + tmpl = env.from_string(""" {% if True %}\n {% endif %}""") assert tmpl.render() == "\n" def test_lstrip_trim(self, env): env = Environment(lstrip_blocks=True, trim_blocks=True) - tmpl = env.from_string(""" {% if True %}\n {% endif %}""") + tmpl = env.from_string(""" {% if True %}\n {% endif %}""") assert tmpl.render() == "" def test_no_lstrip(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) - 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 %}""") + 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(""" hello{% if True %}\n goodbye{% endif %}""") + tmpl = env.from_string(""" hello{% if True %}\n goodbye{% endif %}""") assert tmpl.render() == " hello\n goodbye" def test_lstrip_inline(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string(""" {% if True %}hello {% endif %}""") - assert tmpl.render() == "hello " + tmpl = env.from_string(""" {% if True %}hello {% endif %}""") + assert tmpl.render() == "hello " def test_lstrip_nested(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) tmpl = env.from_string( - """ {% if True %}a {% if True %}b {% endif %}c {% endif %}""" - ) - assert tmpl.render() == "a b c " + """ {% if True %}a {% if True %}b {% endif %}c {% endif %}""" + ) + assert tmpl.render() == "a b c " def test_lstrip_left_chars(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string( - """ abc {% if True %} - hello{% endif %}""" - ) - assert tmpl.render() == " abc \n hello" + tmpl = env.from_string( + """ abc {% if True %} + hello{% endif %}""" + ) + assert tmpl.render() == " abc \n hello" def test_lstrip_embeded_strings(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string(""" {% set x = " {% str %} " %}{{ x }}""") - assert tmpl.render() == " {% str %} " + tmpl = env.from_string(""" {% set x = " {% str %} " %}{{ x }}""") + assert tmpl.render() == " {% str %} " def test_lstrip_preserve_leading_newlines(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string("""\n\n\n{% set hello = 1 %}""") - assert tmpl.render() == "\n\n\n" + tmpl = env.from_string("""\n\n\n{% set hello = 1 %}""") + assert tmpl.render() == "\n\n\n" def test_lstrip_comment(self, env): env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string( - """ {# if True #} + tmpl = env.from_string( + """ {# if True #} hello - {#endif#}""" - ) - assert tmpl.render() == "\nhello\n" + {#endif#}""" + ) + assert tmpl.render() == "\nhello\n" def test_lstrip_angle_bracket_simple(self, env): - env = Environment( - "<%", - "%>", - "${", - "}", - "<%#", - "%>", - "%", - "##", - lstrip_blocks=True, - trim_blocks=True, - ) - tmpl = env.from_string(""" <% if True %>hello <% endif %>""") - assert tmpl.render() == "hello " + env = Environment( + "<%", + "%>", + "${", + "}", + "<%#", + "%>", + "%", + "##", + lstrip_blocks=True, + trim_blocks=True, + ) + tmpl = env.from_string(""" <% if True %>hello <% endif %>""") + assert tmpl.render() == "hello " def test_lstrip_angle_bracket_comment(self, env): - env = Environment( - "<%", - "%>", - "${", - "}", - "<%#", - "%>", - "%", - "##", - lstrip_blocks=True, - trim_blocks=True, - ) - tmpl = env.from_string(""" <%# if True %>hello <%# endif %>""") - assert tmpl.render() == "hello " + env = Environment( + "<%", + "%>", + "${", + "}", + "<%#", + "%>", + "%", + "##", + lstrip_blocks=True, + trim_blocks=True, + ) + tmpl = env.from_string(""" <%# if True %>hello <%# endif %>""") + assert tmpl.render() == "hello " def test_lstrip_angle_bracket(self, env): - env = Environment( - "<%", - "%>", - "${", - "}", - "<%#", - "%>", - "%", - "##", - lstrip_blocks=True, - trim_blocks=True, - ) - tmpl = env.from_string( - """\ + env = Environment( + "<%", + "%>", + "${", + "}", + "<%#", + "%>", + "%", + "##", + lstrip_blocks=True, + trim_blocks=True, + ) + tmpl = env.from_string( + """\ <%# regular comment %> <% for item in seq %> ${item} ## the rest of the stuff - <% endfor %>""" - ) - assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5)) + <% endfor %>""" + ) + assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5)) def test_lstrip_angle_bracket_compact(self, env): - env = Environment( - "<%", - "%>", - "${", - "}", - "<%#", - "%>", - "%", - "##", - lstrip_blocks=True, - trim_blocks=True, - ) - tmpl = env.from_string( - """\ + env = Environment( + "<%", + "%>", + "${", + "}", + "<%#", + "%>", + "%", + "##", + lstrip_blocks=True, + trim_blocks=True, + ) + tmpl = env.from_string( + """\ <%#regular comment%> <%for item in seq%> ${item} ## the rest of the stuff - <%endfor%>""" - ) - assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5)) - - def test_lstrip_blocks_outside_with_new_line(self): - env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string( - " {% if kvs %}(\n" - " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" - " ){% endif %}" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == "(\na=1 b=2 \n )" - - def test_lstrip_trim_blocks_outside_with_new_line(self): - env = Environment(lstrip_blocks=True, trim_blocks=True) - tmpl = env.from_string( - " {% if kvs %}(\n" - " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" - " ){% endif %}" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == "(\na=1 b=2 )" - - def test_lstrip_blocks_inside_with_new_line(self): - env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string( - " ({% if kvs %}\n" - " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" - " {% endif %})" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == " (\na=1 b=2 \n)" - - def test_lstrip_trim_blocks_inside_with_new_line(self): - env = Environment(lstrip_blocks=True, trim_blocks=True) - tmpl = env.from_string( - " ({% if kvs %}\n" - " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" - " {% endif %})" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == " (a=1 b=2 )" - - def test_lstrip_blocks_without_new_line(self): - env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string( - " {% if kvs %}" - " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}" - " {% endif %}" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == " a=1 b=2 " - - def test_lstrip_trim_blocks_without_new_line(self): - env = Environment(lstrip_blocks=True, trim_blocks=True) - tmpl = env.from_string( - " {% if kvs %}" - " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}" - " {% endif %}" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == " a=1 b=2 " - - def test_lstrip_blocks_consume_after_without_new_line(self): - env = Environment(lstrip_blocks=True, trim_blocks=False) - tmpl = env.from_string( - " {% if kvs -%}" - " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}" - " {% endif -%}" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == "a=1 b=2 " - - def test_lstrip_trim_blocks_consume_before_without_new_line(self): - env = Environment(lstrip_blocks=False, trim_blocks=False) - tmpl = env.from_string( - " {%- if kvs %}" - " {%- for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}" - " {%- endif %}" - ) - out = tmpl.render(kvs=[("a", 1), ("b", 2)]) - assert out == "a=1 b=2 " - - def test_lstrip_trim_blocks_comment(self): - env = Environment(lstrip_blocks=True, trim_blocks=True) - tmpl = env.from_string(" {# 1 space #}\n {# 2 spaces #} {# 4 spaces #}") - out = tmpl.render() - assert out == " " * 4 - - def test_lstrip_trim_blocks_raw(self): - env = Environment(lstrip_blocks=True, trim_blocks=True) - tmpl = env.from_string("{{x}}\n{%- raw %} {% endraw -%}\n{{ y }}") - out = tmpl.render(x=1, y=2) - assert out == "1 2" - + <%endfor%>""" + ) + assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5)) + + def test_lstrip_blocks_outside_with_new_line(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string( + " {% if kvs %}(\n" + " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" + " ){% endif %}" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == "(\na=1 b=2 \n )" + + def test_lstrip_trim_blocks_outside_with_new_line(self): + env = Environment(lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string( + " {% if kvs %}(\n" + " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" + " ){% endif %}" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == "(\na=1 b=2 )" + + def test_lstrip_blocks_inside_with_new_line(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string( + " ({% if kvs %}\n" + " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" + " {% endif %})" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == " (\na=1 b=2 \n)" + + def test_lstrip_trim_blocks_inside_with_new_line(self): + env = Environment(lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string( + " ({% if kvs %}\n" + " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" + " {% endif %})" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == " (a=1 b=2 )" + + def test_lstrip_blocks_without_new_line(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string( + " {% if kvs %}" + " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}" + " {% endif %}" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == " a=1 b=2 " + + def test_lstrip_trim_blocks_without_new_line(self): + env = Environment(lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string( + " {% if kvs %}" + " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}" + " {% endif %}" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == " a=1 b=2 " + + def test_lstrip_blocks_consume_after_without_new_line(self): + env = Environment(lstrip_blocks=True, trim_blocks=False) + tmpl = env.from_string( + " {% if kvs -%}" + " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}" + " {% endif -%}" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == "a=1 b=2 " + + def test_lstrip_trim_blocks_consume_before_without_new_line(self): + env = Environment(lstrip_blocks=False, trim_blocks=False) + tmpl = env.from_string( + " {%- if kvs %}" + " {%- for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}" + " {%- endif %}" + ) + out = tmpl.render(kvs=[("a", 1), ("b", 2)]) + assert out == "a=1 b=2 " + + def test_lstrip_trim_blocks_comment(self): + env = Environment(lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string(" {# 1 space #}\n {# 2 spaces #} {# 4 spaces #}") + out = tmpl.render() + assert out == " " * 4 + + def test_lstrip_trim_blocks_raw(self): + env = Environment(lstrip_blocks=True, trim_blocks=True) + tmpl = env.from_string("{{x}}\n{%- raw %} {% endraw -%}\n{{ y }}") + out = tmpl.render(x=1, y=2) + assert out == "1 2" + def test_php_syntax_with_manual(self, env): - env = Environment( - "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True - ) - tmpl = env.from_string( - """\ + env = Environment( + "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True + ) + tmpl = env.from_string( + """\ <!-- I'm a comment, I'm not interesting --> <? for item in seq -?> <?= item ?> - <?- endfor ?>""" - ) - assert tmpl.render(seq=range(5)) == "01234" + <?- endfor ?>""" + ) + assert tmpl.render(seq=range(5)) == "01234" def test_php_syntax(self, env): - env = Environment( - "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True - ) - tmpl = env.from_string( - """\ + env = Environment( + "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True + ) + tmpl = env.from_string( + """\ <!-- I'm a comment, I'm not interesting --> <? for item in seq ?> <?= item ?> - <? endfor ?>""" - ) - assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) + <? endfor ?>""" + ) + assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) def test_php_syntax_compact(self, env): - env = Environment( - "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True - ) - tmpl = env.from_string( - """\ + env = Environment( + "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True + ) + tmpl = env.from_string( + """\ <!-- I'm a comment, I'm not interesting --> <?for item in seq?> <?=item?> - <?endfor?>""" - ) - assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) + <?endfor?>""" + ) + assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) def test_erb_syntax(self, env): - env = Environment( - "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True - ) - tmpl = env.from_string( - """\ + env = Environment( + "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True + ) + tmpl = env.from_string( + """\ <%# I'm a comment, I'm not interesting %> <% for item in seq %> <%= item %> <% endfor %> -""" - ) - assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) +""" + ) + assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) def test_erb_syntax_with_manual(self, env): - env = Environment( - "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True - ) - tmpl = env.from_string( - """\ + env = Environment( + "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True + ) + tmpl = env.from_string( + """\ <%# I'm a comment, I'm not interesting %> <% for item in seq -%> <%= item %> - <%- endfor %>""" - ) - assert tmpl.render(seq=range(5)) == "01234" + <%- endfor %>""" + ) + assert tmpl.render(seq=range(5)) == "01234" def test_erb_syntax_no_lstrip(self, env): - env = Environment( - "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True - ) - tmpl = env.from_string( - """\ + env = Environment( + "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True + ) + tmpl = env.from_string( + """\ <%# I'm a comment, I'm not interesting %> <%+ for item in seq -%> <%= item %> - <%- endfor %>""" - ) - assert tmpl.render(seq=range(5)) == " 01234" + <%- endfor %>""" + ) + assert tmpl.render(seq=range(5)) == " 01234" def test_comment_syntax(self, env): - env = Environment( - "<!--", - "-->", - "${", - "}", - "<!--#", - "-->", - lstrip_blocks=True, - trim_blocks=True, - ) - tmpl = env.from_string( - """\ + env = Environment( + "<!--", + "-->", + "${", + "}", + "<!--#", + "-->", + lstrip_blocks=True, + trim_blocks=True, + ) + tmpl = env.from_string( + """\ <!--# I'm a comment, I'm not interesting -->\ <!-- for item in seq ---> ${item} -<!--- endfor -->""" - ) - assert tmpl.render(seq=range(5)) == "01234" - - -class TestTrimBlocks: - def test_trim(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=False) - tmpl = env.from_string(" {% if True %}\n {% endif %}") - assert tmpl.render() == " " - - def test_no_trim(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=False) - tmpl = env.from_string(" {% if True +%}\n {% endif %}") - assert tmpl.render() == " \n " - - def test_no_trim_outer(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=False) - tmpl = env.from_string("{% if True %}X{% endif +%}\nmore things") - assert tmpl.render() == "X\nmore things" - - def test_lstrip_no_trim(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string(" {% if True +%}\n {% endif %}") - assert tmpl.render() == "\n" - - def test_trim_blocks_false_with_no_trim(self, env): - # Test that + is a NOP (but does not cause an error) if trim_blocks=False - env = Environment(trim_blocks=False, lstrip_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 " - - tmpl = env.from_string(" {# comment #}\n ") - assert tmpl.render() == " \n " - tmpl = env.from_string(" {# comment +#}\n ") - assert tmpl.render() == " \n " - - tmpl = env.from_string(" {% raw %}{% endraw %}\n ") - assert tmpl.render() == " \n " - tmpl = env.from_string(" {% raw %}{% endraw +%}\n ") - assert tmpl.render() == " \n " - - def test_trim_nested(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string( - " {% if True %}\na {% if True %}\nb {% endif %}\nc {% endif %}" - ) - assert tmpl.render() == "a b c " - - def test_no_trim_nested(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string( - " {% if True +%}\na {% if True +%}\nb {% endif +%}\nc {% endif %}" - ) - assert tmpl.render() == "\na \nb \nc " - - def test_comment_trim(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string(""" {# comment #}\n\n """) - assert tmpl.render() == "\n " - - def test_comment_no_trim(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string(""" {# comment +#}\n\n """) - assert tmpl.render() == "\n\n " - - def test_multiple_comment_trim_lstrip(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string( - " {# comment #}\n\n{# comment2 #}\n \n{# comment3 #}\n\n " - ) - assert tmpl.render() == "\n \n\n " - - def test_multiple_comment_no_trim_lstrip(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string( - " {# comment +#}\n\n{# comment2 +#}\n \n{# comment3 +#}\n\n " - ) - assert tmpl.render() == "\n\n\n \n\n\n " - - def test_raw_trim_lstrip(self, env): - env = Environment(trim_blocks=True, lstrip_blocks=True) - tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw %}\n\n{{ y }}") - assert tmpl.render(x=1, y=2) == "1\n\n\n2" - - def test_raw_no_trim_lstrip(self, env): - env = Environment(trim_blocks=False, lstrip_blocks=True) - tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw +%}\n\n{{ y }}") - assert tmpl.render(x=1, y=2) == "1\n\n\n\n2" - - # raw blocks do not process inner text, so start tag cannot ignore trim - with pytest.raises(TemplateSyntaxError): - tmpl = env.from_string("{{x}}{% raw +%}\n\n {% endraw +%}\n\n{{ y }}") - - def test_no_trim_angle_bracket(self, env): - env = Environment( - "<%", "%>", "${", "}", "<%#", "%>", lstrip_blocks=True, trim_blocks=True - ) - tmpl = env.from_string(" <% if True +%>\n\n <% endif %>") - assert tmpl.render() == "\n\n" - - tmpl = env.from_string(" <%# comment +%>\n\n ") - assert tmpl.render() == "\n\n " - - def test_no_trim_php_syntax(self, env): - env = Environment( - "<?", - "?>", - "<?=", - "?>", - "<!--", - "-->", - lstrip_blocks=False, - trim_blocks=True, - ) - tmpl = env.from_string(" <? if True +?>\n\n <? endif ?>") - assert tmpl.render() == " \n\n " - tmpl = env.from_string(" <!-- comment +-->\n\n ") - assert tmpl.render() == " \n\n " +<!--- endfor -->""" + ) + assert tmpl.render(seq=range(5)) == "01234" + + +class TestTrimBlocks: + def test_trim(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=False) + tmpl = env.from_string(" {% if True %}\n {% endif %}") + assert tmpl.render() == " " + + def test_no_trim(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=False) + tmpl = env.from_string(" {% if True +%}\n {% endif %}") + assert tmpl.render() == " \n " + + def test_no_trim_outer(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=False) + tmpl = env.from_string("{% if True %}X{% endif +%}\nmore things") + assert tmpl.render() == "X\nmore things" + + def test_lstrip_no_trim(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string(" {% if True +%}\n {% endif %}") + assert tmpl.render() == "\n" + + def test_trim_blocks_false_with_no_trim(self, env): + # Test that + is a NOP (but does not cause an error) if trim_blocks=False + env = Environment(trim_blocks=False, lstrip_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 " + + tmpl = env.from_string(" {# comment #}\n ") + assert tmpl.render() == " \n " + tmpl = env.from_string(" {# comment +#}\n ") + assert tmpl.render() == " \n " + + tmpl = env.from_string(" {% raw %}{% endraw %}\n ") + assert tmpl.render() == " \n " + tmpl = env.from_string(" {% raw %}{% endraw +%}\n ") + assert tmpl.render() == " \n " + + def test_trim_nested(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string( + " {% if True %}\na {% if True %}\nb {% endif %}\nc {% endif %}" + ) + assert tmpl.render() == "a b c " + + def test_no_trim_nested(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string( + " {% if True +%}\na {% if True +%}\nb {% endif +%}\nc {% endif %}" + ) + assert tmpl.render() == "\na \nb \nc " + + def test_comment_trim(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string(""" {# comment #}\n\n """) + assert tmpl.render() == "\n " + + def test_comment_no_trim(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string(""" {# comment +#}\n\n """) + assert tmpl.render() == "\n\n " + + def test_multiple_comment_trim_lstrip(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string( + " {# comment #}\n\n{# comment2 #}\n \n{# comment3 #}\n\n " + ) + assert tmpl.render() == "\n \n\n " + + def test_multiple_comment_no_trim_lstrip(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string( + " {# comment +#}\n\n{# comment2 +#}\n \n{# comment3 +#}\n\n " + ) + assert tmpl.render() == "\n\n\n \n\n\n " + + def test_raw_trim_lstrip(self, env): + env = Environment(trim_blocks=True, lstrip_blocks=True) + tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw %}\n\n{{ y }}") + assert tmpl.render(x=1, y=2) == "1\n\n\n2" + + def test_raw_no_trim_lstrip(self, env): + env = Environment(trim_blocks=False, lstrip_blocks=True) + tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw +%}\n\n{{ y }}") + assert tmpl.render(x=1, y=2) == "1\n\n\n\n2" + + # raw blocks do not process inner text, so start tag cannot ignore trim + with pytest.raises(TemplateSyntaxError): + tmpl = env.from_string("{{x}}{% raw +%}\n\n {% endraw +%}\n\n{{ y }}") + + def test_no_trim_angle_bracket(self, env): + env = Environment( + "<%", "%>", "${", "}", "<%#", "%>", lstrip_blocks=True, trim_blocks=True + ) + tmpl = env.from_string(" <% if True +%>\n\n <% endif %>") + assert tmpl.render() == "\n\n" + + tmpl = env.from_string(" <%# comment +%>\n\n ") + assert tmpl.render() == "\n\n " + + def test_no_trim_php_syntax(self, env): + env = Environment( + "<?", + "?>", + "<?=", + "?>", + "<!--", + "-->", + lstrip_blocks=False, + trim_blocks=True, + ) + tmpl = env.from_string(" <? if True +?>\n\n <? endif ?>") + assert tmpl.render() == " \n\n " + tmpl = env.from_string(" <!-- comment +-->\n\n ") + assert tmpl.render() == " \n\n " diff --git a/contrib/python/Jinja2/py3/tests/test_loader.py b/contrib/python/Jinja2/py3/tests/test_loader.py index 5042350607..ff07570bc3 100644 --- a/contrib/python/Jinja2/py3/tests/test_loader.py +++ b/contrib/python/Jinja2/py3/tests/test_loader.py @@ -1,197 +1,197 @@ -import importlib.abc -import importlib.machinery -import importlib.util +import importlib.abc +import importlib.machinery +import importlib.util import os -import platform -import shutil +import platform +import shutil import sys import tempfile -import time +import time import weakref -from pathlib import Path - -import pytest - -from jinja2 import Environment -from jinja2 import loaders -from jinja2 import PackageLoader -from jinja2.exceptions import TemplateNotFound +from pathlib import Path + +import pytest + +from jinja2 import Environment +from jinja2 import loaders +from jinja2 import PackageLoader +from jinja2.exceptions import TemplateNotFound from jinja2.loaders import split_template_path -import yatest.common as yc - +import yatest.common as yc -class TestLoaders: + +class TestLoaders: def test_dict_loader(self, dict_loader): env = Environment(loader=dict_loader) - tmpl = env.get_template("justdict.html") - assert tmpl.render().strip() == "FOO" - pytest.raises(TemplateNotFound, env.get_template, "missing.html") + tmpl = env.get_template("justdict.html") + assert tmpl.render().strip() == "FOO" + pytest.raises(TemplateNotFound, env.get_template, "missing.html") def test_package_loader(self, package_loader): env = Environment(loader=package_loader) - tmpl = env.get_template("test.html") - assert tmpl.render().strip() == "BAR" - pytest.raises(TemplateNotFound, env.get_template, "missing.html") - - def test_filesystem_loader_overlapping_names(self, filesystem_loader): - t2_dir = Path(filesystem_loader.searchpath[0]) / ".." / "templates2" - # Make "foo" show up before "foo/test.html". - filesystem_loader.searchpath.insert(0, t2_dir) - e = Environment(loader=filesystem_loader) - e.get_template("foo") - # This would raise NotADirectoryError if "t2/foo" wasn't skipped. - e.get_template("foo/test.html") + tmpl = env.get_template("test.html") + assert tmpl.render().strip() == "BAR" + pytest.raises(TemplateNotFound, env.get_template, "missing.html") + + def test_filesystem_loader_overlapping_names(self, filesystem_loader): + t2_dir = Path(filesystem_loader.searchpath[0]) / ".." / "templates2" + # Make "foo" show up before "foo/test.html". + filesystem_loader.searchpath.insert(0, t2_dir) + e = Environment(loader=filesystem_loader) + e.get_template("foo") + # This would raise NotADirectoryError if "t2/foo" wasn't skipped. + e.get_template("foo/test.html") def test_choice_loader(self, choice_loader): env = Environment(loader=choice_loader) - tmpl = env.get_template("justdict.html") - assert tmpl.render().strip() == "FOO" - tmpl = env.get_template("test.html") - assert tmpl.render().strip() == "BAR" - pytest.raises(TemplateNotFound, env.get_template, "missing.html") + tmpl = env.get_template("justdict.html") + assert tmpl.render().strip() == "FOO" + tmpl = env.get_template("test.html") + assert tmpl.render().strip() == "BAR" + pytest.raises(TemplateNotFound, env.get_template, "missing.html") def test_function_loader(self, function_loader): env = Environment(loader=function_loader) - tmpl = env.get_template("justfunction.html") - assert tmpl.render().strip() == "FOO" - pytest.raises(TemplateNotFound, env.get_template, "missing.html") + tmpl = env.get_template("justfunction.html") + assert tmpl.render().strip() == "FOO" + pytest.raises(TemplateNotFound, env.get_template, "missing.html") def test_prefix_loader(self, prefix_loader): env = Environment(loader=prefix_loader) - tmpl = env.get_template("a/test.html") - assert tmpl.render().strip() == "BAR" - tmpl = env.get_template("b/justdict.html") - assert tmpl.render().strip() == "FOO" - pytest.raises(TemplateNotFound, env.get_template, "missing") + tmpl = env.get_template("a/test.html") + assert tmpl.render().strip() == "BAR" + tmpl = env.get_template("b/justdict.html") + assert tmpl.render().strip() == "FOO" + pytest.raises(TemplateNotFound, env.get_template, "missing") def test_caching(self): changed = False class TestLoader(loaders.BaseLoader): def get_source(self, environment, template): - return "foo", None, lambda: not changed - + return "foo", None, lambda: not changed + env = Environment(loader=TestLoader(), cache_size=-1) - tmpl = env.get_template("template") - assert tmpl is env.get_template("template") + tmpl = env.get_template("template") + assert tmpl is env.get_template("template") changed = True - assert tmpl is not env.get_template("template") + assert tmpl is not env.get_template("template") changed = False def test_no_cache(self): - mapping = {"foo": "one"} + mapping = {"foo": "one"} env = Environment(loader=loaders.DictLoader(mapping), cache_size=0) - assert env.get_template("foo") is not env.get_template("foo") + assert env.get_template("foo") is not env.get_template("foo") def test_limited_size_cache(self): - mapping = {"one": "foo", "two": "bar", "three": "baz"} + mapping = {"one": "foo", "two": "bar", "three": "baz"} loader = loaders.DictLoader(mapping) env = Environment(loader=loader, cache_size=2) - t1 = env.get_template("one") - t2 = env.get_template("two") - assert t2 is env.get_template("two") - assert t1 is env.get_template("one") - env.get_template("three") + t1 = env.get_template("one") + t2 = env.get_template("two") + assert t2 is env.get_template("two") + assert t1 is env.get_template("one") + env.get_template("three") loader_ref = weakref.ref(loader) - assert (loader_ref, "one") in env.cache - assert (loader_ref, "two") not in env.cache - assert (loader_ref, "three") in env.cache + assert (loader_ref, "one") in env.cache + assert (loader_ref, "two") not in env.cache + assert (loader_ref, "three") in env.cache def test_cache_loader_change(self): - loader1 = loaders.DictLoader({"foo": "one"}) - loader2 = loaders.DictLoader({"foo": "two"}) + loader1 = loaders.DictLoader({"foo": "one"}) + loader2 = loaders.DictLoader({"foo": "two"}) env = Environment(loader=loader1, cache_size=2) - assert env.get_template("foo").render() == "one" + assert env.get_template("foo").render() == "one" env.loader = loader2 - assert env.get_template("foo").render() == "two" + assert env.get_template("foo").render() == "two" def test_dict_loader_cache_invalidates(self): - mapping = {"foo": "one"} + mapping = {"foo": "one"} env = Environment(loader=loaders.DictLoader(mapping)) - assert env.get_template("foo").render() == "one" - mapping["foo"] = "two" - assert env.get_template("foo").render() == "two" + assert env.get_template("foo").render() == "one" + mapping["foo"] = "two" + assert env.get_template("foo").render() == "two" def test_split_template_path(self): - assert split_template_path("foo/bar") == ["foo", "bar"] - assert split_template_path("./foo/bar") == ["foo", "bar"] - pytest.raises(TemplateNotFound, split_template_path, "../foo") - - -class TestFileSystemLoader: - searchpath = (Path(yc.test_source_path()) / "res" / "templates").resolve() - - @staticmethod - def _test_common(env): - tmpl = env.get_template("test.html") - assert tmpl.render().strip() == "BAR" - tmpl = env.get_template("foo/test.html") - assert tmpl.render().strip() == "FOO" - pytest.raises(TemplateNotFound, env.get_template, "missing.html") - - def test_searchpath_as_str(self): - filesystem_loader = loaders.FileSystemLoader(str(self.searchpath)) - - env = Environment(loader=filesystem_loader) - self._test_common(env) - - def test_searchpath_as_pathlib(self): - filesystem_loader = loaders.FileSystemLoader(self.searchpath) - env = Environment(loader=filesystem_loader) - self._test_common(env) - - def test_searchpath_as_list_including_pathlib(self): - filesystem_loader = loaders.FileSystemLoader( - ["/tmp/templates", self.searchpath] - ) - env = Environment(loader=filesystem_loader) - self._test_common(env) - - @pytest.mark.skip("Arcadia read only") - def test_caches_template_based_on_mtime(self): - filesystem_loader = loaders.FileSystemLoader(self.searchpath) - - env = Environment(loader=filesystem_loader) - tmpl1 = env.get_template("test.html") - tmpl2 = env.get_template("test.html") - assert tmpl1 is tmpl2 - - os.utime(self.searchpath / "test.html", (time.time(), time.time())) - tmpl3 = env.get_template("test.html") - assert tmpl1 is not tmpl3 - - @pytest.mark.parametrize( - ("encoding", "expect"), - [ - ("utf-8", "文字化け"), - ("iso-8859-1", "æ\x96\x87\xe5\xad\x97\xe5\x8c\x96\xe3\x81\x91"), - ], - ) - def test_uses_specified_encoding(self, encoding, expect): - loader = loaders.FileSystemLoader(self.searchpath, encoding=encoding) - e = Environment(loader=loader) - t = e.get_template("mojibake.txt") - assert t.render() == expect - - -class TestModuleLoader: + assert split_template_path("foo/bar") == ["foo", "bar"] + assert split_template_path("./foo/bar") == ["foo", "bar"] + pytest.raises(TemplateNotFound, split_template_path, "../foo") + + +class TestFileSystemLoader: + searchpath = (Path(yc.test_source_path()) / "res" / "templates").resolve() + + @staticmethod + def _test_common(env): + tmpl = env.get_template("test.html") + assert tmpl.render().strip() == "BAR" + tmpl = env.get_template("foo/test.html") + assert tmpl.render().strip() == "FOO" + pytest.raises(TemplateNotFound, env.get_template, "missing.html") + + def test_searchpath_as_str(self): + filesystem_loader = loaders.FileSystemLoader(str(self.searchpath)) + + env = Environment(loader=filesystem_loader) + self._test_common(env) + + def test_searchpath_as_pathlib(self): + filesystem_loader = loaders.FileSystemLoader(self.searchpath) + env = Environment(loader=filesystem_loader) + self._test_common(env) + + def test_searchpath_as_list_including_pathlib(self): + filesystem_loader = loaders.FileSystemLoader( + ["/tmp/templates", self.searchpath] + ) + env = Environment(loader=filesystem_loader) + self._test_common(env) + + @pytest.mark.skip("Arcadia read only") + def test_caches_template_based_on_mtime(self): + filesystem_loader = loaders.FileSystemLoader(self.searchpath) + + env = Environment(loader=filesystem_loader) + tmpl1 = env.get_template("test.html") + tmpl2 = env.get_template("test.html") + assert tmpl1 is tmpl2 + + os.utime(self.searchpath / "test.html", (time.time(), time.time())) + tmpl3 = env.get_template("test.html") + assert tmpl1 is not tmpl3 + + @pytest.mark.parametrize( + ("encoding", "expect"), + [ + ("utf-8", "文字化け"), + ("iso-8859-1", "æ\x96\x87\xe5\xad\x97\xe5\x8c\x96\xe3\x81\x91"), + ], + ) + def test_uses_specified_encoding(self, encoding, expect): + loader = loaders.FileSystemLoader(self.searchpath, encoding=encoding) + e = Environment(loader=loader) + t = e.get_template("mojibake.txt") + assert t.render() == expect + + +class TestModuleLoader: archive = None - def compile_down(self, prefix_loader, zip="deflated"): + def compile_down(self, prefix_loader, zip="deflated"): log = [] self.reg_env = Environment(loader=prefix_loader) if zip is not None: - fd, self.archive = tempfile.mkstemp(suffix=".zip") + fd, self.archive = tempfile.mkstemp(suffix=".zip") os.close(fd) else: self.archive = tempfile.mkdtemp() - self.reg_env.compile_templates(self.archive, zip=zip, log_function=log.append) + self.reg_env.compile_templates(self.archive, zip=zip, log_function=log.append) self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive)) - return "".join(log) + return "".join(log) def teardown(self): - if hasattr(self, "mod_env"): + if hasattr(self, "mod_env"): if os.path.isfile(self.archive): os.remove(self.archive) else: @@ -200,31 +200,31 @@ class TestModuleLoader: def test_log(self, prefix_loader): log = self.compile_down(prefix_loader) - assert ( - 'Compiled "a/foo/test.html" as ' - "tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a" in log - ) - assert "Finished compiling templates" in log - assert ( - 'Could not compile "a/syntaxerror.html": ' - "Encountered unknown tag 'endif'" in log - ) + assert ( + 'Compiled "a/foo/test.html" as ' + "tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a" in log + ) + assert "Finished compiling templates" in log + assert ( + 'Could not compile "a/syntaxerror.html": ' + "Encountered unknown tag 'endif'" in log + ) def _test_common(self): - tmpl1 = self.reg_env.get_template("a/test.html") - tmpl2 = self.mod_env.get_template("a/test.html") + tmpl1 = self.reg_env.get_template("a/test.html") + tmpl2 = self.mod_env.get_template("a/test.html") assert tmpl1.render() == tmpl2.render() - tmpl1 = self.reg_env.get_template("b/justdict.html") - tmpl2 = self.mod_env.get_template("b/justdict.html") + tmpl1 = self.reg_env.get_template("b/justdict.html") + tmpl2 = self.mod_env.get_template("b/justdict.html") assert tmpl1.render() == tmpl2.render() def test_deflated_zip_compile(self, prefix_loader): - self.compile_down(prefix_loader, zip="deflated") + self.compile_down(prefix_loader, zip="deflated") self._test_common() def test_stored_zip_compile(self, prefix_loader): - self.compile_down(prefix_loader, zip="stored") + self.compile_down(prefix_loader, zip="stored") self._test_common() def test_filesystem_compile(self, prefix_loader): @@ -233,90 +233,90 @@ class TestModuleLoader: def test_weak_references(self, prefix_loader): self.compile_down(prefix_loader) - self.mod_env.get_template("a/test.html") - key = loaders.ModuleLoader.get_template_key("a/test.html") + self.mod_env.get_template("a/test.html") + key = loaders.ModuleLoader.get_template_key("a/test.html") name = self.mod_env.loader.module.__name__ assert hasattr(self.mod_env.loader.module, key) assert name in sys.modules # unset all, ensure the module is gone from sys.modules - self.mod_env = None + self.mod_env = None try: import gc - + gc.collect() - except BaseException: + except BaseException: pass assert name not in sys.modules def test_choice_loader(self, prefix_loader): - self.compile_down(prefix_loader) - self.mod_env.loader = loaders.ChoiceLoader( - [self.mod_env.loader, loaders.DictLoader({"DICT_SOURCE": "DICT_TEMPLATE"})] - ) - tmpl1 = self.mod_env.get_template("a/test.html") - assert tmpl1.render() == "BAR" - tmpl2 = self.mod_env.get_template("DICT_SOURCE") - assert tmpl2.render() == "DICT_TEMPLATE" - - def test_prefix_loader(self, prefix_loader): - self.compile_down(prefix_loader) - self.mod_env.loader = loaders.PrefixLoader( - { - "MOD": self.mod_env.loader, - "DICT": loaders.DictLoader({"test.html": "DICT_TEMPLATE"}), - } - ) - tmpl1 = self.mod_env.get_template("MOD/a/test.html") - assert tmpl1.render() == "BAR" - tmpl2 = self.mod_env.get_template("DICT/test.html") - assert tmpl2.render() == "DICT_TEMPLATE" - - def test_path_as_pathlib(self, prefix_loader): - self.compile_down(prefix_loader) - - mod_path = self.mod_env.loader.module.__path__[0] - mod_loader = loaders.ModuleLoader(Path(mod_path)) - self.mod_env = Environment(loader=mod_loader) - - self._test_common() - - def test_supports_pathlib_in_list_of_paths(self, prefix_loader): - self.compile_down(prefix_loader) - - mod_path = self.mod_env.loader.module.__path__[0] - mod_loader = loaders.ModuleLoader([Path(mod_path), "/tmp/templates"]) - self.mod_env = Environment(loader=mod_loader) - - self._test_common() - - -@pytest.fixture() -def package_dir_loader(monkeypatch): - monkeypatch.syspath_prepend(Path(__file__).parent) - return PackageLoader("res") - - -@pytest.mark.parametrize( - ("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")] -) -def test_package_dir_source(package_dir_loader, template, expect): - source, name, up_to_date = package_dir_loader.get_source(None, template) - assert source.rstrip() == expect - assert name.endswith(os.path.join(*split_template_path(template))) - assert up_to_date() - - -def test_package_dir_list(package_dir_loader): - templates = package_dir_loader.list_templates() - assert "foo/test.html" in templates - assert "test.html" in templates - - -@pytest.fixture() + self.compile_down(prefix_loader) + self.mod_env.loader = loaders.ChoiceLoader( + [self.mod_env.loader, loaders.DictLoader({"DICT_SOURCE": "DICT_TEMPLATE"})] + ) + tmpl1 = self.mod_env.get_template("a/test.html") + assert tmpl1.render() == "BAR" + tmpl2 = self.mod_env.get_template("DICT_SOURCE") + assert tmpl2.render() == "DICT_TEMPLATE" + + def test_prefix_loader(self, prefix_loader): + self.compile_down(prefix_loader) + self.mod_env.loader = loaders.PrefixLoader( + { + "MOD": self.mod_env.loader, + "DICT": loaders.DictLoader({"test.html": "DICT_TEMPLATE"}), + } + ) + tmpl1 = self.mod_env.get_template("MOD/a/test.html") + assert tmpl1.render() == "BAR" + tmpl2 = self.mod_env.get_template("DICT/test.html") + assert tmpl2.render() == "DICT_TEMPLATE" + + def test_path_as_pathlib(self, prefix_loader): + self.compile_down(prefix_loader) + + mod_path = self.mod_env.loader.module.__path__[0] + mod_loader = loaders.ModuleLoader(Path(mod_path)) + self.mod_env = Environment(loader=mod_loader) + + self._test_common() + + def test_supports_pathlib_in_list_of_paths(self, prefix_loader): + self.compile_down(prefix_loader) + + mod_path = self.mod_env.loader.module.__path__[0] + mod_loader = loaders.ModuleLoader([Path(mod_path), "/tmp/templates"]) + self.mod_env = Environment(loader=mod_loader) + + self._test_common() + + +@pytest.fixture() +def package_dir_loader(monkeypatch): + monkeypatch.syspath_prepend(Path(__file__).parent) + return PackageLoader("res") + + +@pytest.mark.parametrize( + ("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")] +) +def test_package_dir_source(package_dir_loader, template, expect): + source, name, up_to_date = package_dir_loader.get_source(None, template) + assert source.rstrip() == expect + assert name.endswith(os.path.join(*split_template_path(template))) + assert up_to_date() + + +def test_package_dir_list(package_dir_loader): + templates = package_dir_loader.list_templates() + assert "foo/test.html" in templates + assert "test.html" in templates + + +@pytest.fixture() def package_file_loader(monkeypatch): monkeypatch.syspath_prepend(Path(__file__).parent / "res") return PackageLoader("res") @@ -339,68 +339,68 @@ def test_package_file_list(package_file_loader): @pytest.fixture() -def package_zip_loader(monkeypatch): - package_zip = (Path(yc.test_source_path()) / "res" / "package.zip").resolve() - monkeypatch.syspath_prepend(package_zip) - return PackageLoader("t_pack") - - -@pytest.mark.parametrize( - ("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")] -) -def test_package_zip_source(package_zip_loader, template, expect): - source, name, up_to_date = package_zip_loader.get_source(None, template) - assert source.rstrip() == expect - assert name.endswith(os.path.join(*split_template_path(template))) - assert up_to_date is None - - -@pytest.mark.xfail( - platform.python_implementation() == "PyPy", - reason="PyPy's zipimporter doesn't have a '_files' attribute.", - raises=TypeError, -) -def test_package_zip_list(package_zip_loader): - assert package_zip_loader.list_templates() == ["foo/test.html", "test.html"] - - -@pytest.mark.parametrize("package_path", ["", ".", "./"]) -def test_package_zip_omit_curdir(package_zip_loader, package_path): - """PackageLoader should not add or include "." or "./" in the root - path, it is invalid in zip paths. - """ - loader = PackageLoader("t_pack", package_path) - assert loader.package_path == "" - source, _, _ = loader.get_source(None, "templates/foo/test.html") - assert source.rstrip() == "FOO" - - -def test_pep_451_import_hook(): - class ImportHook(importlib.abc.MetaPathFinder, importlib.abc.Loader): - def find_spec(self, name, path=None, target=None): - if name != "res": - return None - - spec = importlib.machinery.PathFinder.find_spec(name) - return importlib.util.spec_from_file_location( - name, - spec.origin, - loader=self, - submodule_search_locations=spec.submodule_search_locations, - ) - - def create_module(self, spec): - return None # default behaviour is fine - - def exec_module(self, module): - return None # we need this to satisfy the interface, it's wrong - - # ensure we restore `sys.meta_path` after putting in our loader - before = sys.meta_path[:] - - try: - sys.meta_path.insert(0, ImportHook()) - package_loader = PackageLoader("res") - assert "test.html" in package_loader.list_templates() - finally: - sys.meta_path[:] = before +def package_zip_loader(monkeypatch): + package_zip = (Path(yc.test_source_path()) / "res" / "package.zip").resolve() + monkeypatch.syspath_prepend(package_zip) + return PackageLoader("t_pack") + + +@pytest.mark.parametrize( + ("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")] +) +def test_package_zip_source(package_zip_loader, template, expect): + source, name, up_to_date = package_zip_loader.get_source(None, template) + assert source.rstrip() == expect + assert name.endswith(os.path.join(*split_template_path(template))) + assert up_to_date is None + + +@pytest.mark.xfail( + platform.python_implementation() == "PyPy", + reason="PyPy's zipimporter doesn't have a '_files' attribute.", + raises=TypeError, +) +def test_package_zip_list(package_zip_loader): + assert package_zip_loader.list_templates() == ["foo/test.html", "test.html"] + + +@pytest.mark.parametrize("package_path", ["", ".", "./"]) +def test_package_zip_omit_curdir(package_zip_loader, package_path): + """PackageLoader should not add or include "." or "./" in the root + path, it is invalid in zip paths. + """ + loader = PackageLoader("t_pack", package_path) + assert loader.package_path == "" + source, _, _ = loader.get_source(None, "templates/foo/test.html") + assert source.rstrip() == "FOO" + + +def test_pep_451_import_hook(): + class ImportHook(importlib.abc.MetaPathFinder, importlib.abc.Loader): + def find_spec(self, name, path=None, target=None): + if name != "res": + return None + + spec = importlib.machinery.PathFinder.find_spec(name) + return importlib.util.spec_from_file_location( + name, + spec.origin, + loader=self, + submodule_search_locations=spec.submodule_search_locations, + ) + + def create_module(self, spec): + return None # default behaviour is fine + + def exec_module(self, module): + return None # we need this to satisfy the interface, it's wrong + + # ensure we restore `sys.meta_path` after putting in our loader + before = sys.meta_path[:] + + try: + sys.meta_path.insert(0, ImportHook()) + package_loader = PackageLoader("res") + assert "test.html" in package_loader.list_templates() + finally: + sys.meta_path[:] = before diff --git a/contrib/python/Jinja2/py3/tests/test_nativetypes.py b/contrib/python/Jinja2/py3/tests/test_nativetypes.py index 9bae938cdb..922af2af72 100644 --- a/contrib/python/Jinja2/py3/tests/test_nativetypes.py +++ b/contrib/python/Jinja2/py3/tests/test_nativetypes.py @@ -1,10 +1,10 @@ -import math - +import math + import pytest from jinja2.exceptions import UndefinedError from jinja2.nativetypes import NativeEnvironment -from jinja2.nativetypes import NativeTemplate +from jinja2.nativetypes import NativeTemplate from jinja2.runtime import Undefined @@ -13,140 +13,140 @@ def env(): return NativeEnvironment() -def test_is_defined_native_return(env): - t = env.from_string("{{ missing is defined }}") - assert not t.render() - - -def test_undefined_native_return(env): - t = env.from_string("{{ missing }}") - assert isinstance(t.render(), Undefined) - - -def test_adding_undefined_native_return(env): - t = env.from_string("{{ 3 + missing }}") - - with pytest.raises(UndefinedError): - t.render() - - -def test_cast_int(env): - t = env.from_string("{{ value|int }}") - result = t.render(value="3") - assert isinstance(result, int) - assert result == 3 - - -def test_list_add(env): - t = env.from_string("{{ a + b }}") - result = t.render(a=["a", "b"], b=["c", "d"]) - assert isinstance(result, list) - assert result == ["a", "b", "c", "d"] - - -def test_multi_expression_add(env): - t = env.from_string("{{ a }} + {{ b }}") - result = t.render(a=["a", "b"], b=["c", "d"]) - assert not isinstance(result, list) - assert result == "['a', 'b'] + ['c', 'd']" - - -def test_loops(env): - t = env.from_string("{% for x in value %}{{ x }}{% endfor %}") - result = t.render(value=["a", "b", "c", "d"]) - assert isinstance(result, str) - assert result == "abcd" - - -def test_loops_with_ints(env): - t = env.from_string("{% for x in value %}{{ x }}{% endfor %}") - result = t.render(value=[1, 2, 3, 4]) - assert isinstance(result, int) - assert result == 1234 - - -def test_loop_look_alike(env): - t = env.from_string("{% for x in value %}{{ x }}{% endfor %}") - result = t.render(value=[1]) - assert isinstance(result, int) - assert result == 1 - - -@pytest.mark.parametrize( - ("source", "expect"), - ( - ("{{ value }}", True), - ("{{ value }}", False), - ("{{ 1 == 1 }}", True), - ("{{ 2 + 2 == 5 }}", False), - ("{{ None is none }}", True), - ("{{ '' == None }}", False), - ), -) -def test_booleans(env, source, expect): - t = env.from_string(source) - result = t.render(value=expect) - assert isinstance(result, bool) - assert result is expect - - -def test_variable_dunder(env): - t = env.from_string("{{ x.__class__ }}") - result = t.render(x=True) - assert isinstance(result, type) - - -def test_constant_dunder(env): - t = env.from_string("{{ true.__class__ }}") - result = t.render() - assert isinstance(result, type) - - -def test_constant_dunder_to_string(env): - t = env.from_string("{{ true.__class__|string }}") - result = t.render() - assert not isinstance(result, type) - assert result in {"<type 'bool'>", "<class 'bool'>"} - - -def test_string_literal_var(env): - t = env.from_string("[{{ 'all' }}]") - result = t.render() - assert isinstance(result, str) - assert result == "[all]" - - -def test_string_top_level(env): - t = env.from_string("'Jinja'") - result = t.render() - assert result == "Jinja" - - -def test_tuple_of_variable_strings(env): - t = env.from_string("'{{ a }}', 'data', '{{ b }}', b'{{ c }}'") - result = t.render(a=1, b=2, c="bytes") - assert isinstance(result, tuple) - assert result == ("1", "data", "2", b"bytes") - - -def test_concat_strings_with_quotes(env): - t = env.from_string("--host='{{ host }}' --user \"{{ user }}\"") - result = t.render(host="localhost", user="Jinja") - assert result == "--host='localhost' --user \"Jinja\"" - - -def test_no_intermediate_eval(env): - t = env.from_string("0.000{{ a }}") - result = t.render(a=7) - assert isinstance(result, float) - # If intermediate eval happened, 0.000 would render 0.0, then 7 - # would be appended, resulting in 0.07. - assert math.isclose(result, 0.0007) - - -def test_spontaneous_env(): - t = NativeTemplate("{{ true }}") - assert isinstance(t.environment, NativeEnvironment) +def test_is_defined_native_return(env): + t = env.from_string("{{ missing is defined }}") + assert not t.render() + + +def test_undefined_native_return(env): + t = env.from_string("{{ missing }}") + assert isinstance(t.render(), Undefined) + + +def test_adding_undefined_native_return(env): + t = env.from_string("{{ 3 + missing }}") + + with pytest.raises(UndefinedError): + t.render() + + +def test_cast_int(env): + t = env.from_string("{{ value|int }}") + result = t.render(value="3") + assert isinstance(result, int) + assert result == 3 + + +def test_list_add(env): + t = env.from_string("{{ a + b }}") + result = t.render(a=["a", "b"], b=["c", "d"]) + assert isinstance(result, list) + assert result == ["a", "b", "c", "d"] + + +def test_multi_expression_add(env): + t = env.from_string("{{ a }} + {{ b }}") + result = t.render(a=["a", "b"], b=["c", "d"]) + assert not isinstance(result, list) + assert result == "['a', 'b'] + ['c', 'd']" + + +def test_loops(env): + t = env.from_string("{% for x in value %}{{ x }}{% endfor %}") + result = t.render(value=["a", "b", "c", "d"]) + assert isinstance(result, str) + assert result == "abcd" + + +def test_loops_with_ints(env): + t = env.from_string("{% for x in value %}{{ x }}{% endfor %}") + result = t.render(value=[1, 2, 3, 4]) + assert isinstance(result, int) + assert result == 1234 + + +def test_loop_look_alike(env): + t = env.from_string("{% for x in value %}{{ x }}{% endfor %}") + result = t.render(value=[1]) + assert isinstance(result, int) + assert result == 1 + + +@pytest.mark.parametrize( + ("source", "expect"), + ( + ("{{ value }}", True), + ("{{ value }}", False), + ("{{ 1 == 1 }}", True), + ("{{ 2 + 2 == 5 }}", False), + ("{{ None is none }}", True), + ("{{ '' == None }}", False), + ), +) +def test_booleans(env, source, expect): + t = env.from_string(source) + result = t.render(value=expect) + assert isinstance(result, bool) + assert result is expect + + +def test_variable_dunder(env): + t = env.from_string("{{ x.__class__ }}") + result = t.render(x=True) + assert isinstance(result, type) + + +def test_constant_dunder(env): + t = env.from_string("{{ true.__class__ }}") + result = t.render() + assert isinstance(result, type) + + +def test_constant_dunder_to_string(env): + t = env.from_string("{{ true.__class__|string }}") + result = t.render() + assert not isinstance(result, type) + assert result in {"<type 'bool'>", "<class 'bool'>"} + + +def test_string_literal_var(env): + t = env.from_string("[{{ 'all' }}]") + result = t.render() + assert isinstance(result, str) + assert result == "[all]" + + +def test_string_top_level(env): + t = env.from_string("'Jinja'") + result = t.render() + assert result == "Jinja" + + +def test_tuple_of_variable_strings(env): + t = env.from_string("'{{ a }}', 'data', '{{ b }}', b'{{ c }}'") + result = t.render(a=1, b=2, c="bytes") + assert isinstance(result, tuple) + assert result == ("1", "data", "2", b"bytes") + + +def test_concat_strings_with_quotes(env): + t = env.from_string("--host='{{ host }}' --user \"{{ user }}\"") + result = t.render(host="localhost", user="Jinja") + assert result == "--host='localhost' --user \"Jinja\"" + + +def test_no_intermediate_eval(env): + t = env.from_string("0.000{{ a }}") + result = t.render(a=7) + assert isinstance(result, float) + # If intermediate eval happened, 0.000 would render 0.0, then 7 + # would be appended, resulting in 0.07. + assert math.isclose(result, 0.0007) + + +def test_spontaneous_env(): + t = NativeTemplate("{{ true }}") + assert isinstance(t.environment, NativeEnvironment) def test_leading_spaces(env): diff --git a/contrib/python/Jinja2/py3/tests/test_regression.py b/contrib/python/Jinja2/py3/tests/test_regression.py index 7e2336978e..3eeff98cc0 100644 --- a/contrib/python/Jinja2/py3/tests/test_regression.py +++ b/contrib/python/Jinja2/py3/tests/test_regression.py @@ -1,127 +1,127 @@ import pytest -from jinja2 import DictLoader -from jinja2 import Environment -from jinja2 import PrefixLoader -from jinja2 import Template -from jinja2 import TemplateAssertionError -from jinja2 import TemplateNotFound -from jinja2 import TemplateSyntaxError -from jinja2.utils import pass_context +from jinja2 import DictLoader +from jinja2 import Environment +from jinja2 import PrefixLoader +from jinja2 import Template +from jinja2 import TemplateAssertionError +from jinja2 import TemplateNotFound +from jinja2 import TemplateSyntaxError +from jinja2.utils import pass_context -class TestCorner: +class TestCorner: def test_assigned_scoping(self, env): - t = env.from_string( - """ + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {{- item -}} - """ - ) - assert t.render(item=42) == "[1][2][3][4]42" + """ + ) + assert t.render(item=42) == "[1][2][3][4]42" - t = env.from_string( - """ + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {%- set item = 42 %} {{- item -}} - """ - ) - assert t.render() == "[1][2][3][4]42" + """ + ) + assert t.render() == "[1][2][3][4]42" - t = env.from_string( - """ + t = env.from_string( + """ {%- set item = 42 %} {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {{- item -}} - """ - ) - assert t.render() == "[1][2][3][4]42" + """ + ) + assert t.render() == "[1][2][3][4]42" def test_closure_scoping(self, env): - t = env.from_string( - """ + t = env.from_string( + """ {%- set wrapper = "<FOO>" %} {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {{- wrapper -}} - """ - ) - assert t.render() == "[1][2][3][4]<FOO>" + """ + ) + assert t.render() == "[1][2][3][4]<FOO>" - t = env.from_string( - """ + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {%- set wrapper = "<FOO>" %} {{- wrapper -}} - """ - ) - assert t.render() == "[1][2][3][4]<FOO>" + """ + ) + assert t.render() == "[1][2][3][4]<FOO>" - t = env.from_string( - """ + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {{- wrapper -}} - """ - ) - assert t.render(wrapper=23) == "[1][2][3][4]23" + """ + ) + assert t.render(wrapper=23) == "[1][2][3][4]23" -class TestBug: +class TestBug: def test_keyword_folding(self, env): env = Environment() - env.filters["testing"] = lambda value, some: value + some - assert ( - env.from_string("{{ 'test'|testing(some='stuff') }}").render() - == "teststuff" - ) + env.filters["testing"] = lambda value, some: value + some + assert ( + env.from_string("{{ 'test'|testing(some='stuff') }}").render() + == "teststuff" + ) def test_extends_output_bugs(self, env): - env = Environment( - loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"}) - ) + env = Environment( + loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"}) + ) t = env.from_string( '{% if expr %}{% extends "parent.html" %}{% endif %}' - "[[{% block title %}title{% endblock %}]]" - "{% for item in [1, 2, 3] %}({{ item }}){% endfor %}" + "[[{% block title %}title{% endblock %}]]" + "{% for item in [1, 2, 3] %}({{ item }}){% endfor %}" ) - assert t.render(expr=False) == "[[title]](1)(2)(3)" - assert t.render(expr=True) == "((title))" + assert t.render(expr=False) == "[[title]](1)(2)(3)" + assert t.render(expr=True) == "((title))" def test_urlize_filter_escaping(self, env): tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}') - assert ( - tmpl.render() == '<a href="http://www.example.org/<foo" rel="noopener">' - "http://www.example.org/<foo</a>" - ) - - def test_urlize_filter_closing_punctuation(self, env): - tmpl = env.from_string( - '{{ "(see http://www.example.org/?page=subj_<desc.h>)"|urlize }}' - ) - assert tmpl.render() == ( - '(see <a href="http://www.example.org/?page=subj_<desc.h>" ' - 'rel="noopener">http://www.example.org/?page=subj_<desc.h></a>)' - ) - + assert ( + tmpl.render() == '<a href="http://www.example.org/<foo" rel="noopener">' + "http://www.example.org/<foo</a>" + ) + + def test_urlize_filter_closing_punctuation(self, env): + tmpl = env.from_string( + '{{ "(see http://www.example.org/?page=subj_<desc.h>)"|urlize }}' + ) + assert tmpl.render() == ( + '(see <a href="http://www.example.org/?page=subj_<desc.h>" ' + 'rel="noopener">http://www.example.org/?page=subj_<desc.h></a>)' + ) + def test_loop_call_loop(self, env): - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {% macro test() %} {{ caller() }} @@ -135,35 +135,35 @@ class TestBug: {% endcall %} {% endfor %} - """ - ) + """ + ) - assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5 + assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5 def test_weird_inline_comment(self, env): - env = Environment(line_statement_prefix="%") - pytest.raises( - TemplateSyntaxError, - env.from_string, - "% for item in seq {# missing #}\n...% endfor", - ) + env = Environment(line_statement_prefix="%") + pytest.raises( + TemplateSyntaxError, + env.from_string, + "% for item in seq {# missing #}\n...% endfor", + ) def test_old_macro_loop_scoping_bug(self, env): - tmpl = env.from_string( - "{% for i in (1, 2) %}{{ i }}{% endfor %}" - "{% macro i() %}3{% endmacro %}{{ i() }}" - ) - assert tmpl.render() == "123" + tmpl = env.from_string( + "{% for i in (1, 2) %}{{ i }}{% endfor %}" + "{% macro i() %}3{% endmacro %}{{ i() }}" + ) + assert tmpl.render() == "123" def test_partial_conditional_assignments(self, env): - tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}") - assert tmpl.render(a=23) == "23" - assert tmpl.render(b=True) == "42" + tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}") + assert tmpl.render(a=23) == "23" + assert tmpl.render(b=True) == "42" def test_stacked_locals_scoping_bug(self, env): - env = Environment(line_statement_prefix="#") - t = env.from_string( - """\ + env = Environment(line_statement_prefix="#") + t = env.from_string( + """\ # for j in [1, 2]: # set x = 1 # for i in [1, 2]: @@ -182,13 +182,13 @@ class TestBug: # else # print 'D' # endif - """ - ) - assert t.render(a=0, b=False, c=42, d=42.0) == "1111C" + """ + ) + assert t.render(a=0, b=False, c=42, d=42.0) == "1111C" def test_stacked_locals_scoping_bug_twoframe(self, env): - t = Template( - """ + t = Template( + """ {% set x = 1 %} {% for item in foo %} {% if item == 1 %} @@ -196,14 +196,14 @@ class TestBug: {% endif %} {% endfor %} {{ x }} - """ - ) + """ + ) rv = t.render(foo=[1]).strip() - assert rv == "1" + assert rv == "1" def test_call_with_args(self, env): - t = Template( - """{% macro dump_users(users) -%} + t = Template( + """{% macro dump_users(users) -%} <ul> {%- for user in users -%} <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li> @@ -218,159 +218,159 @@ class TestBug: <dl>Description</dl> <dd>{{ user.description }}</dd> </dl> - {% endcall %}""" - ) - - assert [ - x.strip() - for x in t.render( - list_of_user=[ - { - "username": "apo", - "realname": "something else", - "description": "test", - } - ] - ).splitlines() - ] == [ - "<ul><li><p>apo</p><dl>", - "<dl>Realname</dl>", - "<dd>something else</dd>", - "<dl>Description</dl>", - "<dd>test</dd>", - "</dl>", - "</li></ul>", + {% endcall %}""" + ) + + assert [ + x.strip() + for x in t.render( + list_of_user=[ + { + "username": "apo", + "realname": "something else", + "description": "test", + } + ] + ).splitlines() + ] == [ + "<ul><li><p>apo</p><dl>", + "<dl>Realname</dl>", + "<dd>something else</dd>", + "<dl>Description</dl>", + "<dd>test</dd>", + "</dl>", + "</li></ul>", ] def test_empty_if_condition_fails(self, env): - pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}") - pytest.raises( - TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}" - ) - pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}") - - def test_recursive_loop_compile(self, env): - Template( - """ - {% for p in foo recursive%} + pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}") + pytest.raises( + TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}" + ) + pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}") + + def test_recursive_loop_compile(self, env): + Template( + """ + {% for p in foo recursive%} {{p.bar}} - {% for f in p.fields recursive%} - {{f.baz}} - {{p.bar}} - {% if f.rec %} - {{ loop(f.sub) }} - {% endif %} - {% endfor %} + {% for f in p.fields recursive%} + {{f.baz}} + {{p.bar}} + {% if f.rec %} + {{ loop(f.sub) }} + {% endif %} + {% endfor %} {% endfor %} - """ - ) - Template( - """ - {% for p in foo%} + """ + ) + Template( + """ + {% for p in foo%} {{p.bar}} - {% for f in p.fields recursive%} - {{f.baz}} - {{p.bar}} - {% if f.rec %} - {{ loop(f.sub) }} - {% endif %} - {% endfor %} + {% for f in p.fields recursive%} + {{f.baz}} + {{p.bar}} + {% if f.rec %} + {{ loop(f.sub) }} + {% endif %} + {% endfor %} {% endfor %} - """ - ) + """ + ) def test_else_loop_bug(self, env): - t = Template( - """ + t = Template( + """ {% for x in y %} {{ loop.index0 }} {% else %} {% for i in range(3) %}{{ i }}{% endfor %} {% endfor %} - """ - ) - assert t.render(y=[]).strip() == "012" + """ + ) + assert t.render(y=[]).strip() == "012" def test_correct_prefix_loader_name(self, env): - env = Environment(loader=PrefixLoader({"foo": DictLoader({})})) - with pytest.raises(TemplateNotFound) as e: - env.get_template("foo/bar.html") - - assert e.value.name == "foo/bar.html" - - def test_pass_context_callable_class(self, env): - class CallableClass: - @pass_context + env = Environment(loader=PrefixLoader({"foo": DictLoader({})})) + with pytest.raises(TemplateNotFound) as e: + env.get_template("foo/bar.html") + + assert e.value.name == "foo/bar.html" + + def test_pass_context_callable_class(self, env): + class CallableClass: + @pass_context def __call__(self, ctx): - return ctx.resolve("hello") + return ctx.resolve("hello") tpl = Template("""{{ callableclass() }}""") - output = tpl.render(callableclass=CallableClass(), hello="TEST") - expected = "TEST" + output = tpl.render(callableclass=CallableClass(), hello="TEST") + expected = "TEST" assert output == expected def test_block_set_with_extends(self): - env = Environment( - loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"}) - ) + env = Environment( + loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"}) + ) t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}') - assert t.render() == "[42]" + assert t.render() == "[42]" def test_nested_for_else(self, env): - tmpl = env.from_string( - "{% for x in y %}{{ loop.index0 }}{% else %}" - "{% for i in range(3) %}{{ i }}{% endfor %}" - "{% endfor %}" - ) - assert tmpl.render() == "012" + tmpl = env.from_string( + "{% for x in y %}{{ loop.index0 }}{% else %}" + "{% for i in range(3) %}{{ i }}{% endfor %}" + "{% endfor %}" + ) + assert tmpl.render() == "012" def test_macro_var_bug(self, env): - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {% set i = 1 %} {% macro test() %} {% for i in range(0, 10) %}{{ i }}{% endfor %} {% endmacro %}{{ test() }} - """ - ) - assert tmpl.render().strip() == "0123456789" + """ + ) + assert tmpl.render().strip() == "0123456789" def test_macro_var_bug_advanced(self, env): - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {% macro outer() %} {% set i = 1 %} {% macro test() %} {% for i in range(0, 10) %}{{ i }}{% endfor %} {% endmacro %}{{ test() }} {% endmacro %}{{ outer() }} - """ - ) - assert tmpl.render().strip() == "0123456789" + """ + ) + assert tmpl.render().strip() == "0123456789" def test_callable_defaults(self): env = Environment() - env.globals["get_int"] = lambda: 42 - t = env.from_string( - """ + env.globals["get_int"] = lambda: 42 + t = env.from_string( + """ {% macro test(a, b, c=get_int()) -%} {{ a + b + c }} {%- endmacro %} {{ test(1, 2) }}|{{ test(1, 2, 3) }} - """ - ) - assert t.render().strip() == "45|6" + """ + ) + assert t.render().strip() == "45|6" def test_macro_escaping(self): - env = Environment(autoescape=lambda x: False) + env = Environment(autoescape=lambda x: False) template = "{% macro m() %}<html>{% endmacro %}" template += "{% autoescape true %}{{ m() }}{% endautoescape %}" assert env.from_string(template).render() def test_macro_scoping(self, env): - tmpl = env.from_string( - """ + tmpl = env.from_string( + """ {% set n=[1,2,3,4,5] %} {% for n in [[1,2,3], [3,4,5], [5,6,7]] %} @@ -382,56 +382,56 @@ class TestBug: {{ x(n) }} {% endfor %} - """ - ) - assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5] + """ + ) + assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5] def test_scopes_and_blocks(self): - env = Environment( - loader=DictLoader( - { - "a.html": """ + env = Environment( + loader=DictLoader( + { + "a.html": """ {%- set foo = 'bar' -%} {% include 'x.html' -%} - """, - "b.html": """ + """, + "b.html": """ {%- set foo = 'bar' -%} {% block test %}{% include 'x.html' %}{% endblock -%} - """, - "c.html": """ + """, + "c.html": """ {%- set foo = 'bar' -%} {% block test %}{% set foo = foo %}{% include 'x.html' %}{% endblock -%} - """, - "x.html": """{{ foo }}|{{ test }}""", - } - ) - ) + """, + "x.html": """{{ foo }}|{{ test }}""", + } + ) + ) - a = env.get_template("a.html") - b = env.get_template("b.html") - c = env.get_template("c.html") + a = env.get_template("a.html") + b = env.get_template("b.html") + c = env.get_template("c.html") - assert a.render(test="x").strip() == "bar|x" - assert b.render(test="x").strip() == "bar|x" - assert c.render(test="x").strip() == "bar|x" + assert a.render(test="x").strip() == "bar|x" + assert b.render(test="x").strip() == "bar|x" + assert c.render(test="x").strip() == "bar|x" def test_scopes_and_include(self): - env = Environment( - loader=DictLoader( - { - "include.html": "{{ var }}", - "base.html": '{% include "include.html" %}', - "child.html": '{% extends "base.html" %}{% set var = 42 %}', - } - ) - ) - t = env.get_template("child.html") - assert t.render() == "42" + env = Environment( + loader=DictLoader( + { + "include.html": "{{ var }}", + "base.html": '{% include "include.html" %}', + "child.html": '{% extends "base.html" %}{% set var = 42 %}', + } + ) + ) + t = env.get_template("child.html") + assert t.render() == "42" def test_caller_scoping(self, env): - t = env.from_string( - """ + t = env.from_string( + """ {% macro detail(icon, value) -%} {% if value -%} <p><span class="fa fa-fw fa-{{ icon }}"></span> @@ -449,67 +449,67 @@ class TestBug: <a href="{{ href }}">{{ value }}</a> {%- endcall %} {%- endmacro %} - """ - ) + """ + ) - assert t.module.link_detail("circle", "Index", "/") == ( - '<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>' - ) + assert t.module.link_detail("circle", "Index", "/") == ( + '<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>' + ) def test_variable_reuse(self, env): - t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}") - assert t.render(x={"y": [0, 1, 2]}) == "012" + t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}") + assert t.render(x={"y": [0, 1, 2]}) == "012" - t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}") - assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2" + t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}") + assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2" - t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}") - assert t.render(x={"y": [0, 1, 2]}) == "012" + t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}") + assert t.render(x={"y": [0, 1, 2]}) == "012" def test_double_caller(self, env): - t = env.from_string( - "{% macro x(caller=none) %}[{% if caller %}" - "{{ caller() }}{% endif %}]{% endmacro %}" - "{{ x() }}{% call x() %}aha!{% endcall %}" - ) - assert t.render() == "[][aha!]" + t = env.from_string( + "{% macro x(caller=none) %}[{% if caller %}" + "{{ caller() }}{% endif %}]{% endmacro %}" + "{{ x() }}{% call x() %}aha!{% endcall %}" + ) + assert t.render() == "[][aha!]" def test_double_caller_no_default(self, env): with pytest.raises(TemplateAssertionError) as exc_info: - env.from_string( - "{% macro x(caller) %}[{% if caller %}" - "{{ caller() }}{% endif %}]{% endmacro %}" - ) - assert exc_info.match( - r'"caller" argument must be omitted or ' r"be given a default" - ) - - t = env.from_string( - "{% macro x(caller=none) %}[{% if caller %}" - "{{ caller() }}{% endif %}]{% endmacro %}" - ) + env.from_string( + "{% macro x(caller) %}[{% if caller %}" + "{{ caller() }}{% endif %}]{% endmacro %}" + ) + assert exc_info.match( + r'"caller" argument must be omitted or ' r"be given a default" + ) + + t = env.from_string( + "{% macro x(caller=none) %}[{% if caller %}" + "{{ caller() }}{% endif %}]{% endmacro %}" + ) with pytest.raises(TypeError) as exc_info: t.module.x(None, caller=lambda: 42) - assert exc_info.match( - r"\'x\' was invoked with two values for the " r"special caller argument" - ) + assert exc_info.match( + r"\'x\' was invoked with two values for the " r"special caller argument" + ) def test_macro_blocks(self, env): - t = env.from_string( - "{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}" - ) - assert t.render() == "x" + t = env.from_string( + "{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}" + ) + assert t.render() == "x" def test_scoped_block(self, env): - t = env.from_string( - "{% set x = 1 %}{% with x = 2 %}{% block y scoped %}" - "{{ x }}{% endblock %}{% endwith %}" - ) - assert t.render() == "2" + t = env.from_string( + "{% set x = 1 %}{% with x = 2 %}{% block y scoped %}" + "{{ x }}{% endblock %}{% endwith %}" + ) + assert t.render() == "2" def test_recursive_loop_filter(self, env): - t = env.from_string( - """ + t = env.from_string( + """ <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> {%- for page in [site.root] if page.url != this recursive %} @@ -517,63 +517,63 @@ class TestBug: {{- loop(page.children) }} {%- endfor %} </urlset> - """ - ) - sm = t.render( - this="/foo", - site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}}, - ) + """ + ) + sm = t.render( + this="/foo", + site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}}, + ) lines = [x.strip() for x in sm.splitlines() if x.strip()] assert lines == [ '<?xml version="1.0" encoding="UTF-8"?>', '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">', - "<url><loc>/</loc></url>", - "<url><loc>/bar</loc></url>", - "</urlset>", + "<url><loc>/</loc></url>", + "<url><loc>/bar</loc></url>", + "</urlset>", ] def test_empty_if(self, env): - 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" - ) - + 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", - "main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}', - } - ) - ) - assert env.get_template("main").render() == "foobar" + env = Environment( + loader=DictLoader( + { + "inc": "bar", + "main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}', + } + ) + ) + assert env.get_template("main").render() == "foobar" def test_loop_include(self): - env = Environment( - loader=DictLoader( - { - "inc": "{{ i }}", - "main": '{% for i in [1, 2, 3] %}{% include "inc" %}{% endfor %}', - } - ) - ) - assert env.get_template("main").render() == "123" + env = Environment( + loader=DictLoader( + { + "inc": "{{ i }}", + "main": '{% for i in [1, 2, 3] %}{% include "inc" %}{% endfor %}', + } + ) + ) + assert env.get_template("main").render() == "123" def test_grouper_repr(self): from jinja2.filters import _GroupTuple - - t = _GroupTuple("foo", [1, 2]) - assert t.grouper == "foo" + + t = _GroupTuple("foo", [1, 2]) + assert t.grouper == "foo" assert t.list == [1, 2] assert repr(t) == "('foo', [1, 2])" assert str(t) == "('foo', [1, 2])" @@ -587,175 +587,175 @@ class TestBug: class MyEnvironment(Environment): context_class = MyContext - loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'}) + loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'}) env = MyEnvironment(loader=loader) - assert env.get_template("test").render(foobar="test") == "test" + assert env.get_template("test").render(foobar="test") == "test" def test_legacy_custom_context(self, env): - from jinja2.runtime import Context, missing - - with pytest.deprecated_call(): + from jinja2.runtime import Context, missing - class MyContext(Context): - def resolve(self, name): - if name == "foo": - return 42 - return super().resolve(name) + with pytest.deprecated_call(): - x = MyContext(env, parent={"bar": 23}, name="foo", blocks={}) + 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 + 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 -%}" - ) - assert tmpl.render(values=[]) == "0" - - def test_markup_and_chainable_undefined(self): - from markupsafe import Markup - from jinja2.runtime import ChainableUndefined - - assert str(Markup(ChainableUndefined())) == "" - - def test_scoped_block_loop_vars(self, env): - tmpl = env.from_string( - """\ -Start -{% for i in ["foo", "bar"] -%} -{% block body scoped -%} -{{ loop.index }}) {{ i }}{% if loop.last %} last{% endif -%} -{%- endblock %} -{% endfor -%} -End""" - ) - assert tmpl.render() == "Start\n1) foo\n2) bar last\nEnd" - - def test_pass_context_loop_vars(self, env): - @pass_context - def test(ctx): - return f"{ctx['i']}{ctx['j']}" - - tmpl = env.from_string( - """\ -{% set i = 42 %} -{%- for idx in range(2) -%} -{{ i }}{{ j }} -{% set i = idx -%} -{%- set j = loop.index -%} -{{ test() }} -{{ i }}{{ j }} -{% endfor -%} -{{ i }}{{ j }}""" - ) - tmpl.globals["test"] = test - assert tmpl.render() == "42\n01\n01\n42\n12\n12\n42" - - def test_pass_context_scoped_loop_vars(self, env): - @pass_context - def test(ctx): - return f"{ctx['i']}" - - tmpl = env.from_string( - """\ -{% set i = 42 %} -{%- for idx in range(2) -%} -{{ i }} -{%- set i = loop.index0 -%} -{% block body scoped %} -{{ test() }} -{% endblock -%} -{% endfor -%} -{{ i }}""" - ) - tmpl.globals["test"] = test - assert tmpl.render() == "42\n0\n42\n1\n42" - - def test_pass_context_in_blocks(self, env): - @pass_context - def test(ctx): - return f"{ctx['i']}" - - tmpl = env.from_string( - """\ -{%- set i = 42 -%} -{{ i }} -{% block body -%} -{% set i = 24 -%} -{{ test() }} -{% endblock -%} -{{ i }}""" - ) - tmpl.globals["test"] = test - assert tmpl.render() == "42\n24\n42" - - def test_pass_context_block_and_loop(self, env): - @pass_context - def test(ctx): - return f"{ctx['i']}" - - tmpl = env.from_string( - """\ -{%- set i = 42 -%} -{% for idx in range(2) -%} -{{ test() }} -{%- set i = idx -%} -{% block body scoped %} -{{ test() }} -{% set i = 24 -%} -{{ test() }} -{% endblock -%} -{{ test() }} -{% endfor -%} -{{ test() }}""" - ) - tmpl.globals["test"] = test - - # values set within a block or loop should not - # show up outside of it - assert tmpl.render() == "42\n0\n24\n0\n42\n1\n24\n1\n42" - - @pytest.mark.parametrize("op", ["extends", "include"]) - def test_cached_extends(self, op): - env = Environment( - loader=DictLoader( - {"base": "{{ x }} {{ y }}", "main": f"{{% {op} 'base' %}}"} - ) - ) - env.globals["x"] = "x" - env.globals["y"] = "y" - - # template globals overlay env globals - tmpl = env.get_template("main", globals={"x": "bar"}) - assert tmpl.render() == "bar y" - - # base was loaded indirectly, it just has env globals - tmpl = env.get_template("base") - assert tmpl.render() == "x y" - - # set template globals for base, no longer uses env globals - tmpl = env.get_template("base", globals={"x": 42}) - assert tmpl.render() == "42 y" - - # templates are cached, they keep template globals set earlier - tmpl = env.get_template("main") - assert tmpl.render() == "bar y" - - tmpl = env.get_template("base") - assert tmpl.render() == "42 y" - - def test_nested_loop_scoping(self, env): - tmpl = env.from_string( - "{% set output %}{% for x in [1,2,3] %}hello{% endfor %}" - "{% endset %}{{ output }}" - ) - assert tmpl.render() == "hellohellohello" - - -@pytest.mark.parametrize("unicode_char", ["\N{FORM FEED}", "\x85"]) -def test_unicode_whitespace(env, unicode_char): - content = "Lorem ipsum\n" + unicode_char + "\nMore text" - tmpl = env.from_string(content) - assert tmpl.render() == content + tmpl = env.from_string( + "{%- for value in values recursive %}1{% else %}0{% endfor -%}" + ) + assert tmpl.render(values=[]) == "0" + + def test_markup_and_chainable_undefined(self): + from markupsafe import Markup + from jinja2.runtime import ChainableUndefined + + assert str(Markup(ChainableUndefined())) == "" + + def test_scoped_block_loop_vars(self, env): + tmpl = env.from_string( + """\ +Start +{% for i in ["foo", "bar"] -%} +{% block body scoped -%} +{{ loop.index }}) {{ i }}{% if loop.last %} last{% endif -%} +{%- endblock %} +{% endfor -%} +End""" + ) + assert tmpl.render() == "Start\n1) foo\n2) bar last\nEnd" + + def test_pass_context_loop_vars(self, env): + @pass_context + def test(ctx): + return f"{ctx['i']}{ctx['j']}" + + tmpl = env.from_string( + """\ +{% set i = 42 %} +{%- for idx in range(2) -%} +{{ i }}{{ j }} +{% set i = idx -%} +{%- set j = loop.index -%} +{{ test() }} +{{ i }}{{ j }} +{% endfor -%} +{{ i }}{{ j }}""" + ) + tmpl.globals["test"] = test + assert tmpl.render() == "42\n01\n01\n42\n12\n12\n42" + + def test_pass_context_scoped_loop_vars(self, env): + @pass_context + def test(ctx): + return f"{ctx['i']}" + + tmpl = env.from_string( + """\ +{% set i = 42 %} +{%- for idx in range(2) -%} +{{ i }} +{%- set i = loop.index0 -%} +{% block body scoped %} +{{ test() }} +{% endblock -%} +{% endfor -%} +{{ i }}""" + ) + tmpl.globals["test"] = test + assert tmpl.render() == "42\n0\n42\n1\n42" + + def test_pass_context_in_blocks(self, env): + @pass_context + def test(ctx): + return f"{ctx['i']}" + + tmpl = env.from_string( + """\ +{%- set i = 42 -%} +{{ i }} +{% block body -%} +{% set i = 24 -%} +{{ test() }} +{% endblock -%} +{{ i }}""" + ) + tmpl.globals["test"] = test + assert tmpl.render() == "42\n24\n42" + + def test_pass_context_block_and_loop(self, env): + @pass_context + def test(ctx): + return f"{ctx['i']}" + + tmpl = env.from_string( + """\ +{%- set i = 42 -%} +{% for idx in range(2) -%} +{{ test() }} +{%- set i = idx -%} +{% block body scoped %} +{{ test() }} +{% set i = 24 -%} +{{ test() }} +{% endblock -%} +{{ test() }} +{% endfor -%} +{{ test() }}""" + ) + tmpl.globals["test"] = test + + # values set within a block or loop should not + # show up outside of it + assert tmpl.render() == "42\n0\n24\n0\n42\n1\n24\n1\n42" + + @pytest.mark.parametrize("op", ["extends", "include"]) + def test_cached_extends(self, op): + env = Environment( + loader=DictLoader( + {"base": "{{ x }} {{ y }}", "main": f"{{% {op} 'base' %}}"} + ) + ) + env.globals["x"] = "x" + env.globals["y"] = "y" + + # template globals overlay env globals + tmpl = env.get_template("main", globals={"x": "bar"}) + assert tmpl.render() == "bar y" + + # base was loaded indirectly, it just has env globals + tmpl = env.get_template("base") + assert tmpl.render() == "x y" + + # set template globals for base, no longer uses env globals + tmpl = env.get_template("base", globals={"x": 42}) + assert tmpl.render() == "42 y" + + # templates are cached, they keep template globals set earlier + tmpl = env.get_template("main") + assert tmpl.render() == "bar y" + + tmpl = env.get_template("base") + assert tmpl.render() == "42 y" + + def test_nested_loop_scoping(self, env): + tmpl = env.from_string( + "{% set output %}{% for x in [1,2,3] %}hello{% endfor %}" + "{% endset %}{{ output }}" + ) + assert tmpl.render() == "hellohellohello" + + +@pytest.mark.parametrize("unicode_char", ["\N{FORM FEED}", "\x85"]) +def test_unicode_whitespace(env, unicode_char): + content = "Lorem ipsum\n" + unicode_char + "\nMore text" + tmpl = env.from_string(content) + assert tmpl.render() == content diff --git a/contrib/python/Jinja2/py3/tests/test_runtime.py b/contrib/python/Jinja2/py3/tests/test_runtime.py index 1978c64104..42a94bc1d2 100644 --- a/contrib/python/Jinja2/py3/tests/test_runtime.py +++ b/contrib/python/Jinja2/py3/tests/test_runtime.py @@ -1,75 +1,75 @@ -import itertools - -from jinja2 import Template -from jinja2.runtime import LoopContext - -TEST_IDX_TEMPLATE_STR_1 = ( - "[{% for i in lst|reverse %}(len={{ loop.length }}," - " revindex={{ loop.revindex }}, index={{ loop.index }}, val={{ i }}){% endfor %}]" -) -TEST_IDX0_TEMPLATE_STR_1 = ( - "[{% for i in lst|reverse %}(len={{ loop.length }}," - " revindex0={{ loop.revindex0 }}, index0={{ loop.index0 }}, val={{ i }})" - "{% endfor %}]" -) - - -def test_loop_idx(): - t = Template(TEST_IDX_TEMPLATE_STR_1) - lst = [10] - excepted_render = "[(len=1, revindex=1, index=1, val=10)]" - assert excepted_render == t.render(lst=lst) - - -def test_loop_idx0(): - t = Template(TEST_IDX0_TEMPLATE_STR_1) - lst = [10] - excepted_render = "[(len=1, revindex0=0, index0=0, val=10)]" - assert excepted_render == t.render(lst=lst) - - -def test_loopcontext0(): - in_lst = [] - lc = LoopContext(reversed(in_lst), None) - assert lc.length == len(in_lst) - - -def test_loopcontext1(): - in_lst = [10] - lc = LoopContext(reversed(in_lst), None) - assert lc.length == len(in_lst) - - -def test_loopcontext2(): - in_lst = [10, 11] - lc = LoopContext(reversed(in_lst), None) - assert lc.length == len(in_lst) - - -def test_iterator_not_advanced_early(): - t = Template("{% for _, g in gs %}{{ loop.index }} {{ g|list }}\n{% endfor %}") - out = t.render( - gs=itertools.groupby([(1, "a"), (1, "b"), (2, "c"), (3, "d")], lambda x: x[0]) - ) - # groupby groups depend on the current position of the iterator. If - # it was advanced early, the lists would appear empty. - assert out == "1 [(1, 'a'), (1, 'b')]\n2 [(2, 'c')]\n3 [(3, 'd')]\n" - - -def test_mock_not_pass_arg_marker(): - """If a callable class has a ``__getattr__`` that returns True-like - values for arbitrary attrs, it should not be incorrectly identified - as a ``pass_context`` function. - """ - - class Calc: - def __getattr__(self, item): - return object() - - def __call__(self, *args, **kwargs): - return len(args) + len(kwargs) - - t = Template("{{ calc() }}") - out = t.render(calc=Calc()) - # Would be "1" if context argument was passed. - assert out == "0" +import itertools + +from jinja2 import Template +from jinja2.runtime import LoopContext + +TEST_IDX_TEMPLATE_STR_1 = ( + "[{% for i in lst|reverse %}(len={{ loop.length }}," + " revindex={{ loop.revindex }}, index={{ loop.index }}, val={{ i }}){% endfor %}]" +) +TEST_IDX0_TEMPLATE_STR_1 = ( + "[{% for i in lst|reverse %}(len={{ loop.length }}," + " revindex0={{ loop.revindex0 }}, index0={{ loop.index0 }}, val={{ i }})" + "{% endfor %}]" +) + + +def test_loop_idx(): + t = Template(TEST_IDX_TEMPLATE_STR_1) + lst = [10] + excepted_render = "[(len=1, revindex=1, index=1, val=10)]" + assert excepted_render == t.render(lst=lst) + + +def test_loop_idx0(): + t = Template(TEST_IDX0_TEMPLATE_STR_1) + lst = [10] + excepted_render = "[(len=1, revindex0=0, index0=0, val=10)]" + assert excepted_render == t.render(lst=lst) + + +def test_loopcontext0(): + in_lst = [] + lc = LoopContext(reversed(in_lst), None) + assert lc.length == len(in_lst) + + +def test_loopcontext1(): + in_lst = [10] + lc = LoopContext(reversed(in_lst), None) + assert lc.length == len(in_lst) + + +def test_loopcontext2(): + in_lst = [10, 11] + lc = LoopContext(reversed(in_lst), None) + assert lc.length == len(in_lst) + + +def test_iterator_not_advanced_early(): + t = Template("{% for _, g in gs %}{{ loop.index }} {{ g|list }}\n{% endfor %}") + out = t.render( + gs=itertools.groupby([(1, "a"), (1, "b"), (2, "c"), (3, "d")], lambda x: x[0]) + ) + # groupby groups depend on the current position of the iterator. If + # it was advanced early, the lists would appear empty. + assert out == "1 [(1, 'a'), (1, 'b')]\n2 [(2, 'c')]\n3 [(3, 'd')]\n" + + +def test_mock_not_pass_arg_marker(): + """If a callable class has a ``__getattr__`` that returns True-like + values for arbitrary attrs, it should not be incorrectly identified + as a ``pass_context`` function. + """ + + class Calc: + def __getattr__(self, item): + return object() + + def __call__(self, *args, **kwargs): + return len(args) + len(kwargs) + + t = Template("{{ calc() }}") + out = t.render(calc=Calc()) + # Would be "1" if context argument was passed. + assert out == "0" diff --git a/contrib/python/Jinja2/py3/tests/test_security.py b/contrib/python/Jinja2/py3/tests/test_security.py index 0e8dc5c038..65afb1cf6d 100644 --- a/contrib/python/Jinja2/py3/tests/test_security.py +++ b/contrib/python/Jinja2/py3/tests/test_security.py @@ -1,17 +1,17 @@ import pytest -from markupsafe import escape +from markupsafe import escape from jinja2 import Environment -from jinja2.exceptions import SecurityError -from jinja2.exceptions import TemplateRuntimeError -from jinja2.exceptions import TemplateSyntaxError +from jinja2.exceptions import SecurityError +from jinja2.exceptions import TemplateRuntimeError +from jinja2.exceptions import TemplateSyntaxError from jinja2.nodes import EvalContext -from jinja2.sandbox import ImmutableSandboxedEnvironment -from jinja2.sandbox import SandboxedEnvironment -from jinja2.sandbox import unsafe +from jinja2.sandbox import ImmutableSandboxedEnvironment +from jinja2.sandbox import SandboxedEnvironment +from jinja2.sandbox import unsafe -class PrivateStuff: +class PrivateStuff: def bar(self): return 23 @@ -20,76 +20,76 @@ class PrivateStuff: return 42 def __repr__(self): - return "PrivateStuff" + return "PrivateStuff" -class PublicStuff: - def bar(self): - return 23 - - def _foo(self): - return 42 +class PublicStuff: + def bar(self): + return 23 + def _foo(self): + return 42 + def __repr__(self): - return "PublicStuff" + return "PublicStuff" -class TestSandbox: +class TestSandbox: def test_unsafe(self, env): env = SandboxedEnvironment() - pytest.raises( - SecurityError, env.from_string("{{ foo.foo() }}").render, foo=PrivateStuff() - ) - assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == "23" - - pytest.raises( - SecurityError, env.from_string("{{ foo._foo() }}").render, foo=PublicStuff() - ) - assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == "23" - assert env.from_string("{{ foo.__class__ }}").render(foo=42) == "" - assert env.from_string("{{ foo.func_code }}").render(foo=lambda: None) == "" + pytest.raises( + SecurityError, env.from_string("{{ foo.foo() }}").render, foo=PrivateStuff() + ) + assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == "23" + + pytest.raises( + SecurityError, env.from_string("{{ foo._foo() }}").render, foo=PublicStuff() + ) + assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == "23" + assert env.from_string("{{ foo.__class__ }}").render(foo=42) == "" + assert env.from_string("{{ foo.func_code }}").render(foo=lambda: None) == "" # security error comes from __class__ already. - pytest.raises( - SecurityError, - env.from_string("{{ foo.__class__.__subclasses__() }}").render, - foo=42, - ) + pytest.raises( + SecurityError, + env.from_string("{{ foo.__class__.__subclasses__() }}").render, + foo=42, + ) def test_immutable_environment(self, env): env = ImmutableSandboxedEnvironment() - pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render) - pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render) + pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render) + pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render) def test_restricted(self, env): env = SandboxedEnvironment() - pytest.raises( - TemplateSyntaxError, - env.from_string, - "{% for item.attribute in seq %}...{% endfor %}", - ) - pytest.raises( - TemplateSyntaxError, - env.from_string, - "{% for foo, bar.baz in seq %}...{% endfor %}", - ) + pytest.raises( + TemplateSyntaxError, + env.from_string, + "{% for item.attribute in seq %}...{% endfor %}", + ) + pytest.raises( + TemplateSyntaxError, + env.from_string, + "{% for foo, bar.baz in seq %}...{% endfor %}", + ) def test_template_data(self, env): env = Environment(autoescape=True) - t = env.from_string( - "{% macro say_hello(name) %}" - "<p>Hello {{ name }}!</p>{% endmacro %}" - '{{ say_hello("<blink>foo</blink>") }}' - ) - escaped_out = "<p>Hello <blink>foo</blink>!</p>" + t = env.from_string( + "{% macro say_hello(name) %}" + "<p>Hello {{ name }}!</p>{% endmacro %}" + '{{ say_hello("<blink>foo</blink>") }}' + ) + escaped_out = "<p>Hello <blink>foo</blink>!</p>" assert t.render() == escaped_out - assert str(t.module) == escaped_out + assert str(t.module) == escaped_out assert escape(t.module) == escaped_out - assert t.module.say_hello("<blink>foo</blink>") == escaped_out - assert ( - escape(t.module.say_hello(EvalContext(env), "<blink>foo</blink>")) - == escaped_out - ) - assert escape(t.module.say_hello("<blink>foo</blink>")) == escaped_out + assert t.module.say_hello("<blink>foo</blink>") == escaped_out + assert ( + escape(t.module.say_hello(EvalContext(env), "<blink>foo</blink>")) + == escaped_out + ) + assert escape(t.module.say_hello("<blink>foo</blink>")) == escaped_out def test_attr_filter(self, env): env = SandboxedEnvironment() @@ -98,76 +98,76 @@ class TestSandbox: def test_binary_operator_intercepting(self, env): def disable_op(left, right): - raise TemplateRuntimeError("that operator so does not work") - - for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"): + raise TemplateRuntimeError("that operator so does not work") + + for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"): env = SandboxedEnvironment() - env.binop_table["+"] = disable_op - t = env.from_string(f"{{{{ {expr} }}}}") + env.binop_table["+"] = disable_op + t = env.from_string(f"{{{{ {expr} }}}}") assert t.render(ctx) == rv - env.intercepted_binops = frozenset(["+"]) - t = env.from_string(f"{{{{ {expr} }}}}") - with pytest.raises(TemplateRuntimeError): + env.intercepted_binops = frozenset(["+"]) + t = env.from_string(f"{{{{ {expr} }}}}") + with pytest.raises(TemplateRuntimeError): t.render(ctx) def test_unary_operator_intercepting(self, env): def disable_op(arg): - raise TemplateRuntimeError("that operator so does not work") - - for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"): + raise TemplateRuntimeError("that operator so does not work") + + for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"): env = SandboxedEnvironment() - env.unop_table["-"] = disable_op - t = env.from_string(f"{{{{ {expr} }}}}") + env.unop_table["-"] = disable_op + t = env.from_string(f"{{{{ {expr} }}}}") assert t.render(ctx) == rv - env.intercepted_unops = frozenset(["-"]) - t = env.from_string(f"{{{{ {expr} }}}}") - with pytest.raises(TemplateRuntimeError): + env.intercepted_unops = frozenset(["-"]) + t = env.from_string(f"{{{{ {expr} }}}}") + with pytest.raises(TemplateRuntimeError): t.render(ctx) -class TestStringFormat: +class TestStringFormat: def test_basic_format_safety(self): env = SandboxedEnvironment() t = env.from_string('{{ "a{0.__class__}b".format(42) }}') - assert t.render() == "ab" + assert t.render() == "ab" def test_basic_format_all_okay(self): env = SandboxedEnvironment() t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}') - assert t.render() == "a42b" + assert t.render() == "a42b" def test_safe_format_safety(self): env = SandboxedEnvironment() t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}') - assert t.render() == "ab<foo>" + assert t.render() == "ab<foo>" def test_safe_format_all_okay(self): env = SandboxedEnvironment() t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}') - assert t.render() == "a42b<foo>" - - def test_empty_braces_format(self): - env = SandboxedEnvironment() - t1 = env.from_string('{{ ("a{}b{}").format("foo", "42")}}') - t2 = env.from_string('{{ ("a{}b{}"|safe).format(42, "<foo>") }}') - assert t1.render() == "afoob42" - assert t2.render() == "a42b<foo>" - - -class TestStringFormatMap: - def test_basic_format_safety(self): - env = SandboxedEnvironment() - t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}') - assert t.render() == "ab" - - def test_basic_format_all_okay(self): - env = SandboxedEnvironment() - t = env.from_string('{{ "a{x.foo}b".format_map({"x":{"foo": 42}}) }}') - assert t.render() == "a42b" - - def test_safe_format_all_okay(self): - env = SandboxedEnvironment() - t = env.from_string( - '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}' - ) - assert t.render() == "a42b<foo>" + assert t.render() == "a42b<foo>" + + def test_empty_braces_format(self): + env = SandboxedEnvironment() + t1 = env.from_string('{{ ("a{}b{}").format("foo", "42")}}') + t2 = env.from_string('{{ ("a{}b{}"|safe).format(42, "<foo>") }}') + assert t1.render() == "afoob42" + assert t2.render() == "a42b<foo>" + + +class TestStringFormatMap: + def test_basic_format_safety(self): + env = SandboxedEnvironment() + t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}') + assert t.render() == "ab" + + def test_basic_format_all_okay(self): + env = SandboxedEnvironment() + t = env.from_string('{{ "a{x.foo}b".format_map({"x":{"foo": 42}}) }}') + assert t.render() == "a42b" + + def test_safe_format_all_okay(self): + env = SandboxedEnvironment() + t = env.from_string( + '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}' + ) + assert t.render() == "a42b<foo>" diff --git a/contrib/python/Jinja2/py3/tests/test_tests.py b/contrib/python/Jinja2/py3/tests/test_tests.py index 75178d6adf..5cb4f57e17 100644 --- a/contrib/python/Jinja2/py3/tests/test_tests.py +++ b/contrib/python/Jinja2/py3/tests/test_tests.py @@ -1,233 +1,233 @@ -import pytest -from markupsafe import Markup +import pytest +from markupsafe import Markup -from jinja2 import Environment -from jinja2 import TemplateAssertionError -from jinja2 import TemplateRuntimeError +from jinja2 import Environment +from jinja2 import TemplateAssertionError +from jinja2 import TemplateRuntimeError -class MyDict(dict): - pass +class MyDict(dict): + pass -class TestTestsCase: +class TestTestsCase: def test_defined(self, env): - tmpl = env.from_string("{{ missing is defined }}|{{ true is defined }}") - assert tmpl.render() == "False|True" + tmpl = env.from_string("{{ missing is defined }}|{{ true is defined }}") + assert tmpl.render() == "False|True" def test_even(self, env): - tmpl = env.from_string("""{{ 1 is even }}|{{ 2 is even }}""") - assert tmpl.render() == "False|True" + tmpl = env.from_string("""{{ 1 is even }}|{{ 2 is even }}""") + assert tmpl.render() == "False|True" def test_odd(self, env): - tmpl = env.from_string("""{{ 1 is odd }}|{{ 2 is odd }}""") - assert tmpl.render() == "True|False" + tmpl = env.from_string("""{{ 1 is odd }}|{{ 2 is odd }}""") + assert tmpl.render() == "True|False" def test_lower(self, env): - tmpl = env.from_string("""{{ "foo" is lower }}|{{ "FOO" is lower }}""") - assert tmpl.render() == "True|False" - - # Test type checks - @pytest.mark.parametrize( - "op,expect", - ( - ("none is none", True), - ("false is none", False), - ("true is none", False), - ("42 is none", False), - ("none is true", False), - ("false is true", False), - ("true is true", True), - ("0 is true", False), - ("1 is true", False), - ("42 is true", False), - ("none is false", False), - ("false is false", True), - ("true is false", False), - ("0 is false", False), - ("1 is false", False), - ("42 is false", False), - ("none is boolean", False), - ("false is boolean", True), - ("true is boolean", True), - ("0 is boolean", False), - ("1 is boolean", False), - ("42 is boolean", False), - ("0.0 is boolean", False), - ("1.0 is boolean", False), - ("3.14159 is boolean", False), - ("none is integer", False), - ("false is integer", False), - ("true is integer", False), - ("42 is integer", True), - ("3.14159 is integer", False), - ("(10 ** 100) is integer", True), - ("none is float", False), - ("false is float", False), - ("true is float", False), - ("42 is float", False), - ("4.2 is float", True), - ("(10 ** 100) is float", False), - ("none is number", False), - ("false is number", True), - ("true is number", True), - ("42 is number", True), - ("3.14159 is number", True), - ("complex is number", True), - ("(10 ** 100) is number", True), - ("none is string", False), - ("false is string", False), - ("true is string", False), - ("42 is string", False), - ('"foo" is string', True), - ("none is sequence", False), - ("false is sequence", False), - ("42 is sequence", False), - ('"foo" is sequence', True), - ("[] is sequence", True), - ("[1, 2, 3] is sequence", True), - ("{} is sequence", True), - ("none is mapping", False), - ("false is mapping", False), - ("42 is mapping", False), - ('"foo" is mapping', False), - ("[] is mapping", False), - ("{} is mapping", True), - ("mydict is mapping", True), - ("none is iterable", False), - ("false is iterable", False), - ("42 is iterable", False), - ('"foo" is iterable', True), - ("[] is iterable", True), - ("{} is iterable", True), - ("range(5) is iterable", True), - ("none is callable", False), - ("false is callable", False), - ("42 is callable", False), - ('"foo" is callable', False), - ("[] is callable", False), - ("{} is callable", False), - ("range is callable", True), - ), - ) - def test_types(self, env, op, expect): - t = env.from_string(f"{{{{ {op} }}}}") - assert t.render(mydict=MyDict(), complex=complex(1, 2)) == str(expect) + tmpl = env.from_string("""{{ "foo" is lower }}|{{ "FOO" is lower }}""") + assert tmpl.render() == "True|False" + + # Test type checks + @pytest.mark.parametrize( + "op,expect", + ( + ("none is none", True), + ("false is none", False), + ("true is none", False), + ("42 is none", False), + ("none is true", False), + ("false is true", False), + ("true is true", True), + ("0 is true", False), + ("1 is true", False), + ("42 is true", False), + ("none is false", False), + ("false is false", True), + ("true is false", False), + ("0 is false", False), + ("1 is false", False), + ("42 is false", False), + ("none is boolean", False), + ("false is boolean", True), + ("true is boolean", True), + ("0 is boolean", False), + ("1 is boolean", False), + ("42 is boolean", False), + ("0.0 is boolean", False), + ("1.0 is boolean", False), + ("3.14159 is boolean", False), + ("none is integer", False), + ("false is integer", False), + ("true is integer", False), + ("42 is integer", True), + ("3.14159 is integer", False), + ("(10 ** 100) is integer", True), + ("none is float", False), + ("false is float", False), + ("true is float", False), + ("42 is float", False), + ("4.2 is float", True), + ("(10 ** 100) is float", False), + ("none is number", False), + ("false is number", True), + ("true is number", True), + ("42 is number", True), + ("3.14159 is number", True), + ("complex is number", True), + ("(10 ** 100) is number", True), + ("none is string", False), + ("false is string", False), + ("true is string", False), + ("42 is string", False), + ('"foo" is string', True), + ("none is sequence", False), + ("false is sequence", False), + ("42 is sequence", False), + ('"foo" is sequence', True), + ("[] is sequence", True), + ("[1, 2, 3] is sequence", True), + ("{} is sequence", True), + ("none is mapping", False), + ("false is mapping", False), + ("42 is mapping", False), + ('"foo" is mapping', False), + ("[] is mapping", False), + ("{} is mapping", True), + ("mydict is mapping", True), + ("none is iterable", False), + ("false is iterable", False), + ("42 is iterable", False), + ('"foo" is iterable', True), + ("[] is iterable", True), + ("{} is iterable", True), + ("range(5) is iterable", True), + ("none is callable", False), + ("false is callable", False), + ("42 is callable", False), + ('"foo" is callable', False), + ("[] is callable", False), + ("{} is callable", False), + ("range is callable", True), + ), + ) + def test_types(self, env, op, expect): + t = env.from_string(f"{{{{ {op} }}}}") + assert t.render(mydict=MyDict(), complex=complex(1, 2)) == str(expect) def test_upper(self, env): tmpl = env.from_string('{{ "FOO" is upper }}|{{ "foo" is upper }}') - assert tmpl.render() == "True|False" + assert tmpl.render() == "True|False" def test_equalto(self, env): tmpl = env.from_string( - "{{ foo is eq 12 }}|" - "{{ foo is eq 0 }}|" - "{{ foo is eq (3 * 4) }}|" + "{{ foo is eq 12 }}|" + "{{ foo is eq 0 }}|" + "{{ foo is eq (3 * 4) }}|" '{{ bar is eq "baz" }}|' '{{ bar is eq "zab" }}|' '{{ bar is eq ("ba" + "z") }}|' - "{{ bar is eq bar }}|" - "{{ bar is eq foo }}" + "{{ bar is eq bar }}|" + "{{ bar is eq foo }}" ) - assert ( - tmpl.render(foo=12, bar="baz") - == "True|False|True|True|False|True|True|False" - ) - - @pytest.mark.parametrize( - "op,expect", - ( - ("eq 2", True), - ("eq 3", False), - ("ne 3", True), - ("ne 2", False), - ("lt 3", True), - ("lt 2", False), - ("le 2", True), - ("le 1", False), - ("gt 1", True), - ("gt 2", False), - ("ge 2", True), - ("ge 3", False), - ), - ) + assert ( + tmpl.render(foo=12, bar="baz") + == "True|False|True|True|False|True|True|False" + ) + + @pytest.mark.parametrize( + "op,expect", + ( + ("eq 2", True), + ("eq 3", False), + ("ne 3", True), + ("ne 2", False), + ("lt 3", True), + ("lt 2", False), + ("le 2", True), + ("le 1", False), + ("gt 1", True), + ("gt 2", False), + ("ge 2", True), + ("ge 3", False), + ), + ) def test_compare_aliases(self, env, op, expect): - t = env.from_string(f"{{{{ 2 is {op} }}}}") + t = env.from_string(f"{{{{ 2 is {op} }}}}") assert t.render() == str(expect) def test_sameas(self, env): - tmpl = env.from_string("{{ foo is sameas false }}|{{ 0 is sameas false }}") - assert tmpl.render(foo=False) == "True|False" + tmpl = env.from_string("{{ foo is sameas false }}|{{ 0 is sameas false }}") + assert tmpl.render(foo=False) == "True|False" def test_no_paren_for_arg1(self, env): - tmpl = env.from_string("{{ foo is sameas none }}") - assert tmpl.render(foo=None) == "True" + tmpl = env.from_string("{{ foo is sameas none }}") + assert tmpl.render(foo=None) == "True" def test_escaped(self, env): env = Environment(autoescape=True) - tmpl = env.from_string("{{ x is escaped }}|{{ y is escaped }}") - assert tmpl.render(x="foo", y=Markup("foo")) == "False|True" + tmpl = env.from_string("{{ x is escaped }}|{{ y is escaped }}") + assert tmpl.render(x="foo", y=Markup("foo")) == "False|True" def test_greaterthan(self, env): - tmpl = env.from_string("{{ 1 is greaterthan 0 }}|{{ 0 is greaterthan 1 }}") - assert tmpl.render() == "True|False" + tmpl = env.from_string("{{ 1 is greaterthan 0 }}|{{ 0 is greaterthan 1 }}") + assert tmpl.render() == "True|False" def test_lessthan(self, env): - tmpl = env.from_string("{{ 0 is lessthan 1 }}|{{ 1 is lessthan 0 }}") - assert tmpl.render() == "True|False" + tmpl = env.from_string("{{ 0 is lessthan 1 }}|{{ 1 is lessthan 0 }}") + assert tmpl.render() == "True|False" def test_multiple_tests(self): items = [] - + def matching(x, y): items.append((x, y)) return False - + env = Environment() - env.tests["matching"] = matching - tmpl = env.from_string( - "{{ 'us-west-1' is matching '(us-east-1|ap-northeast-1)'" - " or 'stage' is matching '(dev|stage)' }}" - ) - assert tmpl.render() == "False" - assert items == [ - ("us-west-1", "(us-east-1|ap-northeast-1)"), - ("stage", "(dev|stage)"), - ] + env.tests["matching"] = matching + tmpl = env.from_string( + "{{ 'us-west-1' is matching '(us-east-1|ap-northeast-1)'" + " or 'stage' is matching '(dev|stage)' }}" + ) + assert tmpl.render() == "False" + assert items == [ + ("us-west-1", "(us-east-1|ap-northeast-1)"), + ("stage", "(dev|stage)"), + ] def test_in(self, env): - tmpl = env.from_string( - '{{ "o" is in "foo" }}|' - '{{ "foo" is in "foo" }}|' - '{{ "b" is in "foo" }}|' - "{{ 1 is in ((1, 2)) }}|" - "{{ 3 is in ((1, 2)) }}|" - "{{ 1 is in [1, 2] }}|" - "{{ 3 is in [1, 2] }}|" - '{{ "foo" is in {"foo": 1}}}|' - '{{ "baz" is in {"bar": 1}}}' - ) - assert tmpl.render() == "True|True|False|True|False|True|False|True|False" - - -def test_name_undefined(env): - with pytest.raises(TemplateAssertionError, match="No test named 'f'"): - env.from_string("{{ x is f }}") - - -def test_name_undefined_in_if(env): - t = env.from_string("{% if x is defined %}{{ x is f }}{% endif %}") - assert t.render() == "" - - with pytest.raises(TemplateRuntimeError, match="No test named 'f'"): - t.render(x=1) - - -def test_is_filter(env): - assert env.call_test("filter", "title") - assert not env.call_test("filter", "bad-name") - - -def test_is_test(env): - assert env.call_test("test", "number") - assert not env.call_test("test", "bad-name") + tmpl = env.from_string( + '{{ "o" is in "foo" }}|' + '{{ "foo" is in "foo" }}|' + '{{ "b" is in "foo" }}|' + "{{ 1 is in ((1, 2)) }}|" + "{{ 3 is in ((1, 2)) }}|" + "{{ 1 is in [1, 2] }}|" + "{{ 3 is in [1, 2] }}|" + '{{ "foo" is in {"foo": 1}}}|' + '{{ "baz" is in {"bar": 1}}}' + ) + assert tmpl.render() == "True|True|False|True|False|True|False|True|False" + + +def test_name_undefined(env): + with pytest.raises(TemplateAssertionError, match="No test named 'f'"): + env.from_string("{{ x is f }}") + + +def test_name_undefined_in_if(env): + t = env.from_string("{% if x is defined %}{{ x is f }}{% endif %}") + assert t.render() == "" + + with pytest.raises(TemplateRuntimeError, match="No test named 'f'"): + t.render(x=1) + + +def test_is_filter(env): + assert env.call_test("filter", "title") + assert not env.call_test("filter", "bad-name") + + +def test_is_test(env): + assert env.call_test("test", "number") + assert not env.call_test("test", "bad-name") diff --git a/contrib/python/Jinja2/py3/tests/test_utils.py b/contrib/python/Jinja2/py3/tests/test_utils.py index feaf8dc1d4..cfc9bd0f5e 100644 --- a/contrib/python/Jinja2/py3/tests/test_utils.py +++ b/contrib/python/Jinja2/py3/tests/test_utils.py @@ -1,21 +1,21 @@ -import pickle -import random -from collections import deque -from copy import copy as shallow_copy +import pickle +import random +from collections import deque +from copy import copy as shallow_copy import pytest -from markupsafe import Markup +from markupsafe import Markup -from jinja2.utils import consume -from jinja2.utils import generate_lorem_ipsum -from jinja2.utils import LRUCache -from jinja2.utils import missing -from jinja2.utils import object_type_repr -from jinja2.utils import select_autoescape -from jinja2.utils import urlize +from jinja2.utils import consume +from jinja2.utils import generate_lorem_ipsum +from jinja2.utils import LRUCache +from jinja2.utils import missing +from jinja2.utils import object_type_repr +from jinja2.utils import select_autoescape +from jinja2.utils import urlize -class TestLRUCache: +class TestLRUCache: def test_simple(self): d = LRUCache(3) d["a"] = 1 @@ -23,18 +23,18 @@ class TestLRUCache: d["c"] = 3 d["a"] d["d"] = 4 - assert d.keys() == ["d", "a", "c"] - - def test_values(self): - cache = LRUCache(3) - cache["b"] = 1 - cache["a"] = 2 - assert cache.values() == [2, 1] - - def test_values_empty(self): - cache = LRUCache(2) - assert cache.values() == [] - + assert d.keys() == ["d", "a", "c"] + + def test_values(self): + cache = LRUCache(3) + cache["b"] = 1 + cache["a"] = 2 + assert cache.values() == [2, 1] + + def test_values_empty(self): + cache = LRUCache(2) + assert cache.values() == [] + def test_pickleable(self): cache = LRUCache(2) cache["foo"] = 42 @@ -47,139 +47,139 @@ class TestLRUCache: assert copy._mapping == cache._mapping assert copy._queue == cache._queue - @pytest.mark.parametrize("copy_func", [LRUCache.copy, shallow_copy]) - def test_copy(self, copy_func): - cache = LRUCache(2) - cache["a"] = 1 - cache["b"] = 2 - copy = copy_func(cache) - assert copy._queue == cache._queue - copy["c"] = 3 - assert copy._queue != cache._queue - assert copy.keys() == ["c", "b"] - - def test_clear(self): - d = LRUCache(3) - d["a"] = 1 - d["b"] = 2 - d["c"] = 3 - d.clear() - assert d.__getstate__() == {"capacity": 3, "_mapping": {}, "_queue": deque([])} - - def test_repr(self): - d = LRUCache(3) - d["a"] = 1 - d["b"] = 2 - d["c"] = 3 - # Sort the strings - mapping is unordered - assert sorted(repr(d)) == sorted("<LRUCache {'a': 1, 'b': 2, 'c': 3}>") - - def test_items(self): - """Test various items, keys, values and iterators of LRUCache.""" - d = LRUCache(3) - d["a"] = 1 - d["b"] = 2 - d["c"] = 3 - assert d.items() == [("c", 3), ("b", 2), ("a", 1)] - assert d.keys() == ["c", "b", "a"] - assert d.values() == [3, 2, 1] - assert list(reversed(d)) == ["a", "b", "c"] - - # Change the cache a little - d["b"] - d["a"] = 4 - assert d.items() == [("a", 4), ("b", 2), ("c", 3)] - assert d.keys() == ["a", "b", "c"] - assert d.values() == [4, 2, 3] - assert list(reversed(d)) == ["c", "b", "a"] - - def test_setdefault(self): - d = LRUCache(3) - assert len(d) == 0 - assert d.setdefault("a") is None - assert d.setdefault("a", 1) is None - assert len(d) == 1 - assert d.setdefault("b", 2) == 2 - assert len(d) == 2 - - -class TestHelpers: + @pytest.mark.parametrize("copy_func", [LRUCache.copy, shallow_copy]) + def test_copy(self, copy_func): + cache = LRUCache(2) + cache["a"] = 1 + cache["b"] = 2 + copy = copy_func(cache) + assert copy._queue == cache._queue + copy["c"] = 3 + assert copy._queue != cache._queue + assert copy.keys() == ["c", "b"] + + def test_clear(self): + d = LRUCache(3) + d["a"] = 1 + d["b"] = 2 + d["c"] = 3 + d.clear() + assert d.__getstate__() == {"capacity": 3, "_mapping": {}, "_queue": deque([])} + + def test_repr(self): + d = LRUCache(3) + d["a"] = 1 + d["b"] = 2 + d["c"] = 3 + # Sort the strings - mapping is unordered + assert sorted(repr(d)) == sorted("<LRUCache {'a': 1, 'b': 2, 'c': 3}>") + + def test_items(self): + """Test various items, keys, values and iterators of LRUCache.""" + d = LRUCache(3) + d["a"] = 1 + d["b"] = 2 + d["c"] = 3 + assert d.items() == [("c", 3), ("b", 2), ("a", 1)] + assert d.keys() == ["c", "b", "a"] + assert d.values() == [3, 2, 1] + assert list(reversed(d)) == ["a", "b", "c"] + + # Change the cache a little + d["b"] + d["a"] = 4 + assert d.items() == [("a", 4), ("b", 2), ("c", 3)] + assert d.keys() == ["a", "b", "c"] + assert d.values() == [4, 2, 3] + assert list(reversed(d)) == ["c", "b", "a"] + + def test_setdefault(self): + d = LRUCache(3) + assert len(d) == 0 + assert d.setdefault("a") is None + assert d.setdefault("a", 1) is None + assert len(d) == 1 + assert d.setdefault("b", 2) == 2 + assert len(d) == 2 + + +class TestHelpers: def test_object_type_repr(self): - class X: + class X: pass - assert object_type_repr(42) == "int object" - assert object_type_repr([]) == "list object" - assert object_type_repr(X()) == "__tests__.test_utils.X object" - assert object_type_repr(None) == "None" - assert object_type_repr(Ellipsis) == "Ellipsis" - + assert object_type_repr(42) == "int object" + assert object_type_repr([]) == "list object" + assert object_type_repr(X()) == "__tests__.test_utils.X object" + assert object_type_repr(None) == "None" + assert object_type_repr(Ellipsis) == "Ellipsis" + def test_autoescape_select(self): func = select_autoescape( - enabled_extensions=("html", ".htm"), - disabled_extensions=("txt",), - default_for_string="STRING", - default="NONE", + enabled_extensions=("html", ".htm"), + disabled_extensions=("txt",), + default_for_string="STRING", + default="NONE", ) - assert func(None) == "STRING" - assert func("unknown.foo") == "NONE" - assert func("foo.html") - assert func("foo.htm") - assert not func("foo.txt") - assert func("FOO.HTML") - assert not func("FOO.TXT") + assert func(None) == "STRING" + assert func("unknown.foo") == "NONE" + assert func("foo.html") + assert func("foo.htm") + assert not func("foo.txt") + assert func("FOO.HTML") + assert not func("FOO.TXT") -class TestEscapeUrlizeTarget: +class TestEscapeUrlizeTarget: def test_escape_urlize_target(self): url = "http://example.org" target = "<script>" - assert urlize(url, target=target) == ( - '<a href="http://example.org"' - ' target="<script>">' - "http://example.org</a>" - ) - - -class TestLoremIpsum: - def test_lorem_ipsum_markup(self): - """Test that output of lorem_ipsum is Markup by default.""" - assert isinstance(generate_lorem_ipsum(), Markup) - - def test_lorem_ipsum_html(self): - """Test that output of lorem_ipsum is a string_type when not html.""" - assert isinstance(generate_lorem_ipsum(html=False), str) - - def test_lorem_ipsum_n(self): - """Test that the n (number of lines) works as expected.""" - assert generate_lorem_ipsum(n=0, html=False) == "" - for n in range(1, 50): - assert generate_lorem_ipsum(n=n, html=False).count("\n") == (n - 1) * 2 - - def test_lorem_ipsum_min(self): - """Test that at least min words are in the output of each line""" - for _ in range(5): - m = random.randrange(20, 99) - for _ in range(10): - assert generate_lorem_ipsum(n=1, min=m, html=False).count(" ") >= m - 1 - - def test_lorem_ipsum_max(self): - """Test that at least max words are in the output of each line""" - for _ in range(5): - m = random.randrange(21, 100) - for _ in range(10): - assert generate_lorem_ipsum(n=1, max=m, html=False).count(" ") < m - 1 - - -def test_missing(): - """Test the repr of missing.""" - assert repr(missing) == "missing" - - -def test_consume(): - """Test that consume consumes an iterator.""" - x = iter([1, 2, 3, 4, 5]) - consume(x) - with pytest.raises(StopIteration): - next(x) + assert urlize(url, target=target) == ( + '<a href="http://example.org"' + ' target="<script>">' + "http://example.org</a>" + ) + + +class TestLoremIpsum: + def test_lorem_ipsum_markup(self): + """Test that output of lorem_ipsum is Markup by default.""" + assert isinstance(generate_lorem_ipsum(), Markup) + + def test_lorem_ipsum_html(self): + """Test that output of lorem_ipsum is a string_type when not html.""" + assert isinstance(generate_lorem_ipsum(html=False), str) + + def test_lorem_ipsum_n(self): + """Test that the n (number of lines) works as expected.""" + assert generate_lorem_ipsum(n=0, html=False) == "" + for n in range(1, 50): + assert generate_lorem_ipsum(n=n, html=False).count("\n") == (n - 1) * 2 + + def test_lorem_ipsum_min(self): + """Test that at least min words are in the output of each line""" + for _ in range(5): + m = random.randrange(20, 99) + for _ in range(10): + assert generate_lorem_ipsum(n=1, min=m, html=False).count(" ") >= m - 1 + + def test_lorem_ipsum_max(self): + """Test that at least max words are in the output of each line""" + for _ in range(5): + m = random.randrange(21, 100) + for _ in range(10): + assert generate_lorem_ipsum(n=1, max=m, html=False).count(" ") < m - 1 + + +def test_missing(): + """Test the repr of missing.""" + assert repr(missing) == "missing" + + +def test_consume(): + """Test that consume consumes an iterator.""" + x = iter([1, 2, 3, 4, 5]) + consume(x) + with pytest.raises(StopIteration): + next(x) diff --git a/contrib/python/Jinja2/py3/tests/ya.make b/contrib/python/Jinja2/py3/tests/ya.make index 019fe0025e..680b0c2cde 100644 --- a/contrib/python/Jinja2/py3/tests/ya.make +++ b/contrib/python/Jinja2/py3/tests/ya.make @@ -1,54 +1,54 @@ -PY3TEST() - -OWNER(g:python-contrib) - -PEERDIR( - contrib/python/Jinja2 -) - -PY_SRCS( - TOP_LEVEL - res/__init__.py -) - -DATA( - arcadia/contrib/python/Jinja2/py3/tests/res -) - -RESOURCE_FILES( - PREFIX contrib/python/Jinja2/py3/tests/ - res/templates/broken.html - res/templates/foo/test.html - res/templates/mojibake.txt - res/templates/syntaxerror.html - res/templates/test.html - res/templates2/foo -) - -TEST_SRCS( - conftest.py - test_api.py - test_async_filters.py - test_async.py - test_bytecode_cache.py - test_core_tags.py - test_debug.py - test_ext.py - test_features.py - test_filters.py - test_idtracking.py - test_imports.py - test_inheritance.py - test_lexnparse.py - test_loader.py - test_nativetypes.py - test_regression.py - test_runtime.py - test_security.py - test_tests.py - test_utils.py -) - -NO_LINT() - -END() +PY3TEST() + +OWNER(g:python-contrib) + +PEERDIR( + contrib/python/Jinja2 +) + +PY_SRCS( + TOP_LEVEL + res/__init__.py +) + +DATA( + arcadia/contrib/python/Jinja2/py3/tests/res +) + +RESOURCE_FILES( + PREFIX contrib/python/Jinja2/py3/tests/ + res/templates/broken.html + res/templates/foo/test.html + res/templates/mojibake.txt + res/templates/syntaxerror.html + res/templates/test.html + res/templates2/foo +) + +TEST_SRCS( + conftest.py + test_api.py + test_async_filters.py + test_async.py + test_bytecode_cache.py + test_core_tags.py + test_debug.py + test_ext.py + test_features.py + test_filters.py + test_idtracking.py + test_imports.py + test_inheritance.py + test_lexnparse.py + test_loader.py + test_nativetypes.py + test_regression.py + test_runtime.py + test_security.py + test_tests.py + test_utils.py +) + +NO_LINT() + +END() diff --git a/contrib/python/Jinja2/py3/ya.make b/contrib/python/Jinja2/py3/ya.make index 3043e7fc3d..eedc6e985f 100644 --- a/contrib/python/Jinja2/py3/ya.make +++ b/contrib/python/Jinja2/py3/ya.make @@ -1,24 +1,24 @@ -# Generated by devtools/yamaker (pypi). +# Generated by devtools/yamaker (pypi). + +PY3_LIBRARY() -PY3_LIBRARY() - -OWNER(floatdrop g:python-contrib) +OWNER(floatdrop g:python-contrib) VERSION(3.0.3) -LICENSE(BSD-3-Clause) +LICENSE(BSD-3-Clause) PEERDIR( contrib/python/MarkupSafe ) -NO_LINT() - +NO_LINT() + PY_SRCS( TOP_LEVEL jinja2/__init__.py jinja2/_identifier.py - jinja2/async_utils.py + jinja2/async_utils.py jinja2/bccache.py jinja2/compiler.py jinja2/constants.py @@ -43,16 +43,16 @@ PY_SRCS( jinja2/visitor.py ) -RESOURCE_FILES( - PREFIX contrib/python/Jinja2/py3/ - .dist-info/METADATA - .dist-info/entry_points.txt - .dist-info/top_level.txt - jinja2/py.typed -) - +RESOURCE_FILES( + PREFIX contrib/python/Jinja2/py3/ + .dist-info/METADATA + .dist-info/entry_points.txt + .dist-info/top_level.txt + jinja2/py.typed +) + END() - -RECURSE_FOR_TESTS( - tests -) + +RECURSE_FOR_TESTS( + tests +) |