diff -Nru jinja2-3.0.3/CHANGES.rst jinja2-3.1.2/CHANGES.rst --- jinja2-3.0.3/CHANGES.rst 2021-11-09 20:25:25.000000000 +0000 +++ jinja2-3.1.2/CHANGES.rst 2022-04-28 17:20:15.000000000 +0000 @@ -1,5 +1,65 @@ .. currentmodule:: jinja2 +Version 3.1.2 +------------- + +Released 2022-04-28 + +- Add parameters to ``Environment.overlay`` to match ``__init__``. + :issue:`1645` +- Handle race condition in ``FileSystemBytecodeCache``. :issue:`1654` + + +Version 3.1.1 +------------- + +Released 2022-03-25 + +- The template filename on Windows uses the primary path separator. + :issue:`1637` + + +Version 3.1.0 +------------- + +Released 2022-03-24 + +- Drop support for Python 3.6. :pr:`1534` +- Remove previously deprecated code. :pr:`1544` + + - ``WithExtension`` and ``AutoEscapeExtension`` are built-in now. + - ``contextfilter`` and ``contextfunction`` are replaced by + ``pass_context``. ``evalcontextfilter`` and + ``evalcontextfunction`` are replaced by ``pass_eval_context``. + ``environmentfilter`` and ``environmentfunction`` are replaced + by ``pass_environment``. + - ``Markup`` and ``escape`` should be imported from MarkupSafe. + - Compiled templates from very old Jinja versions may need to be + recompiled. + - Legacy resolve mode for ``Context`` subclasses is no longer + supported. Override ``resolve_or_missing`` instead of + ``resolve``. + - ``unicode_urlencode`` is renamed to ``url_quote``. + +- Add support for native types in macros. :issue:`1510` +- The ``{% trans %}`` tag can use ``pgettext`` and ``npgettext`` by + passing a context string as the first token in the tag, like + ``{% trans "title" %}``. :issue:`1430` +- Update valid identifier characters from Python 3.6 to 3.7. + :pr:`1571` +- Filters and tests decorated with ``@async_variant`` are pickleable. + :pr:`1612` +- Add ``items`` filter. :issue:`1561` +- Subscriptions (``[0]``, etc.) can be used after filters, tests, and + calls when the environment is in async mode. :issue:`1573` +- The ``groupby`` filter is case-insensitive by default, matching + other comparison filters. Added the ``case_sensitive`` parameter to + control this. :issue:`1463` +- Windows drive-relative path segments in template names will not + result in ``FileSystemLoader`` and ``PackageLoader`` loading from + drive-relative paths. :pr:`1621` + + Version 3.0.3 ------------- diff -Nru jinja2-3.0.3/debian/changelog jinja2-3.1.2/debian/changelog --- jinja2-3.0.3/debian/changelog 2022-09-13 08:26:29.000000000 +0000 +++ jinja2-3.1.2/debian/changelog 2023-02-24 15:15:45.000000000 +0000 @@ -1,3 +1,17 @@ +jinja2 (3.1.2-1) unstable; urgency=medium + + [ Thomas Goirand ] + * Add python3-pytest as build-depends (now run tests at build time). + * Add autopkgtest. + + [ Piotr Ożarowski ] + * New upstream release (closes: 1025808, 1023637) + * Add 0003-fix-nose-leftovers patch + * Bump minimum required python3-pallets-sphinx-themes build dependency + (closes: 1005864) + + -- Piotr Ożarowski Fri, 24 Feb 2023 16:15:45 +0100 + jinja2 (3.0.3-2) unstable; urgency=medium * Team upload. diff -Nru jinja2-3.0.3/debian/control jinja2-3.1.2/debian/control --- jinja2-3.0.3/debian/control 2022-09-13 08:26:29.000000000 +0000 +++ jinja2-3.1.2/debian/control 2022-11-07 15:02:25.000000000 +0000 @@ -10,8 +10,9 @@ python3-all, python3-babel, python3-markupsafe (>= 2.0), - python3-pallets-sphinx-themes, + python3-pallets-sphinx-themes (>= 2.0.2), python3-pygments, + python3-pytest, python3-setuptools, python3-sphinx, python3-sphinx-issues, diff -Nru jinja2-3.0.3/debian/patches/0003-fix-nose-leftovers.patch jinja2-3.1.2/debian/patches/0003-fix-nose-leftovers.patch --- jinja2-3.0.3/debian/patches/0003-fix-nose-leftovers.patch 1970-01-01 00:00:00.000000000 +0000 +++ jinja2-3.1.2/debian/patches/0003-fix-nose-leftovers.patch 2023-02-24 15:09:22.000000000 +0000 @@ -0,0 +1,38 @@ +From: =?utf-8?q?Piotr_O=C5=BCarowski?= +Date: Fri, 24 Feb 2023 16:06:57 +0100 +Subject: fix nose leftovers + +taken from upstream repo +--- + tests/test_loader.py | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/tests/test_loader.py b/tests/test_loader.py +index 04c921d..77d686e 100644 +--- a/tests/test_loader.py ++++ b/tests/test_loader.py +@@ -183,6 +183,7 @@ class TestFileSystemLoader: + + class TestModuleLoader: + archive = None ++ mod_env = None + + def compile_down(self, prefix_loader, zip="deflated"): + log = [] +@@ -196,13 +197,14 @@ class TestModuleLoader: + self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive)) + return "".join(log) + +- def teardown(self): +- if hasattr(self, "mod_env"): ++ def teardown_method(self): ++ if self.archive is not None: + if os.path.isfile(self.archive): + os.remove(self.archive) + else: + shutil.rmtree(self.archive) + self.archive = None ++ self.mod_env = None + + def test_log(self, prefix_loader): + log = self.compile_down(prefix_loader) diff -Nru jinja2-3.0.3/debian/patches/py3.9-fix-collections-import.patch jinja2-3.1.2/debian/patches/py3.9-fix-collections-import.patch --- jinja2-3.0.3/debian/patches/py3.9-fix-collections-import.patch 2022-09-13 08:26:29.000000000 +0000 +++ jinja2-3.1.2/debian/patches/py3.9-fix-collections-import.patch 2023-02-24 15:09:22.000000000 +0000 @@ -24,7 +24,7 @@ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/jinja2/lexer.py b/src/jinja2/lexer.py -index c25ab0f..c28c0db 100644 +index aff7e9f..37ef342 100644 --- a/src/jinja2/lexer.py +++ b/src/jinja2/lexer.py @@ -6,7 +6,10 @@ template code and python code in expressions. @@ -56,7 +56,7 @@ from markupsafe import Markup diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py -index 4294884..9f96486 100644 +index 06d7414..f443c18 100644 --- a/src/jinja2/sandbox.py +++ b/src/jinja2/sandbox.py @@ -6,7 +6,10 @@ import types @@ -72,12 +72,12 @@ from markupsafe import EscapeFormatter diff --git a/src/jinja2/utils.py b/src/jinja2/utils.py -index 567185f..1efaece 100644 +index 9b5f5a5..205b2af 100644 --- a/src/jinja2/utils.py +++ b/src/jinja2/utils.py -@@ -5,7 +5,10 @@ import re +@@ -4,7 +4,10 @@ import os + import re import typing as t - import warnings from collections import abc -from collections import deque +try: diff -Nru jinja2-3.0.3/debian/patches/series jinja2-3.1.2/debian/patches/series --- jinja2-3.0.3/debian/patches/series 2022-09-13 08:26:29.000000000 +0000 +++ jinja2-3.1.2/debian/patches/series 2023-02-24 15:09:22.000000000 +0000 @@ -1,2 +1,3 @@ py3.9-fix-collections-import.patch 0002-docs-disable-sphinxcontrib.log_cabinet.patch +0003-fix-nose-leftovers.patch diff -Nru jinja2-3.0.3/debian/rules jinja2-3.1.2/debian/rules --- jinja2-3.0.3/debian/rules 2022-09-13 08:26:29.000000000 +0000 +++ jinja2-3.1.2/debian/rules 2023-02-24 15:13:28.000000000 +0000 @@ -11,7 +11,11 @@ make -C docs clean dh_auto_clean -override_dh_auto_build-indep: +override_dh_auto_build: + # make sure installed version of jinja2 is not used during tests: + dh_auto_build -- --disable=test + pybuild --test --system=custom --test-args='PYTHONPATH="{build_dir}" {interpreter} -m pytest tests' + # build documentation PYTHONPATH=$(CURDIR)/src:$(CURDIR)/docs make -C docs html override_dh_installdocs: diff -Nru jinja2-3.0.3/debian/tests/control jinja2-3.1.2/debian/tests/control --- jinja2-3.0.3/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 +++ jinja2-3.1.2/debian/tests/control 2022-11-07 15:00:44.000000000 +0000 @@ -0,0 +1,4 @@ +Tests: unittests +Depends: + @builddeps@, +Restrictions: allow-stderr diff -Nru jinja2-3.0.3/debian/tests/unittests jinja2-3.1.2/debian/tests/unittests --- jinja2-3.0.3/debian/tests/unittests 1970-01-01 00:00:00.000000000 +0000 +++ jinja2-3.1.2/debian/tests/unittests 2022-11-07 15:00:44.000000000 +0000 @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e +set -x + +CWD=$(pwd) +PYTHON3S=$(py3versions -vs) +for i in ${PYTHON3S} ; do + PYTHON=python${i} python${i} -m pytest -v tests +done diff -Nru jinja2-3.0.3/docs/api.rst jinja2-3.1.2/docs/api.rst --- jinja2-3.0.3/docs/api.rst 2021-05-18 20:40:12.000000000 +0000 +++ jinja2-3.1.2/docs/api.rst 2022-03-24 14:16:48.000000000 +0000 @@ -273,7 +273,7 @@ filters and tests by topic. For example it's perfectly valid to add a function into the filter dict and call it `to.str`. The regular expression for filter and test identifiers is -``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*```. +``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*``. Undefined Types @@ -410,16 +410,19 @@ .. automethod:: jinja2.runtime.Context.call(callable, \*args, \**kwargs) -.. admonition:: Implementation +The context is immutable, it prevents modifications, and if it is +modified somehow despite that those changes may not show up. For +performance, Jinja does not use the context as data storage for, only as +a primary data source. Variables that the template does not define are +looked up in the context, but variables the template does define are +stored locally. + +Instead of modifying the context directly, a function should return +a value that can be assigned to a variable within the template itself. + +.. code-block:: jinja - Context is immutable for the same reason Python's frame locals are - immutable inside functions. Both Jinja and Python are not using the - context / frame locals as data storage for variables but only as primary - data source. - - When a template accesses a variable the template does not define, Jinja - looks up the variable in the context, after that the variable is treated - as if it was defined in the template. + {% set comments = get_latest_comments() %} .. _loaders: @@ -597,18 +600,6 @@ .. autofunction:: jinja2.pass_environment -.. autofunction:: jinja2.contextfilter - -.. autofunction:: jinja2.evalcontextfilter - -.. autofunction:: jinja2.environmentfilter - -.. autofunction:: jinja2.contextfunction - -.. autofunction:: jinja2.evalcontextfunction - -.. autofunction:: jinja2.environmentfunction - .. autofunction:: jinja2.clear_caches .. autofunction:: jinja2.is_undefined diff -Nru jinja2-3.0.3/docs/faq.rst jinja2-3.1.2/docs/faq.rst --- jinja2-3.0.3/docs/faq.rst 2021-11-09 16:37:43.000000000 +0000 +++ jinja2-3.1.2/docs/faq.rst 2022-03-24 14:16:48.000000000 +0000 @@ -1,175 +1,75 @@ Frequently Asked Questions ========================== -This page answers some of the often asked questions about Jinja. - -.. highlight:: html+jinja Why is it called Jinja? ----------------------- -The name Jinja was chosen because it's the name of a Japanese temple and -temple and template share a similar pronunciation. It is not named after -the city in Uganda. - -How fast is it? ---------------- - -We really hate benchmarks especially since they don't reflect much. The -performance of a template depends on many factors and you would have to -benchmark different engines in different situations. The benchmarks from the -testsuite show that Jinja has a similar performance to `Mako`_ and is between -10 and 20 times faster than Django's template engine or Genshi. These numbers -should be taken with tons of salt as the benchmarks that took these numbers -only test a few performance related situations such as looping. Generally -speaking the performance of a template engine doesn't matter much as the -usual bottleneck in a web application is either the database or the application -code. - -.. _Mako: https://www.makotemplates.org/ - -How Compatible is Jinja with Django? ------------------------------------- - -The default syntax of Jinja matches Django syntax in many ways. However -this similarity doesn't mean that you can use a Django template unmodified -in Jinja. For example filter arguments use a function call syntax rather -than a colon to separate filter name and arguments. Additionally the -extension interface in Jinja is fundamentally different from the Django one -which means that your custom tags won't work any longer. - -Generally speaking you will use much less custom extensions as the Jinja -template system allows you to use a certain subset of Python expressions -which can replace most Django extensions. For example instead of using -something like this:: - - {% load comments %} - {% get_latest_comments 10 as latest_comments %} - {% for comment in latest_comments %} - ... - {% endfor %} - -You will most likely provide an object with attributes to retrieve -comments from the database:: - - {% for comment in models.comments.latest(10) %} - ... - {% endfor %} - -Or directly provide the model for quick testing:: - - {% for comment in Comment.objects.order_by('-pub_date')[:10] %} - ... - {% endfor %} - -Please keep in mind that even though you may put such things into templates -it still isn't a good idea. Queries should go into the view code and not -the template! +"Jinja" is a Japanese `Shinto shrine`_, or temple, and temple and +template share a similar English pronunciation. It is not named after +the `city in Uganda`_. -Isn't it a terrible idea to put Logic into Templates? ------------------------------------------------------ +.. _Shinto shrine: https://en.wikipedia.org/wiki/Shinto_shrine +.. _city in Uganda: https://en.wikipedia.org/wiki/Jinja%2C_Uganda -Without a doubt you should try to remove as much logic from templates as -possible. But templates without any logic mean that you have to do all -the processing in the code which is boring and stupid. A template engine -that does that is shipped with Python and called `string.Template`. Comes -without loops and if conditions and is by far the fastest template engine -you can get for Python. - -So some amount of logic is required in templates to keep everyone happy. -And Jinja leaves it pretty much to you how much logic you want to put into -templates. There are some restrictions in what you can do and what not. - -Jinja neither allows you to put arbitrary Python code into templates nor -does it allow all Python expressions. The operators are limited to the -most common ones and more advanced expressions such as list comprehensions -and generator expressions are not supported. This keeps the template engine -easier to maintain and templates more readable. - -Why is Autoescaping not the Default? ------------------------------------- - -There are multiple reasons why automatic escaping is not the default mode -and also not the recommended one. While automatic escaping of variables -means that you will less likely have an XSS problem it also causes a huge -amount of extra processing in the template engine which can cause serious -performance problems. As Python doesn't provide a way to mark strings as -unsafe Jinja has to hack around that limitation by providing a custom -string class (the :class:`Markup` string) that safely interacts with safe -and unsafe strings. - -With explicit escaping however the template engine doesn't have to perform -any safety checks on variables. Also a human knows not to escape integers -or strings that may never contain characters one has to escape or already -HTML markup. For example when iterating over a list over a table of -integers and floats for a table of statistics the template designer can -omit the escaping because he knows that integers or floats don't contain -any unsafe parameters. - -Additionally Jinja is a general purpose template engine and not only used -for HTML/XML generation. For example you may generate LaTeX, emails, -CSS, JavaScript, or configuration files. - -Why is the Context immutable? ------------------------------ - -When writing a :func:`pass_context` function, you may have noticed that -the context tries to stop you from modifying it. If you have managed to -modify the context by using an internal context API you may have noticed -that changes in the context don't seem to be visible in the template. -The reason for this is that Jinja uses the context only as primary data -source for template variables for performance reasons. - -If you want to modify the context write a function that returns a variable -instead that one can assign to a variable by using set:: - - {% set comments = get_latest_comments() %} - -My tracebacks look weird. What's happening? -------------------------------------------- - -Jinja can rewrite tracebacks so they show the template lines numbers and -source rather than the underlying compiled code, but this requires -special Python support. CPython <3.7 requires ``ctypes``, and PyPy -requires transparent proxy support. - -If you are using Google App Engine, ``ctypes`` is not available. You can -make it available in development, but not in production. - -.. code-block:: python - - import os - if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'): - from google.appengine.tools.devappserver2.python import sandbox - sandbox._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt'] -Credit for this snippet goes to `Thomas Johansson -`_ +How fast is Jinja? +------------------ -My Macros are overridden by something -------------------------------------- +Jinja is relatively fast among template engines because it compiles and +caches template code to Python code, so that the template does not need +to be parsed and interpreted each time. Rendering a template becomes as +close to executing a Python function as possible. -In some situations the Jinja scoping appears arbitrary: +Jinja also makes extensive use of caching. Templates are cached by name +after loading, so future uses of the template avoid loading. The +template loading itself uses a bytecode cache to avoid repeated +compiling. The caches can be external to persist across restarts. +Templates can also be precompiled and loaded as fast Python imports. -layout.tmpl: +We dislike benchmarks because they don't reflect real use. Performance +depends on many factors. Different engines have different default +configurations and tradeoffs that make it unclear how to set up a useful +comparison. Often, database access, API calls, and data processing have +a much larger effect on performance than the template engine. -.. sourcecode:: jinja - {% macro foo() %}LAYOUT{% endmacro %} - {% block body %}{% endblock %} +Isn't it a bad idea to put logic in templates? +---------------------------------------------- -child.tmpl: +Without a doubt you should try to remove as much logic from templates as +possible. With less logic, the template is easier to understand, has +fewer potential side effects, and is faster to compile and render. But a +template without any logic means processing must be done in code before +rendering. A template engine that does that is shipped with Python, +called :class:`string.Template`, and while it's definitely fast it's not +convenient. + +Jinja's features such as blocks, statements, filters, and function calls +make it much easier to write expressive templates, with very few +restrictions. Jinja doesn't allow arbitrary Python code in templates, or +every feature available in the Python language. This keeps the engine +easier to maintain, and keeps templates more readable. + +Some amount of logic is required in templates to keep everyone happy. +Too much logic in the template can make it complex to reason about and +maintain. It's up to you to decide how your application will work and +balance how much logic you want to put in the template. -.. sourcecode:: jinja - {% extends 'layout.tmpl' %} - {% macro foo() %}CHILD{% endmacro %} - {% block body %}{{ foo() }}{% endblock %} +Why is HTML escaping not the default? +------------------------------------- -This will print ``LAYOUT`` in Jinja. This is a side effect of having -the parent template evaluated after the child one. This allows child -templates passing information to the parent template. To avoid this -issue rename the macro or variable in the parent template to have an -uncommon prefix. +Jinja provides a feature that can be enabled to escape HTML syntax in +rendered templates. However, it is disabled by default. -.. _Jinja 1: https://pypi.org/project/Jinja/ +Jinja is a general purpose template engine, it is not only used for HTML +documents. You can generate plain text, LaTeX, emails, CSS, JavaScript, +configuration files, etc. HTML escaping wouldn't make sense for any of +these document types. + +While automatic escaping means that you are less likely have an XSS +problem, it also requires significant extra processing during compiling +and rendering, which can reduce performance. Jinja uses MarkupSafe for +escaping, which provides optimized C code for speed, but it still +introduces overhead to track escaping across methods and formatting. diff -Nru jinja2-3.0.3/docs/integration.rst jinja2-3.1.2/docs/integration.rst --- jinja2-3.0.3/docs/integration.rst 2021-04-09 21:59:18.000000000 +0000 +++ jinja2-3.1.2/docs/integration.rst 2022-03-24 14:16:48.000000000 +0000 @@ -1,6 +1,25 @@ Integration =========== + +Flask +----- + +The `Flask`_ web application framework, also maintained by Pallets, uses +Jinja templates by default. Flask sets up a Jinja environment and +template loader for you, and provides functions to easily render +templates from view functions. + +.. _Flask: https://flask.palletsprojects.com + + +Django +------ + +Django supports using Jinja as its template engine, see +https://docs.djangoproject.com/en/stable/topics/templates/#support-for-template-engines. + + .. _babel-integration: Babel diff -Nru jinja2-3.0.3/docs/intro.rst jinja2-3.1.2/docs/intro.rst --- jinja2-3.0.3/docs/intro.rst 2021-11-09 16:37:43.000000000 +0000 +++ jinja2-3.1.2/docs/intro.rst 2022-03-24 14:16:48.000000000 +0000 @@ -30,7 +30,7 @@ ------------ We recommend using the latest version of Python. Jinja supports Python -3.6 and newer. We also recommend using a `virtual environment`_ in order +3.7 and newer. We also recommend using a `virtual environment`_ in order to isolate your project dependencies from other projects and the system. .. _virtual environment: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments diff -Nru jinja2-3.0.3/docs/sandbox.rst jinja2-3.1.2/docs/sandbox.rst --- jinja2-3.0.3/docs/sandbox.rst 2020-02-17 17:14:56.000000000 +0000 +++ jinja2-3.1.2/docs/sandbox.rst 2022-03-24 14:16:48.000000000 +0000 @@ -1,18 +1,56 @@ Sandbox ======= -The Jinja sandbox can be used to evaluate untrusted code. Access to unsafe -attributes and methods is prohibited. +The Jinja sandbox can be used to render untrusted templates. Access to +attributes, method calls, operators, mutating data structures, and +string formatting can be intercepted and prohibited. + +.. code-block:: pycon + + >>> from jinja2.sandbox import SandboxedEnvironment + >>> env = SandboxedEnvironment() + >>> func = lambda: "Hello, Sandbox!" + >>> env.from_string("{{ func() }}").render(func=func) + 'Hello, Sandbox!' + >>> env.from_string("{{ func.__code__.co_code }}").render(func=func) + Traceback (most recent call last): + ... + SecurityError: access to attribute '__code__' of 'function' object is unsafe. + +A sandboxed environment can be useful, for example, to allow users of an +internal reporting system to create custom emails. You would document +what data is available in the templates, then the user would write a +template using that information. Your code would generate the report +data and pass it to the user's sandboxed template to render. + + +Security Considerations +----------------------- + +The sandbox alone is not a solution for perfect security. Keep these +things in mind when using the sandbox. + +Templates can still raise errors when compiled or rendered. Your code +should attempt to catch errors instead of crashing. + +It is possible to construct a relatively small template that renders to +a very large amount of output, which could correspond to a high use of +CPU or memory. You should run your application with limits on resources +such as CPU and memory to mitigate this. + +Jinja only renders text, it does not understand, for example, JavaScript +code. Depending on how the rendered template will be used, you may need +to do other postprocessing to restrict the output. + +Pass only the data that is relevant to the template. Avoid passing +global data, or objects with methods that have side effects. By default +the sandbox prevents private and internal attribute access. You can +override :meth:`~SandboxedEnvironment.is_safe_attribute` to further +restrict attributes access. Decorate methods with :func:`unsafe` to +prevent calling them from templates when passing objects as data. Use +:class:`ImmutableSandboxedEnvironment` to prevent modifying lists and +dictionaries. -Assuming `env` is a :class:`SandboxedEnvironment` in the default configuration -the following piece of code shows how it works: - ->>> env.from_string("{{ func.func_code }}").render(func=lambda:None) -u'' ->>> env.from_string("{{ func.func_code.do_something }}").render(func=lambda:None) -Traceback (most recent call last): - ... -SecurityError: access to attribute 'func_code' of 'function' object is unsafe. API --- @@ -34,61 +72,40 @@ .. autofunction:: modifies_known_mutable -.. admonition:: Note - - The Jinja sandbox alone is no solution for perfect security. Especially - for web applications you have to keep in mind that users may create - templates with arbitrary HTML in so it's crucial to ensure that (if you - are running multiple users on the same server) they can't harm each other - via JavaScript insertions and much more. - - Also the sandbox is only as good as the configuration. We strongly - recommend only passing non-shared resources to the template and use - some sort of whitelisting for attributes. - - Also keep in mind that templates may raise runtime or compile time errors, - so make sure to catch them. Operator Intercepting --------------------- -.. versionadded:: 2.6 +For performance, Jinja outputs operators directly when compiling. This +means it's not possible to intercept operator behavior by overriding +:meth:`SandboxEnvironment.call ` by default, because +operator special methods are handled by the Python interpreter, and +might not correspond with exactly one method depending on the operator's +use. + +The sandbox can instruct the compiler to output a function to intercept +certain operators instead. Override +:attr:`SandboxedEnvironment.intercepted_binops` and +:attr:`SandboxedEnvironment.intercepted_unops` with the operator symbols +you want to intercept. The compiler will replace the symbols with calls +to :meth:`SandboxedEnvironment.call_binop` and +:meth:`SandboxedEnvironment.call_unop` instead. The default +implementation of those methods will use +:attr:`SandboxedEnvironment.binop_table` and +:attr:`SandboxedEnvironment.unop_table` to translate operator symbols +into :mod:`operator` functions. -For maximum performance Jinja will let operators call directly the type -specific callback methods. This means that it's not possible to have this -intercepted by overriding :meth:`Environment.call`. Furthermore a -conversion from operator to special method is not always directly possible -due to how operators work. For instance for divisions more than one -special method exist. - -With Jinja 2.6 there is now support for explicit operator intercepting. -This can be used to customize specific operators as necessary. In order -to intercept an operator one has to override the -:attr:`SandboxedEnvironment.intercepted_binops` attribute. Once the -operator that needs to be intercepted is added to that set Jinja will -generate bytecode that calls the :meth:`SandboxedEnvironment.call_binop` -function. For unary operators the `unary` attributes and methods have to -be used instead. - -The default implementation of :attr:`SandboxedEnvironment.call_binop` -will use the :attr:`SandboxedEnvironment.binop_table` to translate -operator symbols into callbacks performing the default operator behavior. +For example, the power (``**``) operator can be disabled: -This example shows how the power (``**``) operator can be disabled in -Jinja:: +.. code-block:: python from jinja2.sandbox import SandboxedEnvironment - class MyEnvironment(SandboxedEnvironment): - intercepted_binops = frozenset(['**']) + intercepted_binops = frozenset(["**"]) def call_binop(self, context, operator, left, right): - if operator == '**': - return self.undefined('the power operator is unavailable') - return SandboxedEnvironment.call_binop(self, context, - operator, left, right) - -Make sure to always call into the super method, even if you are not -intercepting the call. Jinja might internally call the method to -evaluate expressions. + if operator == "**": + return self.undefined("The power (**) operator is unavailable.") + + return super().call_binop(self, context, operator, left, right) diff -Nru jinja2-3.0.3/docs/switching.rst jinja2-3.1.2/docs/switching.rst --- jinja2-3.0.3/docs/switching.rst 2021-04-05 17:47:37.000000000 +0000 +++ jinja2-3.1.2/docs/switching.rst 2022-03-24 14:16:48.000000000 +0000 @@ -1,141 +1,73 @@ -Switching from other Template Engines +Switching From Other Template Engines ===================================== -.. highlight:: html+jinja - -If you have used a different template engine in the past and want to switch -to Jinja here is a small guide that shows the basic syntactic and semantic -changes between some common, similar text template engines for Python. - -Jinja 1 -------- - -Jinja 2 is mostly compatible with Jinja 1 in terms of API usage and template -syntax. The differences between Jinja 1 and 2 are explained in the following -list. - -API -~~~ - -Loaders - Jinja 2 uses a different loader API. Because the internal representation - of templates changed there is no longer support for external caching - systems such as memcached. The memory consumed by templates is comparable - with regular Python modules now and external caching doesn't give any - advantage. If you have used a custom loader in the past have a look at - the new :ref:`loader API `. - -Loading templates from strings - In the past it was possible to generate templates from a string with the - default environment configuration by using `jinja.from_string`. Jinja 2 - provides a :class:`Template` class that can be used to do the same, but - with optional additional configuration. - -Automatic unicode conversion - Jinja 1 performed automatic conversion of bytes in a given encoding - into unicode objects. This conversion is no longer implemented as it - was inconsistent as most libraries are using the regular Python - ASCII bytes to Unicode conversion. An application powered by Jinja 2 - *has to* use unicode internally everywhere or make sure that Jinja 2 - only gets unicode strings passed. - -i18n - Jinja 1 used custom translators for internationalization. i18n is now - available as Jinja 2 extension and uses a simpler, more gettext friendly - interface and has support for babel. For more details see - :ref:`i18n-extension`. - -Internal methods - Jinja 1 exposed a few internal methods on the environment object such - as `call_function`, `get_attribute` and others. While they were marked - as being an internal method it was possible to override them. Jinja 2 - doesn't have equivalent methods. - -Sandbox - Jinja 1 was running sandbox mode by default. Few applications actually - used that feature so it became optional in Jinja 2. For more details - about the sandboxed execution see :class:`SandboxedEnvironment`. - -Context - Jinja 1 had a stacked context as storage for variables passed to the - environment. In Jinja 2 a similar object exists but it doesn't allow - modifications nor is it a singleton. As inheritance is dynamic now - multiple context objects may exist during template evaluation. - -Filters and Tests - Filters and tests are regular functions now. It's no longer necessary - and allowed to use factory functions. - - -Templates -~~~~~~~~~ - -Jinja 2 has mostly the same syntax as Jinja 1. What's different is that -macros require parentheses around the argument list now. - -Additionally Jinja 2 allows dynamic inheritance now and dynamic includes. -The old helper function `rendertemplate` is gone now, `include` can be used -instead. Includes no longer import macros and variable assignments, for -that the new `import` tag is used. This concept is explained in the -:ref:`import` documentation. - -Another small change happened in the `for`-tag. The special loop variable -doesn't have a `parent` attribute, instead you have to alias the loop -yourself. See :ref:`accessing-the-parent-loop` for more details. +This is a brief guide on some of the differences between Jinja syntax +and other template languages. See :doc:`/templates` for a comprehensive +guide to Jinja syntax and features. Django ------ If you have previously worked with Django templates, you should find -Jinja very familiar. In fact, most of the syntax elements look and -work the same. +Jinja very familiar. Many of the syntax elements look and work the same. +However, Jinja provides some more syntax elements, and some work a bit +differently. + +This section covers the template changes. The API, including extension +support, is fundamentally different so it won't be covered here. -However, Jinja provides some more syntax elements covered in the -documentation and some work a bit different. +Django supports using Jinja as its template engine, see +https://docs.djangoproject.com/en/stable/topics/templates/#support-for-template-engines. -This section covers the template changes. As the API is fundamentally -different we won't cover it here. Method Calls ~~~~~~~~~~~~ -In Django method calls work implicitly, while Jinja requires the explicit -Python syntax. Thus this Django code:: +In Django, methods are called implicitly, without parentheses. + +.. code-block:: django {% for page in user.get_created_pages %} ... {% endfor %} -...looks like this in Jinja:: +In Jinja, using parentheses is required for calls, like in Python. This +allows you to pass variables to the method, which is not possible +in Django. This syntax is also used for calling macros. + +.. code-block:: jinja {% for page in user.get_created_pages() %} ... {% endfor %} -This allows you to pass variables to the method, which is not possible in -Django. This syntax is also used for macros. Filter Arguments ~~~~~~~~~~~~~~~~ -Jinja provides more than one argument for filters. Also the syntax for -argument passing is different. A template that looks like this in Django:: +In Django, one literal value can be passed to a filter after a colon. + +.. code-block:: django {{ items|join:", " }} -looks like this in Jinja:: +In Jinja, filters can take any number of positional and keyword +arguments in parentheses, like function calls. Arguments can also be +variables instead of literal values. + +.. code-block:: jinja - {{ items|join(', ') }} + {{ items|join(", ") }} -It is a bit more verbose, but it allows different types of arguments - -including variables - and more than one of them. Tests ~~~~~ -In addition to filters there also are tests you can perform using the is -operator. Here are some examples:: +In addition to filters, Jinja also has "tests" used with the ``is`` +operator. This operator is not the same as the Python operator. + +.. code-block:: jinja {% if user.user_id is odd %} {{ user.username|e }} is odd @@ -146,11 +78,10 @@ Loops ~~~~~ -For loops work very similarly to Django, but notably the Jinja special -variable for the loop context is called `loop`, not `forloop` as in Django. +In Django, the special variable for the loop context is called +``forloop``, and the ``empty`` is used for no loop items. -In addition, the Django `empty` argument is called `else` in Jinja. For -example, the Django template:: +.. code-block:: django {% for item in items %} {{ item }} @@ -158,52 +89,74 @@ No items! {% endfor %} -...looks like this in Jinja:: +In Jinja, the special variable for the loop context is called ``loop``, +and the ``else`` block is used for no loop items. + +.. code-block:: jinja {% for item in items %} - {{ item }} + {{ loop.index}}. {{ item }} {% else %} No items! {% endfor %} + Cycle ~~~~~ -The ``{% cycle %}`` tag does not exist in Jinja; however, you can achieve the -same output by using the `cycle` method on the loop context special variable. +In Django, the ``{% cycle %}`` can be used in a for loop to alternate +between values per loop. -The following Django template:: +.. code-block:: django {% for user in users %}
  • {{ user }}
  • {% endfor %} -...looks like this in Jinja:: +In Jinja, the ``loop`` context has a ``cycle`` method. + +.. code-block:: jinja {% for user in users %}
  • {{ user }}
  • {% endfor %} -There is no equivalent of ``{% cycle ... as variable %}``. +A cycler can also be assigned to a variable and used outside or across +loops with the ``cycle()`` global function. Mako ---- -.. highlight:: html+mako +You can configure Jinja to look more like Mako: + +.. code-block:: python -If you have used Mako so far and want to switch to Jinja you can configure -Jinja to look more like Mako: + env = Environment( + block_start_string="<%", + block_end_string="%>", + variable_start_string="${", + variable_end_string="}", + comment_start_string="<%doc>", + commend_end_string="", + line_statement_prefix="%", + line_comment_prefix="##", + ) + +With an environment configured like that, Jinja should be able to +interpret a small subset of Mako templates without any changes. + +Jinja does not support embedded Python code, so you would have to move +that out of the template. You could either process the data with the +same code before rendering, or add a global function or filter to the +Jinja environment. -.. sourcecode:: python +The syntax for defs (which are called macros in Jinja) and template +inheritance is different too. - env = Environment('<%', '%>', '${', '}', '<%doc>', '', '%', '##') +The following Mako template: -With an environment configured like that, Jinja should be able to interpret -a small subset of Mako templates. Jinja does not support embedded Python -code, so you would have to move that out of the template. The syntax for defs -(which are called macros in Jinja) and template inheritance is different too. -The following Mako template:: +.. code-block:: mako <%inherit file="layout.html" /> <%def name="title()">Page Title @@ -213,7 +166,9 @@ % endfor -Looks like this in Jinja with the above configuration:: +Looks like this in Jinja with the above configuration: + +.. code-block:: jinja <% extends "layout.html" %> <% block title %>Page Title<% endblock %> diff -Nru jinja2-3.0.3/docs/templates.rst jinja2-3.1.2/docs/templates.rst --- jinja2-3.0.3/docs/templates.rst 2021-11-09 19:04:05.000000000 +0000 +++ jinja2-3.1.2/docs/templates.rst 2022-03-24 14:16:48.000000000 +0000 @@ -938,6 +938,23 @@ If a macro name starts with an underscore, it's not exported and can't be imported. +Due to how scopes work in Jinja, a macro in a child template does not +override a macro in a parent template. The following will output +"LAYOUT", not "CHILD". + +.. code-block:: jinja + :caption: ``layout.txt`` + + {% macro foo() %}LAYOUT{% endmacro %} + {% block body %}{% endblock %} + +.. code-block:: jinja + :caption: ``child.txt`` + + {% extends 'layout.txt' %} + {% macro foo() %}CHILD{% endmacro %} + {% block body %}{{ foo() }}{% endblock %} + .. _call: @@ -1113,42 +1130,45 @@ Include ~~~~~~~ -The `include` tag is useful to include a template and return the -rendered contents of that file into the current namespace:: +The ``include`` tag renders another template and outputs the result into +the current template. + +.. code-block:: jinja {% include 'header.html' %} - Body + Body goes here. {% include 'footer.html' %} -Included templates have access to the variables of the active context by -default. For more details about context behavior of imports and includes, -see :ref:`import-visibility`. - -From Jinja 2.2 onwards, you can mark an include with ``ignore missing``; in -which case Jinja will ignore the statement if the template to be included -does not exist. When combined with ``with`` or ``without context``, it must -be placed *before* the context visibility statement. Here are some valid -examples:: +The included template has access to context of the current template by +default. Use ``without context`` to use a separate context instead. +``with context`` is also valid, but is the default behavior. See +:ref:`import-visibility`. + +The included template can ``extend`` another template and override +blocks in that template. However, the current template cannot override +any blocks that the included template outputs. +Use ``ignore missing`` to ignore the statement if the template does not +exist. It must be placed *before* a context visibility statement. + +.. code-block:: jinja + + {% include "sidebar.html" without context %} {% include "sidebar.html" ignore missing %} {% include "sidebar.html" ignore missing with context %} {% include "sidebar.html" ignore missing without context %} -.. versionadded:: 2.2 - -You can also provide a list of templates that are checked for existence -before inclusion. The first template that exists will be included. If -`ignore missing` is given, it will fall back to rendering nothing if -none of the templates exist, otherwise it will raise an exception. +If a list of templates is given, each will be tried in order until one +is not missing. This can be used with ``ignore missing`` to ignore if +none of the templates exist. -Example:: +.. code-block:: jinja {% include ['page_detailed.html', 'page.html'] %} {% include ['special_sidebar.html', 'sidebar.html'] ignore missing %} -.. versionchanged:: 2.4 - If a template object was passed to the template context, you can - include that object using `include`. +A variable, with either a template name or template object, can also be +passed to the statment. .. _import: @@ -1732,11 +1752,35 @@ .. versionadded:: 2.10 The ``trimmed`` and ``notrimmed`` modifiers have been added. +If the translation depends on the context that the message appears in, +the ``pgettext`` and ``npgettext`` functions take a ``context`` string +as the first argument, which is used to select the appropriate +translation. To specify a context with the ``{% trans %}`` tag, provide +a string as the first token after ``trans``. + +.. code-block:: jinja + + {% trans "fruit" %}apple{% endtrans %} + {% trans "fruit" trimmed count -%} + 1 apple + {%- pluralize -%} + {{ count }} apples + {%- endtrans %} + +.. versionadded:: 3.1 + A context can be passed to the ``trans`` tag to use ``pgettext`` and + ``npgettext``. + It's possible to translate strings in expressions with these functions: -- ``gettext``: translate a single string -- ``ngettext``: translate a pluralizable string -- ``_``: alias for ``gettext`` +- ``_(message)``: Alias for ``gettext``. +- ``gettext(message)``: Translate a message. +- ``ngettext(singluar, plural, n)``: Translate a singular or plural + message based on a count variable. +- ``pgettext(context, message)``: Like ``gettext()``, but picks the + translation based on the context string. +- ``npgettext(context, singular, plural, n)``: Like ``npgettext()``, + but picks the translation based on the context string. You can print a translated string like this: diff -Nru jinja2-3.0.3/docs/tricks.rst jinja2-3.1.2/docs/tricks.rst --- jinja2-3.0.3/docs/tricks.rst 2021-05-11 23:25:55.000000000 +0000 +++ jinja2-3.1.2/docs/tricks.rst 2022-03-24 14:16:48.000000000 +0000 @@ -26,7 +26,7 @@ `standalone` set to `True` a very basic HTML skeleton is added:: {% if not standalone %}{% extends 'default.html' %}{% endif -%} - + {% block title %}The Page Title{% endblock %} {% block body %} @@ -74,8 +74,8 @@ ... ... diff -Nru jinja2-3.0.3/PKG-INFO jinja2-3.1.2/PKG-INFO --- jinja2-3.0.3/PKG-INFO 2021-11-09 20:26:28.880435500 +0000 +++ jinja2-3.1.2/PKG-INFO 2022-04-28 17:20:41.814993900 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Jinja2 -Version: 3.0.3 +Version: 3.1.2 Summary: A very fast and expressive template engine. Home-page: https://palletsprojects.com/p/jinja/ Author: Armin Ronacher @@ -24,7 +24,7 @@ Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Text Processing :: Markup :: HTML -Requires-Python: >=3.6 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst Provides-Extra: i18n License-File: LICENSE.rst diff -Nru jinja2-3.0.3/requirements/dev.txt jinja2-3.1.2/requirements/dev.txt --- jinja2-3.0.3/requirements/dev.txt 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/requirements/dev.txt 2022-04-28 14:08:26.000000000 +0000 @@ -1,134 +1,58 @@ +# SHA1:54b5b77ec8c7a0064ffa93b2fd16cb0130ba177c # -# This file is autogenerated by pip-compile with python 3.10 +# This file is autogenerated by pip-compile-multi # To update, run: # -# pip-compile requirements/dev.in +# pip-compile-multi # -alabaster==0.7.12 - # via sphinx -attrs==21.2.0 - # via pytest -babel==2.9.1 - # via sphinx -backports.entry-points-selectable==1.1.0 - # via virtualenv -certifi==2021.10.8 - # via requests +-r docs.txt +-r tests.txt +-r typing.txt cfgv==3.3.1 # via pre-commit -charset-normalizer==2.0.7 - # via requests -click==8.0.3 - # via pip-tools -distlib==0.3.3 +click==8.1.2 + # via + # pip-compile-multi + # pip-tools +distlib==0.3.4 # via virtualenv -docutils==0.17.1 - # via sphinx -filelock==3.3.2 +filelock==3.6.0 # via # tox # virtualenv -identify==2.3.3 +identify==2.5.0 # via pre-commit -idna==3.3 - # via requests -imagesize==1.2.0 - # via sphinx -iniconfig==1.1.1 - # via pytest -jinja2==3.0.2 - # via sphinx -markupsafe==2.0.1 - # via jinja2 -mypy==0.910 - # via -r requirements/typing.in -mypy-extensions==0.4.3 - # via mypy nodeenv==1.6.0 # via pre-commit -packaging==21.2 - # via - # pallets-sphinx-themes - # pytest - # sphinx - # tox -pallets-sphinx-themes==2.0.1 - # via -r requirements/docs.in pep517==0.12.0 # via pip-tools -pip-tools==6.4.0 +pip-compile-multi==2.4.5 # via -r requirements/dev.in -platformdirs==2.4.0 +pip-tools==6.6.0 + # via pip-compile-multi +platformdirs==2.5.2 # via virtualenv -pluggy==1.0.0 - # via - # pytest - # tox -pre-commit==2.15.0 +pre-commit==2.18.1 # via -r requirements/dev.in -py==1.11.0 - # via - # pytest - # tox -pygments==2.10.0 - # via sphinx -pyparsing==2.4.7 - # via packaging -pytest==6.2.5 - # via -r requirements/tests.in -pytz==2021.3 - # via babel pyyaml==6.0 # via pre-commit -requests==2.26.0 - # via sphinx six==1.16.0 # via # tox # virtualenv -snowballstemmer==2.1.0 - # via sphinx -sphinx==4.2.0 - # via - # -r requirements/docs.in - # pallets-sphinx-themes - # sphinx-issues - # sphinxcontrib-log-cabinet -sphinx-issues==1.2.0 - # via -r requirements/docs.in -sphinxcontrib-applehelp==1.0.2 - # via sphinx -sphinxcontrib-devhelp==1.0.2 - # via sphinx -sphinxcontrib-htmlhelp==2.0.0 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-log-cabinet==1.0.1 - # via -r requirements/docs.in -sphinxcontrib-qthelp==1.0.3 - # via sphinx -sphinxcontrib-serializinghtml==1.1.5 - # via sphinx toml==0.10.2 # via - # mypy # pre-commit - # pytest # tox -tomli==1.2.2 - # via pep517 -tox==3.24.4 +toposort==1.7 + # via pip-compile-multi +tox==3.25.0 # via -r requirements/dev.in -typing-extensions==3.10.0.2 - # via mypy -urllib3==1.26.7 - # via requests -virtualenv==20.10.0 +virtualenv==20.14.1 # via # pre-commit # tox -wheel==0.37.0 +wheel==0.37.1 # via pip-tools # The following packages are considered to be unsafe in a requirements file: diff -Nru jinja2-3.0.3/requirements/docs.txt jinja2-3.1.2/requirements/docs.txt --- jinja2-3.0.3/requirements/docs.txt 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/requirements/docs.txt 2022-04-28 14:03:12.000000000 +0000 @@ -1,50 +1,51 @@ +# SHA1:45c590f97fe95b8bdc755eef796e91adf5fbe4ea # -# This file is autogenerated by pip-compile with python 3.10 +# This file is autogenerated by pip-compile-multi # To update, run: # -# pip-compile requirements/docs.in +# pip-compile-multi # alabaster==0.7.12 # via sphinx -babel==2.9.1 +babel==2.10.1 # via sphinx certifi==2021.10.8 # via requests -charset-normalizer==2.0.7 +charset-normalizer==2.0.12 # via requests docutils==0.17.1 # via sphinx idna==3.3 # via requests -imagesize==1.2.0 +imagesize==1.3.0 # via sphinx -jinja2==3.0.2 +jinja2==3.1.1 # via sphinx -markupsafe==2.0.1 +markupsafe==2.1.1 # via jinja2 -packaging==21.2 +packaging==21.3 # via # pallets-sphinx-themes # sphinx -pallets-sphinx-themes==2.0.1 +pallets-sphinx-themes==2.0.2 # via -r requirements/docs.in -pygments==2.10.0 +pygments==2.12.0 # via sphinx -pyparsing==2.4.7 +pyparsing==3.0.8 # via packaging -pytz==2021.3 +pytz==2022.1 # via babel -requests==2.26.0 +requests==2.27.1 # via sphinx -snowballstemmer==2.1.0 +snowballstemmer==2.2.0 # via sphinx -sphinx==4.2.0 +sphinx==4.5.0 # via # -r requirements/docs.in # pallets-sphinx-themes # sphinx-issues # sphinxcontrib-log-cabinet -sphinx-issues==1.2.0 +sphinx-issues==3.0.1 # via -r requirements/docs.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -60,8 +61,5 @@ # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -urllib3==1.26.7 +urllib3==1.26.9 # via requests - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff -Nru jinja2-3.0.3/requirements/tests.txt jinja2-3.1.2/requirements/tests.txt --- jinja2-3.0.3/requirements/tests.txt 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/requirements/tests.txt 2022-04-28 14:03:13.000000000 +0000 @@ -1,22 +1,23 @@ +# SHA1:0eaa389e1fdb3a1917c0f987514bd561be5718ee # -# This file is autogenerated by pip-compile with python 3.10 +# This file is autogenerated by pip-compile-multi # To update, run: # -# pip-compile requirements/tests.in +# pip-compile-multi # -attrs==21.2.0 +attrs==21.4.0 # via pytest iniconfig==1.1.1 # via pytest -packaging==21.2 +packaging==21.3 # via pytest pluggy==1.0.0 # via pytest py==1.11.0 # via pytest -pyparsing==2.4.7 +pyparsing==3.0.8 # via packaging -pytest==6.2.5 +pytest==7.1.2 # via -r requirements/tests.in -toml==0.10.2 +tomli==2.0.1 # via pytest diff -Nru jinja2-3.0.3/requirements/typing.txt jinja2-3.1.2/requirements/typing.txt --- jinja2-3.0.3/requirements/typing.txt 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/requirements/typing.txt 2022-04-28 14:08:26.000000000 +0000 @@ -1,14 +1,15 @@ +# SHA1:7983aaa01d64547827c20395d77e248c41b2572f # -# This file is autogenerated by pip-compile with python 3.10 +# This file is autogenerated by pip-compile-multi # To update, run: # -# pip-compile requirements/typing.in +# pip-compile-multi # -mypy==0.910 +mypy==0.950 # via -r requirements/typing.in mypy-extensions==0.4.3 # via mypy -toml==0.10.2 +tomli==2.0.1 # via mypy -typing-extensions==3.10.0.2 +typing-extensions==4.2.0 # via mypy diff -Nru jinja2-3.0.3/setup.cfg jinja2-3.1.2/setup.cfg --- jinja2-3.0.3/setup.cfg 2021-11-09 20:26:28.880435500 +0000 +++ jinja2-3.1.2/setup.cfg 2022-04-28 17:20:41.814993900 +0000 @@ -32,8 +32,8 @@ [options] packages = find: package_dir = = src -include_package_data = true -python_requires = >= 3.6 +include_package_data = True +python_requires = >= 3.7 [options.packages.find] where = src @@ -72,7 +72,8 @@ [mypy] files = src/jinja2 -python_version = 3.6 +python_version = 3.7 +show_error_codes = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_defs = True diff -Nru jinja2-3.0.3/src/jinja2/async_utils.py jinja2-3.1.2/src/jinja2/async_utils.py --- jinja2-3.0.3/src/jinja2/async_utils.py 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/async_utils.py 2022-03-24 14:16:48.000000000 +0000 @@ -1,5 +1,6 @@ import inspect import typing as t +from functools import WRAPPER_ASSIGNMENTS from functools import wraps from .utils import _PassArg @@ -23,7 +24,15 @@ def is_async(args: t.Any) -> bool: return t.cast(bool, args[0].environment.is_async) - @wraps(normal_func) + # Take the doc and annotations from the sync function, but the + # name from the async function. Pallets-Sphinx-Themes + # build_function_directive expects __wrapped__ to point to the + # sync function. + async_func_attrs = ("__module__", "__name__", "__qualname__") + normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs)) + + @wraps(normal_func, assigned=normal_func_attrs) + @wraps(async_func, assigned=async_func_attrs, updated=()) def wrapper(*args, **kwargs): # type: ignore b = is_async(args) diff -Nru jinja2-3.0.3/src/jinja2/bccache.py jinja2-3.1.2/src/jinja2/bccache.py --- jinja2-3.0.3/src/jinja2/bccache.py 2021-10-04 20:41:58.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/bccache.py 2022-04-28 14:08:26.000000000 +0000 @@ -79,7 +79,7 @@ self.reset() return - def write_bytecode(self, f: t.BinaryIO) -> None: + def write_bytecode(self, f: t.IO[bytes]) -> None: """Dump the bytecode into the file or file like object passed.""" if self.code is None: raise TypeError("can't write empty bucket") @@ -262,13 +262,55 @@ def load_bytecode(self, bucket: Bucket) -> None: filename = self._get_cache_filename(bucket) - if os.path.exists(filename): - with open(filename, "rb") as f: - bucket.load_bytecode(f) + # Don't test for existence before opening the file, since the + # file could disappear after the test before the open. + try: + f = open(filename, "rb") + except (FileNotFoundError, IsADirectoryError, PermissionError): + # PermissionError can occur on Windows when an operation is + # in progress, such as calling clear(). + return + + with f: + bucket.load_bytecode(f) def dump_bytecode(self, bucket: Bucket) -> None: - with open(self._get_cache_filename(bucket), "wb") as f: - bucket.write_bytecode(f) + # Write to a temporary file, then rename to the real name after + # writing. This avoids another process reading the file before + # it is fully written. + name = self._get_cache_filename(bucket) + f = tempfile.NamedTemporaryFile( + mode="wb", + dir=os.path.dirname(name), + prefix=os.path.basename(name), + suffix=".tmp", + delete=False, + ) + + def remove_silent() -> None: + try: + os.remove(f.name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + pass + + try: + with f: + bucket.write_bytecode(f) + except BaseException: + remove_silent() + raise + + try: + os.replace(f.name, name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + remove_silent() + except BaseException: + remove_silent() + raise def clear(self) -> None: # imported lazily here because google app-engine doesn't support diff -Nru jinja2-3.0.3/src/jinja2/compiler.py jinja2-3.1.2/src/jinja2/compiler.py --- jinja2-3.0.3/src/jinja2/compiler.py 2021-11-09 16:37:43.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/compiler.py 2022-03-24 14:16:48.000000000 +0000 @@ -218,7 +218,7 @@ def copy(self) -> "Frame": """Create a copy of the current one.""" - rv = t.cast(Frame, object.__new__(self.__class__)) + rv = object.__new__(self.__class__) rv.__dict__.update(self.__dict__) rv.symbols = self.symbols.copy() return rv @@ -724,6 +724,7 @@ """ self.writeline("resolve = context.resolve_or_missing") self.writeline("undefined = environment.undefined") + self.writeline("concat = environment.concat") # always use the standard Undefined class for the implicit else of # conditional expressions self.writeline("cond_expr_undefined = Undefined") @@ -835,7 +836,6 @@ else: exported_names = sorted(exported) - self.writeline("from __future__ import generator_stop") # Python < 3.7 self.writeline("from jinja2.runtime import " + ", ".join(exported_names)) # if we want a deferred initialization we cannot move the @@ -1755,7 +1755,7 @@ self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool ) -> t.Iterator[None]: if self.environment.is_async: - self.write("await auto_await(") + self.write("(await auto_await(") if is_filter: self.write(f"{self.filters[node.name]}(") @@ -1790,7 +1790,7 @@ self.write(")") if self.environment.is_async: - self.write(")") + self.write("))") @optimizeconst def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: @@ -1842,7 +1842,7 @@ self, node: nodes.Call, frame: Frame, forward_caller: bool = False ) -> None: if self.environment.is_async: - self.write("await auto_await(") + self.write("(await auto_await(") if self.environment.sandboxed: self.write("environment.call(context, ") else: @@ -1858,7 +1858,7 @@ self.signature(node, frame, extra_kwargs) self.write(")") if self.environment.is_async: - self.write(")") + self.write("))") def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None: self.write(node.key + "=") diff -Nru jinja2-3.0.3/src/jinja2/debug.py jinja2-3.1.2/src/jinja2/debug.py --- jinja2-3.0.3/src/jinja2/debug.py 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/debug.py 2022-03-24 14:16:48.000000000 +0000 @@ -1,4 +1,3 @@ -import platform import sys import typing as t from types import CodeType @@ -68,7 +67,8 @@ # Assign tb_next in reverse to avoid circular references. for tb in reversed(stack): - tb_next = tb_set_next(tb, tb_next) + tb.tb_next = tb_next + tb_next = tb return exc_value.with_traceback(tb_next) @@ -189,71 +189,3 @@ data[name] = value return data - - -if sys.version_info >= (3, 7): - # tb_next is directly assignable as of Python 3.7 - def tb_set_next( - tb: TracebackType, tb_next: t.Optional[TracebackType] - ) -> TracebackType: - tb.tb_next = tb_next - return tb - - -elif platform.python_implementation() == "PyPy": - # PyPy might have special support, and won't work with ctypes. - try: - import tputil # type: ignore - except ImportError: - # Without tproxy support, use the original traceback. - def tb_set_next( - tb: TracebackType, tb_next: t.Optional[TracebackType] - ) -> TracebackType: - return tb - - else: - # With tproxy support, create a proxy around the traceback that - # returns the new tb_next. - def tb_set_next( - tb: TracebackType, tb_next: t.Optional[TracebackType] - ) -> TracebackType: - def controller(op): # type: ignore - if op.opname == "__getattribute__" and op.args[0] == "tb_next": - return tb_next - - return op.delegate() - - return tputil.make_proxy(controller, obj=tb) # type: ignore - - -else: - # Use ctypes to assign tb_next at the C level since it's read-only - # from Python. - import ctypes - - class _CTraceback(ctypes.Structure): - _fields_ = [ - # Extra PyObject slots when compiled with Py_TRACE_REFS. - ("PyObject_HEAD", ctypes.c_byte * object().__sizeof__()), - # Only care about tb_next as an object, not a traceback. - ("tb_next", ctypes.py_object), - ] - - def tb_set_next( - tb: TracebackType, tb_next: t.Optional[TracebackType] - ) -> TracebackType: - c_tb = _CTraceback.from_address(id(tb)) - - # Clear out the old tb_next. - if tb.tb_next is not None: - c_tb_next = ctypes.py_object(tb.tb_next) - c_tb.tb_next = ctypes.py_object() - ctypes.pythonapi.Py_DecRef(c_tb_next) - - # Assign the new tb_next. - if tb_next is not None: - c_tb_next = ctypes.py_object(tb_next) - ctypes.pythonapi.Py_IncRef(c_tb_next) - c_tb.tb_next = c_tb_next - - return tb diff -Nru jinja2-3.0.3/src/jinja2/environment.py jinja2-3.1.2/src/jinja2/environment.py --- jinja2-3.0.3/src/jinja2/environment.py 2021-11-09 18:10:36.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/environment.py 2022-04-25 19:41:10.000000000 +0000 @@ -2,7 +2,6 @@ options. """ import os -import sys import typing import typing as t import weakref @@ -282,6 +281,8 @@ #: :class:`~jinja2.compiler.CodeGenerator` for more information. code_generator_class: t.Type["CodeGenerator"] = CodeGenerator + concat = "".join + #: the context class that is used for templates. See #: :class:`~jinja2.runtime.Context` for more information. context_class: t.Type[Context] = Context @@ -392,6 +393,8 @@ line_comment_prefix: t.Optional[str] = missing, trim_blocks: bool = missing, lstrip_blocks: bool = missing, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing, + keep_trailing_newline: bool = missing, extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing, optimized: bool = missing, undefined: t.Type[Undefined] = missing, @@ -401,6 +404,7 @@ cache_size: int = missing, auto_reload: bool = missing, bytecode_cache: t.Optional["BytecodeCache"] = missing, + enable_async: bool = False, ) -> "Environment": """Create a new overlay environment that shares all the data with the current environment except for cache and the overridden attributes. @@ -412,9 +416,13 @@ up completely. Not all attributes are truly linked, some are just copied over so modifications on the original environment may not shine through. + + .. versionchanged:: 3.1.2 + Added the ``newline_sequence``,, ``keep_trailing_newline``, + and ``enable_async`` parameters to match ``__init__``. """ args = dict(locals()) - del args["self"], args["cache_size"], args["extensions"] + del args["self"], args["cache_size"], args["extensions"], args["enable_async"] rv = object.__new__(self.__class__) rv.__dict__.update(self.__dict__) @@ -436,6 +444,9 @@ if extensions is not missing: rv.extensions.update(load_extensions(rv, extensions)) + if enable_async is not missing: + rv.is_async = enable_async + return _environment_config_check(rv) @property @@ -938,7 +949,7 @@ @internalcode def _load_template( - self, name: str, globals: t.Optional[t.Mapping[str, t.Any]] + self, name: str, globals: t.Optional[t.MutableMapping[str, t.Any]] ) -> "Template": if self.loader is None: raise TypeError("no loader for this environment specified") @@ -966,13 +977,15 @@ self, name: t.Union[str, "Template"], parent: t.Optional[str] = None, - globals: t.Optional[t.Mapping[str, t.Any]] = None, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, ) -> "Template": """Load a template by name with :attr:`loader` and return a :class:`Template`. If the template does not exist a :exc:`TemplateNotFound` exception is raised. - :param name: Name of the template to load. + :param name: Name of the template to load. When loading + templates from the filesystem, "/" is used as the path + separator, even on Windows. :param parent: The name of the parent template importing this template. :meth:`join_path` can be used to implement name transformations with this. @@ -1001,7 +1014,7 @@ self, names: t.Iterable[t.Union[str, "Template"]], parent: t.Optional[str] = None, - globals: t.Optional[t.Mapping[str, t.Any]] = None, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, ) -> "Template": """Like :meth:`get_template`, but tries loading multiple names. If none of the names can be loaded a :exc:`TemplatesNotFound` @@ -1057,7 +1070,7 @@ str, "Template", t.List[t.Union[str, "Template"]] ], parent: t.Optional[str] = None, - globals: t.Optional[t.Mapping[str, t.Any]] = None, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, ) -> "Template": """Use :meth:`select_template` if an iterable of template names is given, or :meth:`get_template` if one name is given. @@ -1073,7 +1086,7 @@ def from_string( self, source: t.Union[str, nodes.Template], - globals: t.Optional[t.Mapping[str, t.Any]] = None, + globals: t.Optional[t.MutableMapping[str, t.Any]] = None, template_class: t.Optional[t.Type["Template"]] = None, ) -> "Template": """Load a template from a source string without using @@ -1092,7 +1105,7 @@ return cls.from_code(self, self.compile(source), gs, None) def make_globals( - self, d: t.Optional[t.Mapping[str, t.Any]] + self, d: t.Optional[t.MutableMapping[str, t.Any]] ) -> t.MutableMapping[str, t.Any]: """Make the globals map for a template. Any given template globals overlay the environment :attr:`globals`. @@ -1268,14 +1281,11 @@ close = False - if sys.version_info < (3, 7): - loop = asyncio.get_event_loop() - else: - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - close = True + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + close = True try: return loop.run_until_complete(self.render_async(*args, **kwargs)) @@ -1286,7 +1296,7 @@ ctx = self.new_context(dict(*args, **kwargs)) try: - return concat(self.root_render_func(ctx)) # type: ignore + return self.environment.concat(self.root_render_func(ctx)) # type: ignore except Exception: self.environment.handle_exception() @@ -1307,7 +1317,9 @@ ctx = self.new_context(dict(*args, **kwargs)) try: - return concat([n async for n in self.root_render_func(ctx)]) # type: ignore + return self.environment.concat( # type: ignore + [n async for n in self.root_render_func(ctx)] # type: ignore + ) except Exception: return self.environment.handle_exception() @@ -1331,13 +1343,7 @@ async def to_list() -> t.List[str]: return [x async for x in self.generate_async(*args, **kwargs)] - if sys.version_info < (3, 7): - loop = asyncio.get_event_loop() - out = loop.run_until_complete(to_list()) - else: - out = asyncio.run(to_list()) - - yield from out + yield from asyncio.run(to_list()) return ctx = self.new_context(dict(*args, **kwargs)) diff -Nru jinja2-3.0.3/src/jinja2/ext.py jinja2-3.1.2/src/jinja2/ext.py --- jinja2-3.0.3/src/jinja2/ext.py 2021-05-14 01:01:18.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/ext.py 2022-03-24 14:16:48.000000000 +0000 @@ -2,7 +2,6 @@ import pprint import re import typing as t -import warnings from markupsafe import Markup @@ -91,7 +90,7 @@ def bind(self, environment: Environment) -> "Extension": """Create a copy of this extension bound to another environment.""" - rv = t.cast(Extension, object.__new__(self.__class__)) + rv = object.__new__(self.__class__) rv.__dict__.update(self.__dict__) rv.environment = environment return rv @@ -355,13 +354,19 @@ def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]: """Parse a translatable tag.""" lineno = next(parser.stream).lineno - num_called_num = False + + context = None + context_token = parser.stream.next_if("string") + + if context_token is not None: + context = context_token.value # find all the variables referenced. Additionally a variable can be # defined in the body of the trans block too, but this is checked at # a later state. plural_expr: t.Optional[nodes.Expr] = None plural_expr_assignment: t.Optional[nodes.Assign] = None + num_called_num = False variables: t.Dict[str, nodes.Expr] = {} trimmed = None while parser.stream.current.type != "block_end": @@ -456,6 +461,7 @@ node = self._make_node( singular, plural, + context, variables, plural_expr, bool(referenced), @@ -511,6 +517,7 @@ self, singular: str, plural: t.Optional[str], + context: t.Optional[str], variables: t.Dict[str, nodes.Expr], plural_expr: t.Optional[nodes.Expr], vars_referenced: bool, @@ -527,21 +534,18 @@ if plural: plural = plural.replace("%%", "%") - # singular only: - if plural_expr is None: - gettext = nodes.Name("gettext", "load") - node = nodes.Call(gettext, [nodes.Const(singular)], [], None, None) + func_name = "gettext" + func_args: t.List[nodes.Expr] = [nodes.Const(singular)] - # singular and plural - else: - ngettext = nodes.Name("ngettext", "load") - node = nodes.Call( - ngettext, - [nodes.Const(singular), nodes.Const(plural), plural_expr], - [], - None, - None, - ) + if context is not None: + func_args.insert(0, nodes.Const(context)) + func_name = f"p{func_name}" + + if plural_expr is not None: + func_name = f"n{func_name}" + func_args.extend((nodes.Const(plural), plural_expr)) + + node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None) # in case newstyle gettext is used, the method is powerful # enough to handle the variable expansion and autoescape @@ -597,28 +601,6 @@ return nodes.Continue(lineno=token.lineno) -class WithExtension(Extension): - def __init__(self, environment: Environment) -> None: - super().__init__(environment) - warnings.warn( - "The 'with' extension is deprecated and will be removed in" - " Jinja 3.1. This is built in now.", - DeprecationWarning, - stacklevel=3, - ) - - -class AutoEscapeExtension(Extension): - def __init__(self, environment: Environment) -> None: - super().__init__(environment) - warnings.warn( - "The 'autoescape' extension is deprecated and will be" - " removed in Jinja 3.1. This is built in now.", - DeprecationWarning, - stacklevel=3, - ) - - class DebugExtension(Extension): """A ``{% debug %}`` tag that dumps the available variables, filters, and tests. @@ -874,6 +856,4 @@ i18n = InternationalizationExtension do = ExprStmtExtension loopcontrols = LoopControlExtension -with_ = WithExtension -autoescape = AutoEscapeExtension debug = DebugExtension diff -Nru jinja2-3.0.3/src/jinja2/filters.py jinja2-3.1.2/src/jinja2/filters.py --- jinja2-3.0.3/src/jinja2/filters.py 2021-10-04 20:41:58.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/filters.py 2022-04-28 14:08:26.000000000 +0000 @@ -4,7 +4,6 @@ import re import typing import typing as t -import warnings from collections import abc from itertools import chain from itertools import groupby @@ -44,58 +43,6 @@ V = t.TypeVar("V") -def contextfilter(f: F) -> F: - """Pass the context as the first argument to the decorated function. - - .. deprecated:: 3.0 - Will be removed in Jinja 3.1. Use :func:`~jinja2.pass_context` - instead. - """ - warnings.warn( - "'contextfilter' is renamed to 'pass_context', the old name" - " will be removed in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return pass_context(f) - - -def evalcontextfilter(f: F) -> F: - """Pass the eval context as the first argument to the decorated - function. - - .. deprecated:: 3.0 - Will be removed in Jinja 3.1. Use - :func:`~jinja2.pass_eval_context` instead. - - .. versionadded:: 2.4 - """ - warnings.warn( - "'evalcontextfilter' is renamed to 'pass_eval_context', the old" - " name will be removed in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return pass_eval_context(f) - - -def environmentfilter(f: F) -> F: - """Pass the environment as the first argument to the decorated - function. - - .. deprecated:: 3.0 - Will be removed in Jinja 3.1. Use - :func:`~jinja2.pass_environment` instead. - """ - warnings.warn( - "'environmentfilter' is renamed to 'pass_environment', the old" - " name will be removed in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return pass_environment(f) - - def ignore_case(value: V) -> V: """For use as a postprocessor for :func:`make_attrgetter`. Converts strings to lowercase and returns other types as-is.""" @@ -271,6 +218,36 @@ return soft_str(s).lower() +def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K, V]]: + """Return an iterator over the ``(key, value)`` items of a mapping. + + ``x|items`` is the same as ``x.items()``, except if ``x`` is + undefined an empty iterator is returned. + + This filter is useful if you expect the template to be rendered with + an implementation of Jinja in another programming language that does + not have a ``.items()`` method on its mapping type. + + .. code-block:: html+jinja + +
    + {% for key, value in my_dict|items %} +
    {{ key }} +
    {{ value }} + {% endfor %} +
    + + .. versionadded:: 3.1 + """ + if isinstance(value, Undefined): + return + + if not isinstance(value, abc.Mapping): + raise TypeError("Can only get item pairs from a mapping.") + + yield from value.items() + + @pass_eval_context def do_xmlattr( eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True @@ -415,7 +392,7 @@ .. sourcecode:: jinja - {% for user users|sort(attribute="age,name") %} + {% for user in users|sort(attribute="age,name") %} ... {% endfor %} @@ -1164,7 +1141,7 @@ return round(value, precision) func = getattr(math, method) - return t.cast(float, func(value * (10 ** precision)) / (10 ** precision)) + return t.cast(float, func(value * (10**precision)) / (10**precision)) class _GroupTuple(t.NamedTuple): @@ -1186,7 +1163,8 @@ value: "t.Iterable[V]", attribute: t.Union[str, int], default: t.Optional[t.Any] = None, -) -> "t.List[t.Tuple[t.Any, t.List[V]]]": + case_sensitive: bool = False, +) -> "t.List[_GroupTuple]": """Group a sequence of objects by an attribute using Python's :func:`itertools.groupby`. The attribute can use dot notation for nested access, like ``"address.city"``. Unlike Python's ``groupby``, @@ -1226,18 +1204,42 @@
  • {{ city }}: {{ items|map(attribute="name")|join(", ") }}
  • {% endfor %} + Like the :func:`~jinja-filters.sort` filter, sorting and grouping is + case-insensitive by default. The ``key`` for each group will have + the case of the first item in that group of values. For example, if + a list of users has cities ``["CA", "NY", "ca"]``, the "CA" group + will have two values. This can be disabled by passing + ``case_sensitive=True``. + + .. versionchanged:: 3.1 + Added the ``case_sensitive`` parameter. Sorting and grouping is + case-insensitive by default, matching other filters that do + comparisons. + .. versionchanged:: 3.0 Added the ``default`` parameter. .. versionchanged:: 2.6 The attribute supports dot notation for nested access. """ - expr = make_attrgetter(environment, attribute, default=default) - return [ + expr = make_attrgetter( + environment, + attribute, + postprocess=ignore_case if not case_sensitive else None, + default=default, + ) + out = [ _GroupTuple(key, list(values)) for key, values in groupby(sorted(value, key=expr), expr) ] + if not case_sensitive: + # Return the real key from the first value instead of the lowercase key. + output_expr = make_attrgetter(environment, attribute, default=default) + out = [_GroupTuple(output_expr(values[0]), values) for _, values in out] + + return out + @async_variant(sync_do_groupby) # type: ignore async def do_groupby( @@ -1245,13 +1247,26 @@ value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", attribute: t.Union[str, int], default: t.Optional[t.Any] = None, -) -> "t.List[t.Tuple[t.Any, t.List[V]]]": - expr = make_attrgetter(environment, attribute, default=default) - return [ + case_sensitive: bool = False, +) -> "t.List[_GroupTuple]": + expr = make_attrgetter( + environment, + attribute, + postprocess=ignore_case if not case_sensitive else None, + default=default, + ) + out = [ _GroupTuple(key, await auto_to_list(values)) for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr) ] + if not case_sensitive: + # Return the real key from the first value instead of the lowercase key. + output_expr = make_attrgetter(environment, attribute, default=default) + out = [_GroupTuple(output_expr(values[0]), values) for _, values in out] + + return out + @pass_environment def sync_do_sum( @@ -1271,13 +1286,13 @@ Total: {{ items|sum(attribute='price') }} .. versionchanged:: 2.6 - The `attribute` parameter was added to allow suming up over - attributes. Also the `start` parameter was moved on to the right. + The ``attribute`` parameter was added to allow summing up over + attributes. Also the ``start`` parameter was moved on to the right. """ if attribute is not None: iterable = map(make_attrgetter(environment, attribute), iterable) - return sum(iterable, start) + return sum(iterable, start) # type: ignore[no-any-return, call-overload] @async_variant(sync_do_sum) # type: ignore @@ -1792,6 +1807,7 @@ "length": len, "list": do_list, "lower": do_lower, + "items": do_items, "map": do_map, "min": do_min, "max": do_max, diff -Nru jinja2-3.0.3/src/jinja2/_identifier.py jinja2-3.1.2/src/jinja2/_identifier.py --- jinja2-3.0.3/src/jinja2/_identifier.py 2020-02-17 17:14:56.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/_identifier.py 2022-03-24 14:16:48.000000000 +0000 @@ -2,5 +2,5 @@ # generated by scripts/generate_identifier_pattern.py pattern = re.compile( - r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 + r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 ) diff -Nru jinja2-3.0.3/src/jinja2/idtracking.py jinja2-3.1.2/src/jinja2/idtracking.py --- jinja2-3.0.3/src/jinja2/idtracking.py 2021-08-10 13:34:22.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/idtracking.py 2022-03-11 22:53:03.000000000 +0000 @@ -84,7 +84,7 @@ return rv def copy(self) -> "Symbols": - rv = t.cast(Symbols, object.__new__(self.__class__)) + rv = object.__new__(self.__class__) rv.__dict__.update(self.__dict__) rv.refs = self.refs.copy() rv.loads = self.loads.copy() diff -Nru jinja2-3.0.3/src/jinja2/__init__.py jinja2-3.1.2/src/jinja2/__init__.py --- jinja2-3.0.3/src/jinja2/__init__.py 2021-11-09 20:25:25.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/__init__.py 2022-04-28 17:20:15.000000000 +0000 @@ -14,9 +14,6 @@ from .exceptions import TemplatesNotFound as TemplatesNotFound from .exceptions import TemplateSyntaxError as TemplateSyntaxError from .exceptions import UndefinedError as UndefinedError -from .filters import contextfilter -from .filters import environmentfilter -from .filters import evalcontextfilter from .loaders import BaseLoader as BaseLoader from .loaders import ChoiceLoader as ChoiceLoader from .loaders import DictLoader as DictLoader @@ -31,15 +28,10 @@ from .runtime import StrictUndefined as StrictUndefined from .runtime import Undefined as Undefined from .utils import clear_caches as clear_caches -from .utils import contextfunction -from .utils import environmentfunction -from .utils import escape -from .utils import evalcontextfunction from .utils import is_undefined as is_undefined -from .utils import Markup from .utils import pass_context as pass_context from .utils import pass_environment as pass_environment from .utils import pass_eval_context as pass_eval_context from .utils import select_autoescape as select_autoescape -__version__ = "3.0.3" +__version__ = "3.1.2" diff -Nru jinja2-3.0.3/src/jinja2/lexer.py jinja2-3.1.2/src/jinja2/lexer.py --- jinja2-3.0.3/src/jinja2/lexer.py 2021-10-04 20:41:58.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/lexer.py 2022-03-24 14:16:48.000000000 +0000 @@ -507,19 +507,17 @@ # block suffix if trimming is enabled block_suffix_re = "\\n?" if environment.trim_blocks else "" - # If lstrip is enabled, it should not be applied if there is any - # non-whitespace between the newline and block. - self.lstrip_unless_re = c(r"[^ \t]") if environment.lstrip_blocks else None + self.lstrip_blocks = environment.lstrip_blocks self.newline_sequence = environment.newline_sequence self.keep_trailing_newline = environment.keep_trailing_newline root_raw_re = ( - fr"(?P{block_start_re}(\-|\+|)\s*raw\s*" - fr"(?:\-{block_end_re}\s*|{block_end_re}))" + rf"(?P{block_start_re}(\-|\+|)\s*raw\s*" + rf"(?:\-{block_end_re}\s*|{block_end_re}))" ) root_parts_re = "|".join( - [root_raw_re] + [fr"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules] + [root_raw_re] + [rf"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules] ) # global lexing rules @@ -527,7 +525,7 @@ "root": [ # directives _Rule( - c(fr"(.*?)(?:{root_parts_re})"), + c(rf"(.*?)(?:{root_parts_re})"), OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore "#bygroup", ), @@ -538,8 +536,8 @@ TOKEN_COMMENT_BEGIN: [ _Rule( c( - fr"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*" - fr"|{comment_end_re}{block_suffix_re}))" + rf"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*" + rf"|{comment_end_re}{block_suffix_re}))" ), (TOKEN_COMMENT, TOKEN_COMMENT_END), "#pop", @@ -550,8 +548,8 @@ TOKEN_BLOCK_BEGIN: [ _Rule( c( - fr"(?:\+{block_end_re}|\-{block_end_re}\s*" - fr"|{block_end_re}{block_suffix_re})" + rf"(?:\+{block_end_re}|\-{block_end_re}\s*" + rf"|{block_end_re}{block_suffix_re})" ), TOKEN_BLOCK_END, "#pop", @@ -561,7 +559,7 @@ # variables TOKEN_VARIABLE_BEGIN: [ _Rule( - c(fr"\-{variable_end_re}\s*|{variable_end_re}"), + c(rf"\-{variable_end_re}\s*|{variable_end_re}"), TOKEN_VARIABLE_END, "#pop", ) @@ -571,9 +569,9 @@ TOKEN_RAW_BEGIN: [ _Rule( c( - fr"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*" - fr"(?:\+{block_end_re}|\-{block_end_re}\s*" - fr"|{block_end_re}{block_suffix_re}))" + rf"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*" + rf"(?:\+{block_end_re}|\-{block_end_re}\s*" + rf"|{block_end_re}{block_suffix_re}))" ), OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore "#pop", @@ -697,7 +695,6 @@ statetokens = self.rules[stack[-1]] source_length = len(source) balancing_stack: t.List[str] = [] - lstrip_unless_re = self.lstrip_unless_re newlines_stripped = 0 line_starting = True @@ -723,7 +720,7 @@ # tuples support more options if isinstance(tokens, tuple): - groups = m.groups() + groups: t.Sequence[str] = m.groups() if isinstance(tokens, OptionalLStrip): # Rule supports lstrip. Match will look like @@ -743,7 +740,7 @@ # Not marked for preserving whitespace. strip_sign != "+" # lstrip is enabled. - and lstrip_unless_re is not None + and self.lstrip_blocks # Not a variable expression. and not m.groupdict().get(TOKEN_VARIABLE_BEGIN) ): @@ -753,7 +750,7 @@ if l_pos > 0 or line_starting: # If there's only whitespace between the newline and the # tag, strip it. - if not lstrip_unless_re.search(text, l_pos): + if whitespace_re.fullmatch(text, l_pos): groups = [text[:l_pos], *groups[1:]] for idx, token in enumerate(tokens): diff -Nru jinja2-3.0.3/src/jinja2/loaders.py jinja2-3.1.2/src/jinja2/loaders.py --- jinja2-3.0.3/src/jinja2/loaders.py 2021-11-09 20:21:27.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/loaders.py 2022-04-28 16:37:28.000000000 +0000 @@ -3,6 +3,7 @@ """ import importlib.util import os +import posixpath import sys import typing as t import weakref @@ -193,7 +194,9 @@ ) -> t.Tuple[str, str, t.Callable[[], bool]]: pieces = split_template_path(template) for searchpath in self.searchpath: - filename = os.path.join(searchpath, *pieces) + # Use posixpath even on Windows to avoid "drive:" or UNC + # segments breaking out of the search directory. + filename = posixpath.join(searchpath, *pieces) f = open_if_exists(filename) if f is None: continue @@ -210,7 +213,8 @@ except OSError: return False - return contents, filename, uptodate + # Use normpath to convert Windows altsep to sep. + return contents, os.path.normpath(filename), uptodate raise TemplateNotFound(template) def list_templates(self) -> t.List[str]: @@ -296,7 +300,7 @@ if isinstance(loader, zipimport.zipimporter): self._archive = loader.archive pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore - template_root = os.path.join(pkgdir, package_path) + template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep) else: roots: t.List[str] = [] @@ -326,7 +330,12 @@ def get_source( self, environment: "Environment", template: str ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]: - p = os.path.join(self._template_root, *split_template_path(template)) + # Use posixpath even on Windows to avoid "drive:" or UNC + # segments breaking out of the search directory. Use normpath to + # convert Windows altsep to sep. + p = os.path.normpath( + posixpath.join(self._template_root, *split_template_path(template)) + ) up_to_date: t.Optional[t.Callable[[], bool]] if self._archive is None: @@ -603,7 +612,7 @@ if not isinstance(path, abc.Iterable) or isinstance(path, str): path = [path] - mod.__path__ = [os.fspath(p) for p in path] # type: ignore + mod.__path__ = [os.fspath(p) for p in path] sys.modules[package_name] = weakref.proxy( mod, lambda x: sys.modules.pop(package_name, None) diff -Nru jinja2-3.0.3/src/jinja2/nativetypes.py jinja2-3.1.2/src/jinja2/nativetypes.py --- jinja2-3.0.3/src/jinja2/nativetypes.py 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/nativetypes.py 2022-03-24 14:16:48.000000000 +0000 @@ -3,6 +3,7 @@ from ast import parse from itertools import chain from itertools import islice +from types import GeneratorType from . import nodes from .compiler import CodeGenerator @@ -31,7 +32,9 @@ if not isinstance(raw, str): return raw else: - raw = "".join([str(v) for v in chain(head, values)]) + if isinstance(values, GeneratorType): + values = chain(head, values) + raw = "".join([str(v) for v in values]) try: return literal_eval( @@ -86,6 +89,7 @@ """An environment that renders templates to native Python types.""" code_generator_class = NativeCodeGenerator + concat = staticmethod(native_concat) # type: ignore class NativeTemplate(Template): @@ -101,7 +105,9 @@ ctx = self.new_context(dict(*args, **kwargs)) try: - return native_concat(self.root_render_func(ctx)) # type: ignore + return self.environment_class.concat( # type: ignore + self.root_render_func(ctx) # type: ignore + ) except Exception: return self.environment.handle_exception() @@ -114,7 +120,7 @@ ctx = self.new_context(dict(*args, **kwargs)) try: - return native_concat( + return self.environment_class.concat( # type: ignore [n async for n in self.root_render_func(ctx)] # type: ignore ) except Exception: diff -Nru jinja2-3.0.3/src/jinja2/parser.py jinja2-3.1.2/src/jinja2/parser.py --- jinja2-3.0.3/src/jinja2/parser.py 2021-05-14 01:01:13.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/parser.py 2022-03-11 22:53:03.000000000 +0000 @@ -160,7 +160,7 @@ self._last_identifier += 1 rv = object.__new__(nodes.InternalName) nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno) - return rv # type: ignore + return rv def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]: """Parse a single statement.""" @@ -364,14 +364,10 @@ node.names = [] def parse_context() -> bool: - if ( - self.stream.current.value - in { - "with", - "without", - } - and self.stream.look().test("name:context") - ): + if self.stream.current.value in { + "with", + "without", + } and self.stream.look().test("name:context"): node.with_context = next(self.stream).value == "with" self.stream.skip() return True @@ -957,19 +953,15 @@ kwargs = [] if self.stream.current.type == "lparen": args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() - elif ( - self.stream.current.type - in { - "name", - "string", - "integer", - "float", - "lparen", - "lbracket", - "lbrace", - } - and not self.stream.current.test_any("name:else", "name:or", "name:and") - ): + elif self.stream.current.type in { + "name", + "string", + "integer", + "float", + "lparen", + "lbracket", + "lbrace", + } and not self.stream.current.test_any("name:else", "name:or", "name:and"): if self.stream.current.test("name:is"): self.fail("You cannot chain multiple tests with is") arg_node = self.parse_primary() diff -Nru jinja2-3.0.3/src/jinja2/runtime.py jinja2-3.1.2/src/jinja2/runtime.py --- jinja2-3.0.3/src/jinja2/runtime.py 2021-08-10 13:34:22.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/runtime.py 2022-03-24 14:16:48.000000000 +0000 @@ -49,7 +49,6 @@ "Markup", "TemplateRuntimeError", "missing", - "concat", "escape", "markup_join", "str_join", @@ -89,18 +88,6 @@ return concat(map(str, seq)) -def unicode_join(seq: t.Iterable[t.Any]) -> str: - import warnings - - warnings.warn( - "This template must be recompiled with at least Jinja 3.0, or" - " it will fail in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return str_join(seq) - - def new_context( environment: "Environment", template_name: t.Optional[str], @@ -173,27 +160,6 @@ :class:`Undefined` object for missing variables. """ - _legacy_resolve_mode: t.ClassVar[bool] = False - - def __init_subclass__(cls) -> None: - if "resolve_or_missing" in cls.__dict__: - # If the subclass overrides resolve_or_missing it opts in to - # modern mode no matter what. - cls._legacy_resolve_mode = False - elif "resolve" in cls.__dict__ or cls._legacy_resolve_mode: - # If the subclass overrides resolve, or if its base is - # already in legacy mode, warn about legacy behavior. - import warnings - - warnings.warn( - "Overriding 'resolve' is deprecated and will not have" - " the expected behavior in Jinja 3.1. Override" - " 'resolve_or_missing' instead ", - DeprecationWarning, - stacklevel=2, - ) - cls._legacy_resolve_mode = True - def __init__( self, environment: "Environment", @@ -251,15 +217,6 @@ :param key: The variable name to look up. """ - if self._legacy_resolve_mode: - if key in self.vars: - return self.vars[key] - - if key in self.parent: - return self.parent[key] - - return self.environment.undefined(name=key) - rv = self.resolve_or_missing(key) if rv is missing: @@ -277,14 +234,6 @@ :param key: The variable name to look up. """ - if self._legacy_resolve_mode: - rv = self.resolve(key) - - if isinstance(rv, Undefined): - return missing - - return rv - if key in self.vars: return self.vars[key] diff -Nru jinja2-3.0.3/src/jinja2/sandbox.py jinja2-3.1.2/src/jinja2/sandbox.py --- jinja2-3.0.3/src/jinja2/sandbox.py 2021-05-10 13:52:40.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/sandbox.py 2022-03-11 22:53:03.000000000 +0000 @@ -409,7 +409,7 @@ class SandboxedFormatter(Formatter): def __init__(self, env: Environment, **kwargs: t.Any) -> None: self._env = env - super().__init__(**kwargs) # type: ignore + super().__init__(**kwargs) def get_field( self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any] diff -Nru jinja2-3.0.3/src/jinja2/utils.py jinja2-3.1.2/src/jinja2/utils.py --- jinja2-3.0.3/src/jinja2/utils.py 2021-10-04 20:41:58.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/utils.py 2022-03-24 14:16:48.000000000 +0000 @@ -3,7 +3,6 @@ import os import re import typing as t -import warnings from collections import abc from collections import deque from random import choice @@ -84,74 +83,9 @@ if hasattr(obj, "jinja_pass_arg"): return obj.jinja_pass_arg # type: ignore - for prefix in "context", "eval_context", "environment": - squashed = prefix.replace("_", "") - - for name in f"{squashed}function", f"{squashed}filter": - if getattr(obj, name, False) is True: - warnings.warn( - f"{name!r} is deprecated and will stop working" - f" in Jinja 3.1. Use 'pass_{prefix}' instead.", - DeprecationWarning, - stacklevel=2, - ) - return cls[prefix] - return None -def contextfunction(f: F) -> F: - """Pass the context as the first argument to the decorated function. - - .. deprecated:: 3.0 - Will be removed in Jinja 3.1. Use :func:`~jinja2.pass_context` - instead. - """ - warnings.warn( - "'contextfunction' is renamed to 'pass_context', the old name" - " will be removed in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return pass_context(f) - - -def evalcontextfunction(f: F) -> F: - """Pass the eval context as the first argument to the decorated - function. - - .. deprecated:: 3.0 - Will be removed in Jinja 3.1. Use - :func:`~jinja2.pass_eval_context` instead. - - .. versionadded:: 2.4 - """ - warnings.warn( - "'evalcontextfunction' is renamed to 'pass_eval_context', the" - " old name will be removed in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return pass_eval_context(f) - - -def environmentfunction(f: F) -> F: - """Pass the environment as the first argument to the decorated - function. - - .. deprecated:: 3.0 - Will be removed in Jinja 3.1. Use - :func:`~jinja2.pass_environment` instead. - """ - warnings.warn( - "'environmentfunction' is renamed to 'pass_environment', the" - " old name will be removed in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return pass_environment(f) - - def internalcode(f: F) -> F: """Marks the function as internally used""" internal_code.add(f.__code__) @@ -483,18 +417,6 @@ return rv -def unicode_urlencode(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str: - import warnings - - warnings.warn( - "'unicode_urlencode' has been renamed to 'url_quote'. The old" - " name will be removed in Jinja 3.1.", - DeprecationWarning, - stacklevel=2, - ) - return url_quote(obj, charset=charset, for_qs=for_qs) - - @abc.MutableMapping.register class LRUCache: """A simple LRU Cache implementation.""" @@ -831,24 +753,3 @@ def __repr__(self) -> str: return f"" - - -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 -Nru jinja2-3.0.3/src/jinja2/visitor.py jinja2-3.1.2/src/jinja2/visitor.py --- jinja2-3.0.3/src/jinja2/visitor.py 2021-05-10 13:52:40.000000000 +0000 +++ jinja2-3.1.2/src/jinja2/visitor.py 2022-04-25 19:41:10.000000000 +0000 @@ -30,7 +30,7 @@ exists for this node. In that case the generic visit function is used instead. """ - return getattr(self, f"visit_{type(node).__name__}", None) # type: ignore + return getattr(self, f"visit_{type(node).__name__}", None) def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: """Visit a node.""" @@ -43,8 +43,8 @@ def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: """Called if no explicit visitor function exists for a node.""" - for node in node.iter_child_nodes(): - self.visit(node, *args, **kwargs) + for child_node in node.iter_child_nodes(): + self.visit(child_node, *args, **kwargs) class NodeTransformer(NodeVisitor): diff -Nru jinja2-3.0.3/src/Jinja2.egg-info/entry_points.txt jinja2-3.1.2/src/Jinja2.egg-info/entry_points.txt --- jinja2-3.0.3/src/Jinja2.egg-info/entry_points.txt 2021-11-09 20:26:28.000000000 +0000 +++ jinja2-3.1.2/src/Jinja2.egg-info/entry_points.txt 2022-04-28 17:20:41.000000000 +0000 @@ -1,3 +1,2 @@ [babel.extractors] -jinja2 = jinja2.ext:babel_extract [i18n] - +jinja2 = jinja2.ext:babel_extract[i18n] diff -Nru jinja2-3.0.3/src/Jinja2.egg-info/PKG-INFO jinja2-3.1.2/src/Jinja2.egg-info/PKG-INFO --- jinja2-3.0.3/src/Jinja2.egg-info/PKG-INFO 2021-11-09 20:26:28.000000000 +0000 +++ jinja2-3.1.2/src/Jinja2.egg-info/PKG-INFO 2022-04-28 17:20:41.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Jinja2 -Version: 3.0.3 +Version: 3.1.2 Summary: A very fast and expressive template engine. Home-page: https://palletsprojects.com/p/jinja/ Author: Armin Ronacher @@ -24,7 +24,7 @@ Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Text Processing :: Markup :: HTML -Requires-Python: >=3.6 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst Provides-Extra: i18n License-File: LICENSE.rst diff -Nru jinja2-3.0.3/src/Jinja2.egg-info/SOURCES.txt jinja2-3.1.2/src/Jinja2.egg-info/SOURCES.txt --- jinja2-3.0.3/src/Jinja2.egg-info/SOURCES.txt 2021-11-09 20:26:28.000000000 +0000 +++ jinja2-3.1.2/src/Jinja2.egg-info/SOURCES.txt 2022-04-28 17:20:41.000000000 +0000 @@ -80,7 +80,6 @@ tests/test_core_tags.py tests/test_debug.py tests/test_ext.py -tests/test_features.py tests/test_filters.py tests/test_idtracking.py tests/test_imports.py @@ -89,6 +88,7 @@ tests/test_loader.py tests/test_nativetypes.py tests/test_nodes.py +tests/test_pickle.py tests/test_regression.py tests/test_runtime.py tests/test_security.py diff -Nru jinja2-3.0.3/tests/test_async_filters.py jinja2-3.1.2/tests/test_async_filters.py --- jinja2-3.0.3/tests/test_async_filters.py 2021-04-12 01:58:25.000000000 +0000 +++ jinja2-3.1.2/tests/test_async_filters.py 2022-03-24 14:16:48.000000000 +0000 @@ -57,6 +57,26 @@ ] +@pytest.mark.parametrize( + ("case_sensitive", "expect"), + [ + (False, "a: 1, 3\nb: 2\n"), + (True, "A: 3\na: 1\nb: 2\n"), + ], +) +def test_groupby_case(env_async, case_sensitive, expect): + tmpl = env_async.from_string( + "{% for k, vs in data|groupby('k', case_sensitive=cs) %}" + "{{ k }}: {{ vs|join(', ', attribute='v') }}\n" + "{% endfor %}" + ) + out = tmpl.render( + data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}], + cs=case_sensitive, + ) + assert out == expect + + @mark_dualiter("items", lambda: [("a", 1), ("a", 2), ("b", 1)]) def test_groupby_tuple_index(env_async, items): tmpl = env_async.from_string( diff -Nru jinja2-3.0.3/tests/test_async.py jinja2-3.1.2/tests/test_async.py --- jinja2-3.0.3/tests/test_async.py 2021-11-09 16:37:43.000000000 +0000 +++ jinja2-3.1.2/tests/test_async.py 2022-03-24 14:16:48.000000000 +0000 @@ -1,5 +1,4 @@ import asyncio -import sys import pytest @@ -14,19 +13,6 @@ from jinja2.nativetypes import NativeEnvironment -if sys.version_info < (3, 7): - - def run(coro): - loop = asyncio.get_event_loop() - return loop.run_until_complete(coro) - - -else: - - def run(coro): - return asyncio.run(coro) - - def test_basic_async(): t = Template( "{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True @@ -35,7 +21,7 @@ async def func(): return await t.render_async() - rv = run(func()) + rv = asyncio.run(func()) assert rv == "[1][2][3]" @@ -51,7 +37,7 @@ async def func(): return await t.render_async(async_func=async_func, normal_func=normal_func) - rv = run(func()) + rv = asyncio.run(func()) assert rv == "65" @@ -65,7 +51,6 @@ return 23 rv = t.render(async_func=async_func, normal_func=normal_func) - assert rv == "65" @@ -81,7 +66,7 @@ async def func(): return await t.render_async(async_func=async_func) - rv = run(func()) + rv = asyncio.run(func()) assert rv == "[42][42]" @@ -95,7 +80,7 @@ async def func(): return await t.render_async() - rv = run(func()) + rv = asyncio.run(func()) assert rv == "" @@ -172,19 +157,18 @@ test_env_async.from_string('{% from "foo" import bar, with with context %}') def test_exports(self, test_env_async): - m = run( - test_env_async.from_string( - """ + coro = test_env_async.from_string( + """ {% macro toplevel() %}...{% endmacro %} {% macro __private() %}...{% endmacro %} {% set variable = 42 %} {% for item in [1] %} {% macro notthere() %}{% endmacro %} {% endfor %} - """ - )._get_default_module_async() - ) - assert run(m.toplevel()) == "..." + """ + )._get_default_module_async() + m = asyncio.run(coro) + assert asyncio.run(m.toplevel()) == "..." assert not hasattr(m, "__missing") assert m.variable == 42 assert not hasattr(m, "notthere") @@ -621,7 +605,7 @@ actual = await t.render_async() assert actual == "Bar" - run(_test()) + asyncio.run(_test()) def test_chainable_undefined_aiter(): @@ -634,7 +618,7 @@ rv = await t.render_async(a={}) assert rv == "" - run(_test()) + asyncio.run(_test()) @pytest.fixture @@ -648,7 +632,7 @@ rv = await t.render_async(x=23) assert rv == 23 - run(_test()) + asyncio.run(_test()) def test_native_list_async(async_native_env): @@ -657,4 +641,20 @@ rv = await t.render_async(x=list(range(3))) assert rv == [0, 1, 2] - run(_test()) + asyncio.run(_test()) + + +def test_getitem_after_filter(): + env = Environment(enable_async=True) + env.filters["add_each"] = lambda v, x: [i + x for i in v] + t = env.from_string("{{ (a|add_each(2))[1:] }}") + out = t.render(a=range(3)) + assert out == "[3, 4]" + + +def test_getitem_after_call(): + env = Environment(enable_async=True) + env.globals["add_each"] = lambda v, x: [i + x for i in v] + t = env.from_string("{{ add_each(a, 2)[1:] }}") + out = t.render(a=range(3)) + assert out == "[3, 4]" diff -Nru jinja2-3.0.3/tests/test_debug.py jinja2-3.1.2/tests/test_debug.py --- jinja2-3.0.3/tests/test_debug.py 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/tests/test_debug.py 2022-03-24 14:16:48.000000000 +0000 @@ -25,7 +25,7 @@ m = re.search(expected_tb.strip(), "".join(tb)) assert ( m is not None - ), "Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}" + ), f"Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}" def test_runtime_error(self, fs_env): def test(): diff -Nru jinja2-3.0.3/tests/test_ext.py jinja2-3.1.2/tests/test_ext.py --- jinja2-3.0.3/tests/test_ext.py 2021-05-11 23:25:55.000000000 +0000 +++ jinja2-3.1.2/tests/test_ext.py 2022-03-24 14:16:48.000000000 +0000 @@ -43,6 +43,9 @@ "pgettext.html": '{{ pgettext("fruit", "Apple") }}', "npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s apples",' " apples) }}", + "pgettext_block": "{% trans 'fruit' num=apples %}Apple{% endtrans %}", + "npgettext_block": "{% trans 'fruit' num=apples %}{{ num }} apple" + "{% pluralize %}{{ num }} apples{% endtrans %}", "transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}", "transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}", "transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}", @@ -593,11 +596,20 @@ tmpl = newstyle_i18n_env.get_template("pgettext.html") assert tmpl.render(LANGUAGE="de") == "Apple" - def test_context_newstyle_plural(self): + def test_context_plural(self): tmpl = newstyle_i18n_env.get_template("npgettext.html") assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple" assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples" + def test_context_block(self): + tmpl = newstyle_i18n_env.get_template("pgettext_block") + assert tmpl.render(LANGUAGE="de") == "Apple" + + def test_context_plural_block(self): + tmpl = newstyle_i18n_env.get_template("npgettext_block") + assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple" + assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples" + class TestAutoEscape: def test_scoped_setting(self): diff -Nru jinja2-3.0.3/tests/test_features.py jinja2-3.1.2/tests/test_features.py --- jinja2-3.0.3/tests/test_features.py 2021-11-09 16:37:43.000000000 +0000 +++ jinja2-3.1.2/tests/test_features.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -import pytest - -from jinja2 import Template - - -# Python < 3.7 -def test_generator_stop(): - class X: - def __getattr__(self, name): - raise StopIteration() - - t = Template("a{{ bad.bar() }}b") - with pytest.raises(RuntimeError): - t.render(bad=X()) diff -Nru jinja2-3.0.3/tests/test_filters.py jinja2-3.1.2/tests/test_filters.py --- jinja2-3.0.3/tests/test_filters.py 2021-04-10 17:25:21.000000000 +0000 +++ jinja2-3.1.2/tests/test_filters.py 2022-03-24 14:16:48.000000000 +0000 @@ -251,6 +251,17 @@ out = tmpl.render() assert out == "foo" + def test_items(self, env): + d = {i: c for i, c in enumerate("abc")} + tmpl = env.from_string("""{{ d|items|list }}""") + out = tmpl.render(d=d) + assert out == "[(0, 'a'), (1, 'b'), (2, 'c')]" + + def test_items_undefined(self, env): + tmpl = env.from_string("""{{ d|items|list }}""") + out = tmpl.render() + assert out == "[]" + def test_pprint(self, env): from pprint import pformat @@ -608,6 +619,25 @@ ) assert out == "NY: emma, john\nWA: smith\n" + @pytest.mark.parametrize( + ("case_sensitive", "expect"), + [ + (False, "a: 1, 3\nb: 2\n"), + (True, "A: 3\na: 1\nb: 2\n"), + ], + ) + def test_groupby_case(self, env, case_sensitive, expect): + tmpl = env.from_string( + "{% for k, vs in data|groupby('k', case_sensitive=cs) %}" + "{{ k }}: {{ vs|join(', ', attribute='v') }}\n" + "{% endfor %}" + ) + out = tmpl.render( + data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}], + cs=case_sensitive, + ) + assert out == expect + def test_filtertag(self, env): tmpl = env.from_string( "{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}" diff -Nru jinja2-3.0.3/tests/test_loader.py jinja2-3.1.2/tests/test_loader.py --- jinja2-3.0.3/tests/test_loader.py 2021-11-09 20:21:27.000000000 +0000 +++ jinja2-3.1.2/tests/test_loader.py 2022-03-25 22:34:12.000000000 +0000 @@ -171,6 +171,15 @@ t = e.get_template("mojibake.txt") assert t.render() == expect + def test_filename_normpath(self): + """Nested template names should only contain ``os.sep`` in the + loaded filename. + """ + loader = loaders.FileSystemLoader(self.searchpath) + e = Environment(loader=loader) + t = e.get_template("foo/test.html") + assert t.filename == str(self.searchpath / "foo" / "test.html") + class TestModuleLoader: archive = None diff -Nru jinja2-3.0.3/tests/test_nativetypes.py jinja2-3.1.2/tests/test_nativetypes.py --- jinja2-3.0.3/tests/test_nativetypes.py 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/tests/test_nativetypes.py 2022-03-24 14:16:48.000000000 +0000 @@ -153,3 +153,10 @@ t = env.from_string(" {{ True }}") result = t.render() assert result == " True" + + +def test_macro(env): + t = env.from_string("{%- macro x() -%}{{- [1,2] -}}{%- endmacro -%}{{- x()[1] -}}") + result = t.render() + assert result == 2 + assert isinstance(result, int) diff -Nru jinja2-3.0.3/tests/test_pickle.py jinja2-3.1.2/tests/test_pickle.py --- jinja2-3.0.3/tests/test_pickle.py 1970-01-01 00:00:00.000000000 +0000 +++ jinja2-3.1.2/tests/test_pickle.py 2022-03-24 14:16:48.000000000 +0000 @@ -0,0 +1,6 @@ +import pickle + + +def test_environment(env): + env = pickle.loads(pickle.dumps(env)) + assert env.from_string("x={{ x }}").render(x=42) == "x=42" diff -Nru jinja2-3.0.3/tests/test_regression.py jinja2-3.1.2/tests/test_regression.py --- jinja2-3.0.3/tests/test_regression.py 2021-08-10 13:34:22.000000000 +0000 +++ jinja2-3.1.2/tests/test_regression.py 2022-03-24 14:16:48.000000000 +0000 @@ -591,23 +591,6 @@ env = MyEnvironment(loader=loader) assert env.get_template("test").render(foobar="test") == "test" - def test_legacy_custom_context(self, env): - from jinja2.runtime import Context, missing - - with pytest.deprecated_call(): - - class MyContext(Context): - def resolve(self, name): - if name == "foo": - return 42 - return super().resolve(name) - - x = MyContext(env, parent={"bar": 23}, name="foo", blocks={}) - assert x._legacy_resolve_mode - assert x.resolve_or_missing("foo") == 42 - assert x.resolve_or_missing("bar") == 23 - assert x.resolve_or_missing("baz") is missing - def test_recursive_loop_bug(self, env): tmpl = env.from_string( "{%- for value in values recursive %}1{% else %}0{% endfor -%}" diff -Nru jinja2-3.0.3/tox.ini jinja2-3.1.2/tox.ini --- jinja2-3.0.3/tox.ini 2021-11-09 17:17:58.000000000 +0000 +++ jinja2-3.1.2/tox.ini 2022-03-24 14:16:48.000000000 +0000 @@ -1,6 +1,6 @@ [tox] envlist = - py{311,310,39,38,37,36,py37} + py3{11,10,9,8,7},pypy3{8,7} style typing docs