Compare commits
No commits in common. "main" and "stable" have entirely different histories.
@ -1,17 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Install uv if not already installed
|
||||
if ! command -v uv &> /dev/null; then
|
||||
echo "Installing uv..."
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
fi
|
||||
|
||||
# Create venv using uv and install dependencies
|
||||
echo "Creating virtual environment and installing dependencies..."
|
||||
uv sync
|
||||
|
||||
# Install pre-commit hooks
|
||||
echo "Installing pre-commit hooks..."
|
||||
python3 -m venv --upgrade-deps .venv
|
||||
. .venv/bin/activate
|
||||
pip install -r requirements/dev.txt
|
||||
pip install -e .
|
||||
pre-commit install --install-hooks
|
||||
|
||||
1
.github/workflows/lock.yaml
vendored
1
.github/workflows/lock.yaml
vendored
@ -10,7 +10,6 @@ on:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
concurrency:
|
||||
group: lock
|
||||
jobs:
|
||||
|
||||
25
.github/workflows/pre-commit.yaml
vendored
25
.github/workflows/pre-commit.yaml
vendored
@ -1,25 +0,0 @@
|
||||
name: pre-commit
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main, stable]
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
||||
with:
|
||||
enable-cache: true
|
||||
prune-cache: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
id: setup-python
|
||||
with:
|
||||
python-version-file: pyproject.toml
|
||||
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }}
|
||||
- run: uv run --locked --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files
|
||||
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
||||
if: ${{ !cancelled() }}
|
||||
27
.github/workflows/publish.yaml
vendored
27
.github/workflows/publish.yaml
vendored
@ -1,22 +1,23 @@
|
||||
name: Publish
|
||||
on:
|
||||
push:
|
||||
tags: ['*']
|
||||
tags:
|
||||
- '*'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
||||
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
prune-cache: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version-file: pyproject.toml
|
||||
python-version: '3.x'
|
||||
cache: pip
|
||||
cache-dependency-path: requirements*/*.txt
|
||||
- run: pip install -r requirements/build.txt
|
||||
# Use the commit date instead of the current date during the build.
|
||||
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
|
||||
- run: uv build
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- run: python -m build
|
||||
- uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
path: ./dist
|
||||
create-release:
|
||||
@ -25,7 +26,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
- name: create release
|
||||
run: >
|
||||
gh release create --draft --repo ${{ github.repository }}
|
||||
@ -34,6 +35,8 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
publish-pypi:
|
||||
needs: [build]
|
||||
# Wait for approval before attempting to upload to PyPI. This allows reviewing the
|
||||
# files in the draft release.
|
||||
environment:
|
||||
name: publish
|
||||
url: https://pypi.org/project/Jinja2/${{ github.ref_name }}
|
||||
@ -41,7 +44,7 @@ jobs:
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
- uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
|
||||
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
- uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
|
||||
with:
|
||||
packages-dir: artifact/
|
||||
|
||||
42
.github/workflows/tests.yaml
vendored
42
.github/workflows/tests.yaml
vendored
@ -1,10 +1,10 @@
|
||||
name: Tests
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore: ['docs/**', 'README.md']
|
||||
push:
|
||||
branches: [main, stable]
|
||||
paths-ignore: ['docs/**', 'README.md']
|
||||
paths-ignore: ['docs/**', '*.md', '*.rst']
|
||||
pull_request:
|
||||
paths-ignore: [ 'docs/**', '*.md', '*.rst' ]
|
||||
jobs:
|
||||
tests:
|
||||
name: ${{ matrix.name || matrix.python }}
|
||||
@ -14,36 +14,38 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- {python: '3.13'}
|
||||
- {name: Windows, python: '3.13', os: windows-latest}
|
||||
- {name: Mac, python: '3.13', os: macos-latest}
|
||||
- {python: '3.12'}
|
||||
- {name: Windows, python: '3.12', os: windows-latest}
|
||||
- {name: Mac, python: '3.12', os: macos-latest}
|
||||
- {python: '3.11'}
|
||||
- {python: '3.10'}
|
||||
- {name: PyPy, python: 'pypy-3.11', tox: pypy3.11}
|
||||
- {python: '3.9'}
|
||||
- {python: '3.8'}
|
||||
- {python: '3.7'}
|
||||
- {name: PyPy, python: 'pypy-3.10', tox: pypy310}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
||||
with:
|
||||
enable-cache: true
|
||||
prune-cache: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
- run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
|
||||
allow-prereleases: true
|
||||
cache: pip
|
||||
cache-dependency-path: requirements*/*.txt
|
||||
- run: pip install tox
|
||||
- run: tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
|
||||
typing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
||||
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
prune-cache: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version-file: pyproject.toml
|
||||
python-version: '3.x'
|
||||
cache: pip
|
||||
cache-dependency-path: requirements*/*.txt
|
||||
- name: cache mypy
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: ./.mypy_cache
|
||||
key: mypy|${{ hashFiles('pyproject.toml') }}
|
||||
- run: uv run --locked tox run -e typing
|
||||
- run: pip install tox
|
||||
- run: tox run -e typing
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
.venv*/
|
||||
venv*/
|
||||
__pycache__/
|
||||
dist/
|
||||
.coverage*
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
ci:
|
||||
autoupdate_schedule: monthly
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 76e47323a83cd9795e4ff9a1de1c0d2eef610f17 # frozen: v0.11.11
|
||||
rev: v0.8.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 648bdbfd6bb1a82f132ecc2c666e0d1b2e4b0d94 # frozen: 0.7.8
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: debug-statements
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
version: 2
|
||||
build:
|
||||
os: ubuntu-24.04
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: '3.13'
|
||||
commands:
|
||||
- asdf plugin add uv
|
||||
- asdf install uv latest
|
||||
- asdf global uv latest
|
||||
- uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html
|
||||
python: '3.12'
|
||||
python:
|
||||
install:
|
||||
- requirements: requirements/docs.txt
|
||||
- method: pip
|
||||
path: .
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
builder: dirhtml
|
||||
fail_on_warning: true
|
||||
|
||||
15
CHANGES.rst
15
CHANGES.rst
@ -1,20 +1,5 @@
|
||||
.. currentmodule:: jinja2
|
||||
|
||||
Version 3.2.0
|
||||
-------------
|
||||
|
||||
Unreleased
|
||||
|
||||
- Drop support for Python 3.7, 3.8, and 3.9.
|
||||
- Update minimum MarkupSafe version to >= 3.0.
|
||||
- Update minimum Babel version to >= 2.17.
|
||||
- Deprecate the ``__version__`` attribute. Use feature detection or
|
||||
``importlib.metadata.version("jinja2")`` instead.
|
||||
- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``.
|
||||
:pr:`1793`
|
||||
- Use ``flit_core`` instead of ``setuptools`` as build backend.
|
||||
|
||||
|
||||
Version 3.1.6
|
||||
-------------
|
||||
|
||||
|
||||
@ -697,10 +697,10 @@ enabled before escaping the input and marking the output safe.
|
||||
br = Markup(br)
|
||||
|
||||
result = "\n\n".join(
|
||||
f"<p>{br.join(p.splitlines())}</p>"
|
||||
f"<p>{br.join(p.splitlines())}<\p>"
|
||||
for p in re.split(r"(?:\r\n|\r(?!\n)|\n){2,}", value)
|
||||
)
|
||||
return Markup(result) if eval_ctx.autoescape else result
|
||||
return Markup(result) if autoescape else result
|
||||
|
||||
|
||||
.. _writing-tests:
|
||||
|
||||
@ -30,7 +30,7 @@ Installation
|
||||
------------
|
||||
|
||||
We recommend using the latest version of Python. Jinja supports Python
|
||||
3.10 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
|
||||
|
||||
143
pyproject.toml
143
pyproject.toml
@ -1,65 +1,36 @@
|
||||
[project]
|
||||
name = "Jinja2"
|
||||
version = "3.2.0.dev"
|
||||
description = "A very fast and expressive template engine."
|
||||
readme = "README.md"
|
||||
license = "BSD-3-Clause"
|
||||
license-files = ["LICENSE.txt"]
|
||||
license = {file = "LICENSE.txt"}
|
||||
maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Web Environment",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||
"Topic :: Text Processing :: Markup :: HTML",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
dependencies = ["MarkupSafe>=3.0"]
|
||||
requires-python = ">=3.7"
|
||||
dependencies = ["MarkupSafe>=2.0"]
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.urls]
|
||||
Donate = "https://palletsprojects.com/donate"
|
||||
Documentation = "https://jinja.palletsprojects.com/"
|
||||
Changes = "https://jinja.palletsprojects.com/page/changes/"
|
||||
Changes = "https://jinja.palletsprojects.com/changes/"
|
||||
Source = "https://github.com/pallets/jinja/"
|
||||
Chat = "https://discord.gg/pallets"
|
||||
|
||||
[project.optional-dependencies]
|
||||
i18n = ["Babel>=2.17"]
|
||||
i18n = ["Babel>=2.7"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff",
|
||||
"tox",
|
||||
"tox-uv",
|
||||
]
|
||||
docs = [
|
||||
"pallets-sphinx-themes",
|
||||
"sphinx",
|
||||
"sphinxcontrib-log-cabinet",
|
||||
]
|
||||
docs-auto = [
|
||||
"sphinx-autobuild",
|
||||
]
|
||||
gha-update = [
|
||||
"gha-update ; python_full_version >= '3.12'",
|
||||
]
|
||||
pre-commit = [
|
||||
"pre-commit",
|
||||
"pre-commit-uv",
|
||||
]
|
||||
tests = [
|
||||
"pytest",
|
||||
"pytest-timeout",
|
||||
"trio"
|
||||
]
|
||||
typing = [
|
||||
"mypy",
|
||||
"pyright",
|
||||
"pytest",
|
||||
]
|
||||
[project.entry-points."babel.extractors"]
|
||||
jinja2 = "jinja2.ext:babel_extract[i18n]"
|
||||
|
||||
[build-system]
|
||||
requires = ["flit_core<4"]
|
||||
@ -71,18 +42,15 @@ name = "jinja2"
|
||||
[tool.flit.sdist]
|
||||
include = [
|
||||
"docs/",
|
||||
"examples/",
|
||||
"requirements/",
|
||||
"tests/",
|
||||
"CHANGES.rst",
|
||||
"uv.lock"
|
||||
"CHANGES.md",
|
||||
"tox.ini",
|
||||
]
|
||||
exclude = [
|
||||
"docs/_build/",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
default-groups = ["dev", "pre-commit", "tests", "typing"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
filterwarnings = [
|
||||
@ -96,24 +64,17 @@ source = ["jinja2", "tests"]
|
||||
[tool.coverage.paths]
|
||||
source = ["src", "*/site-packages"]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_also = [
|
||||
"if t.TYPE_CHECKING",
|
||||
"raise NotImplementedError",
|
||||
": \\.{3}",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
files = ["src"]
|
||||
python_version = "3.8"
|
||||
files = ["src/jinja2"]
|
||||
show_error_codes = true
|
||||
pretty = true
|
||||
strict = true
|
||||
|
||||
[tool.pyright]
|
||||
pythonVersion = "3.10"
|
||||
include = ["src"]
|
||||
typeCheckingMode = "standard"
|
||||
pythonVersion = "3.8"
|
||||
include = ["src/jinja2"]
|
||||
typeCheckingMode = "basic"
|
||||
|
||||
[tool.ruff]
|
||||
src = ["src"]
|
||||
@ -130,9 +91,6 @@ select = [
|
||||
"UP", # pyupgrade
|
||||
"W", # pycodestyle warning
|
||||
]
|
||||
ignore = [
|
||||
"UP038", # keep isinstance tuple
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
@ -142,70 +100,3 @@ order-by-type = false
|
||||
tag-only = [
|
||||
"slsa-framework/slsa-github-generator",
|
||||
]
|
||||
|
||||
[tool.tox]
|
||||
env_list = [
|
||||
"py3.13", "py3.12", "py3.11", "py3.10",
|
||||
"pypy3.11",
|
||||
"style",
|
||||
"typing",
|
||||
"docs",
|
||||
]
|
||||
|
||||
[tool.tox.env_run_base]
|
||||
description = "pytest on latest dependency versions"
|
||||
runner = "uv-venv-lock-runner"
|
||||
package = "wheel"
|
||||
wheel_build_env = ".pkg"
|
||||
constrain_package_deps = true
|
||||
use_frozen_constraints = true
|
||||
dependency_groups = ["tests"]
|
||||
commands = [[
|
||||
"pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}",
|
||||
{replace = "posargs", default = [], extend = true},
|
||||
]]
|
||||
|
||||
[tool.tox.env.style]
|
||||
description = "run all pre-commit hooks on all files"
|
||||
dependency_groups = ["pre-commit"]
|
||||
skip_install = true
|
||||
commands = [["pre-commit", "run", "--all-files"]]
|
||||
|
||||
[tool.tox.env.typing]
|
||||
description = "run static type checkers"
|
||||
dependency_groups = ["typing"]
|
||||
commands = [
|
||||
["mypy"],
|
||||
]
|
||||
|
||||
[tool.tox.env.docs]
|
||||
description = "build docs"
|
||||
dependency_groups = ["docs"]
|
||||
commands = [["sphinx-build", "-E", "-W", "-b", "dirhtml", "docs", "docs/_build/dirhtml"]]
|
||||
|
||||
[tool.tox.env.docs-auto]
|
||||
description = "continuously rebuild docs and start a local server"
|
||||
dependency_groups = ["docs", "docs-auto"]
|
||||
commands = [["sphinx-autobuild", "-W", "-b", "dirhtml", "--watch", "src", "docs", "docs/_build/dirhtml"]]
|
||||
|
||||
[tool.tox.env.update-actions]
|
||||
description = "update GitHub Actions pins"
|
||||
labels = ["update"]
|
||||
dependency_groups = ["gha-update"]
|
||||
skip_install = true
|
||||
commands = [["gha-update"]]
|
||||
|
||||
[tool.tox.env.update-pre_commit]
|
||||
description = "update pre-commit pins"
|
||||
labels = ["update"]
|
||||
dependency_groups = ["pre-commit"]
|
||||
skip_install = true
|
||||
commands = [["pre-commit", "autoupdate", "--freeze", "-j4"]]
|
||||
|
||||
[tool.tox.env.update-requirements]
|
||||
description = "update uv lock"
|
||||
labels = ["update"]
|
||||
dependency_groups = []
|
||||
no_default_groups = true
|
||||
skip_install = true
|
||||
commands = [["uv", "lock", {replace = "posargs", default = ["-U"], extend = true}]]
|
||||
|
||||
1
requirements/build.in
Normal file
1
requirements/build.in
Normal file
@ -0,0 +1 @@
|
||||
build
|
||||
12
requirements/build.txt
Normal file
12
requirements/build.txt
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.13
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile build.in
|
||||
#
|
||||
build==1.2.2.post1
|
||||
# via -r build.in
|
||||
packaging==24.2
|
||||
# via build
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
6
requirements/dev.in
Normal file
6
requirements/dev.in
Normal file
@ -0,0 +1,6 @@
|
||||
-r docs.in
|
||||
-r tests.in
|
||||
-r typing.in
|
||||
pip-compile-multi
|
||||
pre-commit
|
||||
tox
|
||||
151
requirements/dev.txt
Normal file
151
requirements/dev.txt
Normal file
@ -0,0 +1,151 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.13
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile dev.in
|
||||
#
|
||||
alabaster==1.0.0
|
||||
# via sphinx
|
||||
attrs==24.3.0
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
babel==2.16.0
|
||||
# via sphinx
|
||||
build==1.2.2.post1
|
||||
# via pip-tools
|
||||
cachetools==5.5.0
|
||||
# via tox
|
||||
certifi==2024.12.14
|
||||
# via requests
|
||||
cfgv==3.4.0
|
||||
# via pre-commit
|
||||
chardet==5.2.0
|
||||
# via tox
|
||||
charset-normalizer==3.4.0
|
||||
# via requests
|
||||
click==8.1.7
|
||||
# via
|
||||
# pip-compile-multi
|
||||
# pip-tools
|
||||
colorama==0.4.6
|
||||
# via tox
|
||||
distlib==0.3.9
|
||||
# via virtualenv
|
||||
docutils==0.21.2
|
||||
# via sphinx
|
||||
filelock==3.16.1
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
identify==2.6.3
|
||||
# via pre-commit
|
||||
idna==3.10
|
||||
# via
|
||||
# requests
|
||||
# trio
|
||||
imagesize==1.4.1
|
||||
# via sphinx
|
||||
iniconfig==2.0.0
|
||||
# via pytest
|
||||
jinja2==3.1.4
|
||||
# via sphinx
|
||||
markupsafe==3.0.2
|
||||
# via jinja2
|
||||
mypy==1.14.0
|
||||
# via -r /Users/david/Projects/jinja/requirements/typing.in
|
||||
mypy-extensions==1.0.0
|
||||
# via mypy
|
||||
nodeenv==1.9.1
|
||||
# via pre-commit
|
||||
outcome==1.3.0.post0
|
||||
# via trio
|
||||
packaging==24.2
|
||||
# via
|
||||
# build
|
||||
# pallets-sphinx-themes
|
||||
# pyproject-api
|
||||
# pytest
|
||||
# sphinx
|
||||
# tox
|
||||
pallets-sphinx-themes==2.3.0
|
||||
# via -r /Users/david/Projects/jinja/requirements/docs.in
|
||||
pip-compile-multi==2.7.1
|
||||
# via -r dev.in
|
||||
pip-tools==7.4.1
|
||||
# via pip-compile-multi
|
||||
platformdirs==4.3.6
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
pluggy==1.5.0
|
||||
# via
|
||||
# pytest
|
||||
# tox
|
||||
pre-commit==4.0.1
|
||||
# via -r dev.in
|
||||
pygments==2.18.0
|
||||
# via sphinx
|
||||
pyproject-api==1.8.0
|
||||
# via tox
|
||||
pyproject-hooks==1.2.0
|
||||
# via
|
||||
# build
|
||||
# pip-tools
|
||||
pytest==8.3.4
|
||||
# via -r /Users/david/Projects/jinja/requirements/tests.in
|
||||
pyyaml==6.0.2
|
||||
# via pre-commit
|
||||
requests==2.32.3
|
||||
# via sphinx
|
||||
sniffio==1.3.1
|
||||
# via trio
|
||||
snowballstemmer==2.2.0
|
||||
# via sphinx
|
||||
sortedcontainers==2.4.0
|
||||
# via trio
|
||||
sphinx==8.1.3
|
||||
# via
|
||||
# -r /Users/david/Projects/jinja/requirements/docs.in
|
||||
# pallets-sphinx-themes
|
||||
# sphinx-issues
|
||||
# sphinx-notfound-page
|
||||
# sphinxcontrib-log-cabinet
|
||||
sphinx-issues==5.0.0
|
||||
# via -r /Users/david/Projects/jinja/requirements/docs.in
|
||||
sphinx-notfound-page==1.0.4
|
||||
# via pallets-sphinx-themes
|
||||
sphinxcontrib-applehelp==2.0.0
|
||||
# via sphinx
|
||||
sphinxcontrib-devhelp==2.0.0
|
||||
# via sphinx
|
||||
sphinxcontrib-htmlhelp==2.1.0
|
||||
# via sphinx
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
# via sphinx
|
||||
sphinxcontrib-log-cabinet==1.0.1
|
||||
# via -r /Users/david/Projects/jinja/requirements/docs.in
|
||||
sphinxcontrib-qthelp==2.0.0
|
||||
# via sphinx
|
||||
sphinxcontrib-serializinghtml==2.0.0
|
||||
# via sphinx
|
||||
toposort==1.10
|
||||
# via pip-compile-multi
|
||||
tox==4.23.2
|
||||
# via -r dev.in
|
||||
trio==0.27.0
|
||||
# via -r /Users/david/Projects/jinja/requirements/tests.in
|
||||
typing-extensions==4.12.2
|
||||
# via mypy
|
||||
urllib3==2.2.3
|
||||
# via requests
|
||||
virtualenv==20.28.0
|
||||
# via
|
||||
# pre-commit
|
||||
# tox
|
||||
wheel==0.45.1
|
||||
# via pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
||||
# setuptools
|
||||
4
requirements/docs.in
Normal file
4
requirements/docs.in
Normal file
@ -0,0 +1,4 @@
|
||||
Pallets-Sphinx-Themes
|
||||
Sphinx
|
||||
sphinx-issues
|
||||
sphinxcontrib-log-cabinet
|
||||
63
requirements/docs.txt
Normal file
63
requirements/docs.txt
Normal file
@ -0,0 +1,63 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.13
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile docs.in
|
||||
#
|
||||
alabaster==1.0.0
|
||||
# via sphinx
|
||||
babel==2.16.0
|
||||
# via sphinx
|
||||
certifi==2024.12.14
|
||||
# via requests
|
||||
charset-normalizer==3.4.0
|
||||
# via requests
|
||||
docutils==0.21.2
|
||||
# via sphinx
|
||||
idna==3.10
|
||||
# via requests
|
||||
imagesize==1.4.1
|
||||
# via sphinx
|
||||
jinja2==3.1.4
|
||||
# via sphinx
|
||||
markupsafe==3.0.2
|
||||
# via jinja2
|
||||
packaging==24.2
|
||||
# via
|
||||
# pallets-sphinx-themes
|
||||
# sphinx
|
||||
pallets-sphinx-themes==2.3.0
|
||||
# via -r docs.in
|
||||
pygments==2.18.0
|
||||
# via sphinx
|
||||
requests==2.32.3
|
||||
# via sphinx
|
||||
snowballstemmer==2.2.0
|
||||
# via sphinx
|
||||
sphinx==8.1.3
|
||||
# via
|
||||
# -r docs.in
|
||||
# pallets-sphinx-themes
|
||||
# sphinx-issues
|
||||
# sphinx-notfound-page
|
||||
# sphinxcontrib-log-cabinet
|
||||
sphinx-issues==5.0.0
|
||||
# via -r docs.in
|
||||
sphinx-notfound-page==1.0.4
|
||||
# via pallets-sphinx-themes
|
||||
sphinxcontrib-applehelp==2.0.0
|
||||
# via sphinx
|
||||
sphinxcontrib-devhelp==2.0.0
|
||||
# via sphinx
|
||||
sphinxcontrib-htmlhelp==2.1.0
|
||||
# via sphinx
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
# via sphinx
|
||||
sphinxcontrib-log-cabinet==1.0.1
|
||||
# via -r docs.in
|
||||
sphinxcontrib-qthelp==2.0.0
|
||||
# via sphinx
|
||||
sphinxcontrib-serializinghtml==2.0.0
|
||||
# via sphinx
|
||||
urllib3==2.2.3
|
||||
# via requests
|
||||
2
requirements/tests.in
Normal file
2
requirements/tests.in
Normal file
@ -0,0 +1,2 @@
|
||||
pytest
|
||||
trio
|
||||
28
requirements/tests.txt
Normal file
28
requirements/tests.txt
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.13
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile tests.in
|
||||
#
|
||||
attrs==24.3.0
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
idna==3.10
|
||||
# via trio
|
||||
iniconfig==2.0.0
|
||||
# via pytest
|
||||
outcome==1.3.0.post0
|
||||
# via trio
|
||||
packaging==24.2
|
||||
# via pytest
|
||||
pluggy==1.5.0
|
||||
# via pytest
|
||||
pytest==8.3.4
|
||||
# via -r tests.in
|
||||
sniffio==1.3.1
|
||||
# via trio
|
||||
sortedcontainers==2.4.0
|
||||
# via trio
|
||||
trio==0.27.0
|
||||
# via -r tests.in
|
||||
43
requirements/tests37.txt
Normal file
43
requirements/tests37.txt
Normal file
@ -0,0 +1,43 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.7
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --output-file=tests37.txt tests.in
|
||||
#
|
||||
attrs==24.2.0
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
exceptiongroup==1.2.2
|
||||
# via
|
||||
# pytest
|
||||
# trio
|
||||
idna==3.10
|
||||
# via trio
|
||||
importlib-metadata==6.7.0
|
||||
# via
|
||||
# attrs
|
||||
# pluggy
|
||||
# pytest
|
||||
iniconfig==2.0.0
|
||||
# via pytest
|
||||
outcome==1.3.0.post0
|
||||
# via trio
|
||||
packaging==24.0
|
||||
# via pytest
|
||||
pluggy==1.2.0
|
||||
# via pytest
|
||||
pytest==7.4.4
|
||||
# via -r tests.in
|
||||
sniffio==1.3.1
|
||||
# via trio
|
||||
sortedcontainers==2.4.0
|
||||
# via trio
|
||||
tomli==2.0.1
|
||||
# via pytest
|
||||
trio==0.22.2
|
||||
# via -r tests.in
|
||||
typing-extensions==4.7.1
|
||||
# via importlib-metadata
|
||||
zipp==3.15.0
|
||||
# via importlib-metadata
|
||||
1
requirements/typing.in
Normal file
1
requirements/typing.in
Normal file
@ -0,0 +1 @@
|
||||
mypy
|
||||
12
requirements/typing.txt
Normal file
12
requirements/typing.txt
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.13
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile typing.in
|
||||
#
|
||||
mypy==1.14.0
|
||||
# via -r typing.in
|
||||
mypy-extensions==1.0.0
|
||||
# via mypy
|
||||
typing-extensions==4.12.2
|
||||
# via mypy
|
||||
@ -30,8 +30,8 @@ def collapse_ranges(data):
|
||||
Source: https://stackoverflow.com/a/4629241/400617
|
||||
"""
|
||||
for _, g in itertools.groupby(enumerate(data), lambda x: ord(x[1]) - x[0]):
|
||||
lb = list(g)
|
||||
yield lb[0][1], lb[-1][1]
|
||||
b = list(g)
|
||||
yield b[0][1], b[-1][1]
|
||||
|
||||
|
||||
def build_pattern(ranges):
|
||||
@ -54,16 +54,17 @@ def build_pattern(ranges):
|
||||
|
||||
|
||||
def main():
|
||||
"""Build the regex pattern and write it to ``jinja2/_identifier.py``."""
|
||||
"""Build the regex pattern and write it to
|
||||
``jinja2/_identifier.py``.
|
||||
"""
|
||||
pattern = build_pattern(collapse_ranges(get_characters()))
|
||||
filename = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "src", "jinja2", "_identifier.py")
|
||||
)
|
||||
|
||||
with open(filename, "w", encoding="utf8") as f:
|
||||
f.write("# generated by scripts/generate_identifier_pattern.py")
|
||||
f.write(f"# Python {sys.version_info[0]}.{sys.version_info[1]}\n")
|
||||
f.write("import re\n\n")
|
||||
f.write("# generated by scripts/generate_identifier_pattern.py\n")
|
||||
f.write("pattern = re.compile(\n")
|
||||
f.write(f' r"[\\w{pattern}]+" # noqa: B950\n')
|
||||
f.write(")\n")
|
||||
|
||||
@ -3,10 +3,6 @@ non-XML syntax that supports inline expressions and an optional
|
||||
sandboxed environment.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
|
||||
from .bccache import BytecodeCache as BytecodeCache
|
||||
from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
|
||||
from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
|
||||
@ -39,19 +35,4 @@ 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
|
||||
|
||||
|
||||
def __getattr__(name: str) -> t.Any:
|
||||
if name == "__version__":
|
||||
import importlib.metadata
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The `__version__` attribute is deprecated and will be removed in"
|
||||
" Jinja 3.3. Use feature detection or"
|
||||
' `importlib.metadata.version("jinja2")` instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return importlib.metadata.version("jinja2")
|
||||
|
||||
raise AttributeError(name)
|
||||
__version__ = "3.1.6"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# generated by scripts/generate_identifier_pattern.py for Python 3.10
|
||||
import re
|
||||
|
||||
# generated by scripts/generate_identifier_pattern.py
|
||||
pattern = re.compile(
|
||||
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍୕-ୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣඁ-ඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᪿᫀᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧ꠬ꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧𐺫𐺬-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑇎𑇏𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑤰-𑤵𑤷𑤸𑤻-𑤾𑥀𑥂𑥃𑧑-𑧗𑧚-𑧠𑧤𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽏𖽑-𖾇𖾏-𖾒𖿤𖿰𖿱𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞀪𞄰-𞄶𞋬-𞣐𞋯-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
|
||||
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
|
||||
)
|
||||
|
||||
@ -85,7 +85,7 @@ class _IteratorToAsyncIterator(t.Generic[V]):
|
||||
|
||||
|
||||
def auto_aiter(
|
||||
iterable: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
) -> "t.AsyncIterator[V]":
|
||||
if hasattr(iterable, "__aiter__"):
|
||||
return iterable.__aiter__()
|
||||
@ -94,6 +94,6 @@ def auto_aiter(
|
||||
|
||||
|
||||
async def auto_to_list(
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
) -> list["V"]:
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
) -> t.List["V"]:
|
||||
return [x async for x in auto_aiter(value)]
|
||||
|
||||
@ -27,7 +27,9 @@ if t.TYPE_CHECKING:
|
||||
class _MemcachedClient(te.Protocol):
|
||||
def get(self, key: str) -> bytes: ...
|
||||
|
||||
def set(self, key: str, value: bytes, timeout: int | None = None) -> None: ...
|
||||
def set(
|
||||
self, key: str, value: bytes, timeout: t.Optional[int] = None
|
||||
) -> None: ...
|
||||
|
||||
|
||||
bc_version = 5
|
||||
@ -58,7 +60,7 @@ class Bucket:
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Resets the bucket (unloads the bytecode)."""
|
||||
self.code: CodeType | None = None
|
||||
self.code: t.Optional[CodeType] = None
|
||||
|
||||
def load_bytecode(self, f: t.BinaryIO) -> None:
|
||||
"""Loads bytecode from a file or file like object."""
|
||||
@ -147,7 +149,9 @@ class BytecodeCache:
|
||||
by a particular environment.
|
||||
"""
|
||||
|
||||
def get_cache_key(self, name: str, filename: str | None = None) -> str:
|
||||
def get_cache_key(
|
||||
self, name: str, filename: t.Optional[t.Union[str]] = None
|
||||
) -> str:
|
||||
"""Returns the unique hash key for this template name."""
|
||||
hash = sha1(name.encode("utf-8"))
|
||||
|
||||
@ -164,7 +168,7 @@ class BytecodeCache:
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
filename: str | None,
|
||||
filename: t.Optional[str],
|
||||
source: str,
|
||||
) -> Bucket:
|
||||
"""Return a cache bucket for the given template. All arguments are
|
||||
@ -200,7 +204,7 @@ class FileSystemBytecodeCache(BytecodeCache):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, directory: str | None = None, pattern: str = "__jinja2_%s.cache"
|
||||
self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
|
||||
) -> None:
|
||||
if directory is None:
|
||||
directory = self._get_default_cache_dir()
|
||||
@ -373,7 +377,7 @@ class MemcachedBytecodeCache(BytecodeCache):
|
||||
self,
|
||||
client: "_MemcachedClient",
|
||||
prefix: str = "jinja2/bytecode/",
|
||||
timeout: int | None = None,
|
||||
timeout: t.Optional[int] = None,
|
||||
ignore_memcache_errors: bool = True,
|
||||
):
|
||||
self.client = client
|
||||
|
||||
@ -101,12 +101,12 @@ def _make_unop(
|
||||
def generate(
|
||||
node: nodes.Template,
|
||||
environment: "Environment",
|
||||
name: str | None,
|
||||
filename: str | None,
|
||||
stream: t.TextIO | None = None,
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str],
|
||||
stream: t.Optional[t.TextIO] = None,
|
||||
defer_init: bool = False,
|
||||
optimized: bool = True,
|
||||
) -> str | None:
|
||||
) -> t.Optional[str]:
|
||||
"""Generate the python source for a node tree."""
|
||||
if not isinstance(node, nodes.Template):
|
||||
raise TypeError("Can't compile non template nodes")
|
||||
@ -139,7 +139,9 @@ def has_safe_repr(value: t.Any) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def find_undeclared(nodes: t.Iterable[nodes.Node], names: t.Iterable[str]) -> set[str]:
|
||||
def find_undeclared(
|
||||
nodes: t.Iterable[nodes.Node], names: t.Iterable[str]
|
||||
) -> t.Set[str]:
|
||||
"""Check if the names passed are accessed undeclared. The return value
|
||||
is a set of all the undeclared names from the sequence of names found.
|
||||
"""
|
||||
@ -153,7 +155,7 @@ def find_undeclared(nodes: t.Iterable[nodes.Node], names: t.Iterable[str]) -> se
|
||||
|
||||
|
||||
class MacroRef:
|
||||
def __init__(self, node: nodes.Macro | nodes.CallBlock) -> None:
|
||||
def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None:
|
||||
self.node = node
|
||||
self.accesses_caller = False
|
||||
self.accesses_kwargs = False
|
||||
@ -167,7 +169,7 @@ class Frame:
|
||||
self,
|
||||
eval_ctx: EvalContext,
|
||||
parent: t.Optional["Frame"] = None,
|
||||
level: int | None = None,
|
||||
level: t.Optional[int] = None,
|
||||
) -> None:
|
||||
self.eval_ctx = eval_ctx
|
||||
|
||||
@ -185,10 +187,10 @@ class Frame:
|
||||
# this for example affects {% filter %} or {% macro %}. If a frame
|
||||
# is buffered this variable points to the name of the list used as
|
||||
# buffer.
|
||||
self.buffer: str | None = None
|
||||
self.buffer: t.Optional[str] = None
|
||||
|
||||
# the name of the block we're in, otherwise None.
|
||||
self.block: str | None = None
|
||||
self.block: t.Optional[str] = None
|
||||
|
||||
else:
|
||||
self.symbols = Symbols(parent.symbols, level=level)
|
||||
@ -251,8 +253,8 @@ class DependencyFinderVisitor(NodeVisitor):
|
||||
"""A visitor that collects filter and test calls."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.filters: set[str] = set()
|
||||
self.tests: set[str] = set()
|
||||
self.filters: t.Set[str] = set()
|
||||
self.tests: t.Set[str] = set()
|
||||
|
||||
def visit_Filter(self, node: nodes.Filter) -> None:
|
||||
self.generic_visit(node)
|
||||
@ -274,7 +276,7 @@ class UndeclaredNameVisitor(NodeVisitor):
|
||||
|
||||
def __init__(self, names: t.Iterable[str]) -> None:
|
||||
self.names = set(names)
|
||||
self.undeclared: set[str] = set()
|
||||
self.undeclared: t.Set[str] = set()
|
||||
|
||||
def visit_Name(self, node: nodes.Name) -> None:
|
||||
if node.ctx == "load" and node.name in self.names:
|
||||
@ -299,9 +301,9 @@ class CodeGenerator(NodeVisitor):
|
||||
def __init__(
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str | None,
|
||||
filename: str | None,
|
||||
stream: t.TextIO | None = None,
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str],
|
||||
stream: t.Optional[t.TextIO] = None,
|
||||
defer_init: bool = False,
|
||||
optimized: bool = True,
|
||||
) -> None:
|
||||
@ -313,17 +315,17 @@ class CodeGenerator(NodeVisitor):
|
||||
self.stream = stream
|
||||
self.created_block_context = False
|
||||
self.defer_init = defer_init
|
||||
self.optimizer: Optimizer | None = None
|
||||
self.optimizer: t.Optional[Optimizer] = None
|
||||
|
||||
if optimized:
|
||||
self.optimizer = Optimizer(environment)
|
||||
|
||||
# aliases for imports
|
||||
self.import_aliases: dict[str, str] = {}
|
||||
self.import_aliases: t.Dict[str, str] = {}
|
||||
|
||||
# a registry for all blocks. Because blocks are moved out
|
||||
# into the global python scope they are registered here
|
||||
self.blocks: dict[str, nodes.Block] = {}
|
||||
self.blocks: t.Dict[str, nodes.Block] = {}
|
||||
|
||||
# the number of extends statements so far
|
||||
self.extends_so_far = 0
|
||||
@ -337,12 +339,12 @@ class CodeGenerator(NodeVisitor):
|
||||
self.code_lineno = 1
|
||||
|
||||
# registry of all filters and tests (global, not block local)
|
||||
self.tests: dict[str, str] = {}
|
||||
self.filters: dict[str, str] = {}
|
||||
self.tests: t.Dict[str, str] = {}
|
||||
self.filters: t.Dict[str, str] = {}
|
||||
|
||||
# the debug information
|
||||
self.debug_info: list[tuple[int, int]] = []
|
||||
self._write_debug_info: int | None = None
|
||||
self.debug_info: t.List[t.Tuple[int, int]] = []
|
||||
self._write_debug_info: t.Optional[int] = None
|
||||
|
||||
# the number of new lines before the next write()
|
||||
self._new_lines = 0
|
||||
@ -361,10 +363,10 @@ class CodeGenerator(NodeVisitor):
|
||||
self._indentation = 0
|
||||
|
||||
# Tracks toplevel assignments
|
||||
self._assign_stack: list[set[str]] = []
|
||||
self._assign_stack: t.List[t.Set[str]] = []
|
||||
|
||||
# Tracks parameter definition blocks
|
||||
self._param_def_block: list[set[str]] = []
|
||||
self._param_def_block: t.List[t.Set[str]] = []
|
||||
|
||||
# Tracks the current context.
|
||||
self._context_reference_stack = ["context"]
|
||||
@ -417,7 +419,7 @@ class CodeGenerator(NodeVisitor):
|
||||
"""Outdent by step."""
|
||||
self._indentation -= step
|
||||
|
||||
def start_write(self, frame: Frame, node: nodes.Node | None = None) -> None:
|
||||
def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None:
|
||||
"""Yield or write into the frame buffer."""
|
||||
if frame.buffer is None:
|
||||
self.writeline("yield ", node)
|
||||
@ -430,7 +432,7 @@ class CodeGenerator(NodeVisitor):
|
||||
self.write(")")
|
||||
|
||||
def simple_write(
|
||||
self, s: str, frame: Frame, node: nodes.Node | None = None
|
||||
self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None
|
||||
) -> None:
|
||||
"""Simple shortcut for start_write + write + end_write."""
|
||||
self.start_write(frame, node)
|
||||
@ -462,12 +464,14 @@ class CodeGenerator(NodeVisitor):
|
||||
self._new_lines = 0
|
||||
self.stream.write(x)
|
||||
|
||||
def writeline(self, x: str, node: nodes.Node | None = None, extra: int = 0) -> None:
|
||||
def writeline(
|
||||
self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0
|
||||
) -> None:
|
||||
"""Combination of newline and write."""
|
||||
self.newline(node, extra)
|
||||
self.write(x)
|
||||
|
||||
def newline(self, node: nodes.Node | None = None, extra: int = 0) -> None:
|
||||
def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None:
|
||||
"""Add one or more newlines before the next write."""
|
||||
self._new_lines = max(self._new_lines, 1 + extra)
|
||||
if node is not None and node.lineno != self._last_line:
|
||||
@ -476,9 +480,9 @@ class CodeGenerator(NodeVisitor):
|
||||
|
||||
def signature(
|
||||
self,
|
||||
node: nodes.Call | nodes.Filter | nodes.Test,
|
||||
node: t.Union[nodes.Call, nodes.Filter, nodes.Test],
|
||||
frame: Frame,
|
||||
extra_kwargs: t.Mapping[str, t.Any] | None = None,
|
||||
extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
) -> None:
|
||||
"""Writes a function call to the stream for the current node.
|
||||
A leading comma is added automatically. The extra keyword
|
||||
@ -608,8 +612,8 @@ class CodeGenerator(NodeVisitor):
|
||||
return f"{self.choose_async()}def {name}"
|
||||
|
||||
def macro_body(
|
||||
self, node: nodes.Macro | nodes.CallBlock, frame: Frame
|
||||
) -> tuple[Frame, MacroRef]:
|
||||
self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame
|
||||
) -> t.Tuple[Frame, MacroRef]:
|
||||
"""Dump the function def of a macro or call block."""
|
||||
frame = frame.inner()
|
||||
frame.symbols.analyze_node(node)
|
||||
@ -822,7 +826,9 @@ class CodeGenerator(NodeVisitor):
|
||||
|
||||
# -- Statement Visitors
|
||||
|
||||
def visit_Template(self, node: nodes.Template, frame: Frame | None = None) -> None:
|
||||
def visit_Template(
|
||||
self, node: nodes.Template, frame: t.Optional[Frame] = None
|
||||
) -> None:
|
||||
assert frame is None, "no root frame allowed"
|
||||
eval_ctx = EvalContext(self.environment, self.name)
|
||||
|
||||
@ -1092,7 +1098,7 @@ class CodeGenerator(NodeVisitor):
|
||||
self.outdent()
|
||||
|
||||
def _import_common(
|
||||
self, node: nodes.Import | nodes.FromImport, frame: Frame
|
||||
self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame
|
||||
) -> None:
|
||||
self.write(f"{self.choose_async('await ')}environment.get_template(")
|
||||
self.visit(node.template, frame)
|
||||
@ -1365,7 +1371,7 @@ class CodeGenerator(NodeVisitor):
|
||||
with_frame = frame.inner()
|
||||
with_frame.symbols.analyze_node(node)
|
||||
self.enter_frame(with_frame)
|
||||
for target, expr in zip(node.targets, node.values, strict=False):
|
||||
for target, expr in zip(node.targets, node.values):
|
||||
self.newline()
|
||||
self.visit(target, with_frame)
|
||||
self.write(" = ")
|
||||
@ -1378,8 +1384,8 @@ class CodeGenerator(NodeVisitor):
|
||||
self.visit(node.node, frame)
|
||||
|
||||
class _FinalizeInfo(t.NamedTuple):
|
||||
const: t.Callable[..., str] | None
|
||||
src: str | None
|
||||
const: t.Optional[t.Callable[..., str]]
|
||||
src: t.Optional[str]
|
||||
|
||||
@staticmethod
|
||||
def _default_finalize(value: t.Any) -> t.Any:
|
||||
@ -1389,7 +1395,7 @@ class CodeGenerator(NodeVisitor):
|
||||
"""
|
||||
return str(value)
|
||||
|
||||
_finalize: _FinalizeInfo | None = None
|
||||
_finalize: t.Optional[_FinalizeInfo] = None
|
||||
|
||||
def _make_finalize(self) -> _FinalizeInfo:
|
||||
"""Build the finalize function to be used on constants and at
|
||||
@ -1407,7 +1413,7 @@ class CodeGenerator(NodeVisitor):
|
||||
if self._finalize is not None:
|
||||
return self._finalize
|
||||
|
||||
finalize: t.Callable[..., t.Any] | None
|
||||
finalize: t.Optional[t.Callable[..., t.Any]]
|
||||
finalize = default = self._default_finalize
|
||||
src = None
|
||||
|
||||
@ -1505,7 +1511,7 @@ class CodeGenerator(NodeVisitor):
|
||||
self.indent()
|
||||
|
||||
finalize = self._make_finalize()
|
||||
body: list[list[t.Any] | nodes.Expr] = []
|
||||
body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = []
|
||||
|
||||
# Evaluate constants at compile time if possible. Each item in
|
||||
# body will be either a list of static data or a node to be
|
||||
@ -1580,7 +1586,7 @@ class CodeGenerator(NodeVisitor):
|
||||
# it is only valid if it references a Namespace object. Emit a check for
|
||||
# that for each ref here, before assignment code is emitted. This can't
|
||||
# be done in visit_NSRef as the ref could be in the middle of a tuple.
|
||||
seen_refs: set[str] = set()
|
||||
seen_refs: t.Set[str] = set()
|
||||
|
||||
for nsref in node.find_all(nodes.NSRef):
|
||||
if nsref.name in seen_refs:
|
||||
@ -1787,7 +1793,7 @@ class CodeGenerator(NodeVisitor):
|
||||
|
||||
@contextmanager
|
||||
def _filter_test_common(
|
||||
self, node: nodes.Filter | nodes.Test, frame: Frame, is_filter: bool
|
||||
self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool
|
||||
) -> t.Iterator[None]:
|
||||
if self.environment.is_async:
|
||||
self.write("(await auto_await(")
|
||||
|
||||
@ -11,7 +11,7 @@ if t.TYPE_CHECKING:
|
||||
from .runtime import Context
|
||||
|
||||
|
||||
def rewrite_traceback_stack(source: str | None = None) -> BaseException:
|
||||
def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
|
||||
"""Rewrite the current exception to replace any tracebacks from
|
||||
within compiled template code with tracebacks that look like they
|
||||
came from the template source.
|
||||
@ -74,7 +74,7 @@ def rewrite_traceback_stack(source: str | None = None) -> BaseException:
|
||||
|
||||
|
||||
def fake_traceback( # type: ignore
|
||||
exc_value: BaseException, tb: TracebackType | None, filename: str, lineno: int
|
||||
exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
|
||||
) -> TracebackType:
|
||||
"""Produce a new traceback object that looks like it came from the
|
||||
template source instead of the compiled code. The filename, line
|
||||
@ -118,7 +118,26 @@ def fake_traceback( # type: ignore
|
||||
elif function.startswith("block_"):
|
||||
location = f"block {function[6:]!r}"
|
||||
|
||||
code = code.replace(co_name=location)
|
||||
if sys.version_info >= (3, 8):
|
||||
code = code.replace(co_name=location)
|
||||
else:
|
||||
code = CodeType(
|
||||
code.co_argcount,
|
||||
code.co_kwonlyargcount,
|
||||
code.co_nlocals,
|
||||
code.co_stacksize,
|
||||
code.co_flags,
|
||||
code.co_code,
|
||||
code.co_consts,
|
||||
code.co_names,
|
||||
code.co_varnames,
|
||||
code.co_filename,
|
||||
location,
|
||||
code.co_firstlineno,
|
||||
code.co_lnotab,
|
||||
code.co_freevars,
|
||||
code.co_cellvars,
|
||||
)
|
||||
|
||||
# Execute the new code, which is guaranteed to raise, and return
|
||||
# the new traceback without this frame.
|
||||
@ -128,15 +147,15 @@ def fake_traceback( # type: ignore
|
||||
return sys.exc_info()[2].tb_next # type: ignore
|
||||
|
||||
|
||||
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> dict[str, t.Any]:
|
||||
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]:
|
||||
"""Based on the runtime locals, get the context that would be
|
||||
available at that point in the template.
|
||||
"""
|
||||
# Start with the current template context.
|
||||
ctx: Context | None = real_locals.get("context")
|
||||
ctx: t.Optional[Context] = real_locals.get("context")
|
||||
|
||||
if ctx is not None:
|
||||
data: dict[str, t.Any] = ctx.get_all().copy()
|
||||
data: t.Dict[str, t.Any] = ctx.get_all().copy()
|
||||
else:
|
||||
data = {}
|
||||
|
||||
@ -144,7 +163,7 @@ def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> dict[str, t.Any]:
|
||||
# rather than pushing a context. Local variables follow the scheme
|
||||
# l_depth_name. Find the highest-depth local that has a value for
|
||||
# each name.
|
||||
local_overrides: dict[str, tuple[int, t.Any]] = {}
|
||||
local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {}
|
||||
|
||||
for name, value in real_locals.items():
|
||||
if not name.startswith("l_") or value is missing:
|
||||
|
||||
@ -17,8 +17,8 @@ VARIABLE_START_STRING = "{{"
|
||||
VARIABLE_END_STRING = "}}"
|
||||
COMMENT_START_STRING = "{#"
|
||||
COMMENT_END_STRING = "#}"
|
||||
LINE_STATEMENT_PREFIX: str | None = None
|
||||
LINE_COMMENT_PREFIX: str | None = None
|
||||
LINE_STATEMENT_PREFIX: t.Optional[str] = None
|
||||
LINE_COMMENT_PREFIX: t.Optional[str] = None
|
||||
TRIM_BLOCKS = False
|
||||
LSTRIP_BLOCKS = False
|
||||
NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n"
|
||||
@ -36,7 +36,7 @@ DEFAULT_NAMESPACE = {
|
||||
}
|
||||
|
||||
# default policies
|
||||
DEFAULT_POLICIES: dict[str, t.Any] = {
|
||||
DEFAULT_POLICIES: t.Dict[str, t.Any] = {
|
||||
"compiler.ascii_str": True,
|
||||
"urlize.rel": "noopener",
|
||||
"urlize.target": None,
|
||||
|
||||
@ -7,7 +7,6 @@ import typing
|
||||
import typing as t
|
||||
import weakref
|
||||
from collections import ChainMap
|
||||
from contextlib import aclosing
|
||||
from functools import lru_cache
|
||||
from functools import partial
|
||||
from functools import reduce
|
||||
@ -67,7 +66,7 @@ _env_bound = t.TypeVar("_env_bound", bound="Environment")
|
||||
|
||||
# for direct template usage we have up to ten living environments
|
||||
@lru_cache(maxsize=10)
|
||||
def get_spontaneous_environment(cls: type[_env_bound], *args: t.Any) -> _env_bound:
|
||||
def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound:
|
||||
"""Return a new spontaneous environment. A spontaneous environment
|
||||
is used for templates created directly rather than through an
|
||||
existing environment.
|
||||
@ -82,7 +81,7 @@ def get_spontaneous_environment(cls: type[_env_bound], *args: t.Any) -> _env_bou
|
||||
|
||||
def create_cache(
|
||||
size: int,
|
||||
) -> t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None:
|
||||
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
|
||||
"""Return the cache class for the given size."""
|
||||
if size == 0:
|
||||
return None
|
||||
@ -94,8 +93,8 @@ def create_cache(
|
||||
|
||||
|
||||
def copy_cache(
|
||||
cache: t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None,
|
||||
) -> t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None:
|
||||
cache: t.Optional[t.MutableMapping[t.Any, t.Any]],
|
||||
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
|
||||
"""Create an empty copy of the given cache."""
|
||||
if cache is None:
|
||||
return None
|
||||
@ -108,8 +107,8 @@ def copy_cache(
|
||||
|
||||
def load_extensions(
|
||||
environment: "Environment",
|
||||
extensions: t.Sequence[str | type["Extension"]],
|
||||
) -> dict[str, "Extension"]:
|
||||
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]],
|
||||
) -> t.Dict[str, "Extension"]:
|
||||
"""Load the extensions from the list and bind it to the environment.
|
||||
Returns a dict of instantiated extensions.
|
||||
"""
|
||||
@ -117,7 +116,7 @@ def load_extensions(
|
||||
|
||||
for extension in extensions:
|
||||
if isinstance(extension, str):
|
||||
extension = t.cast(type["Extension"], import_string(extension))
|
||||
extension = t.cast(t.Type["Extension"], import_string(extension))
|
||||
|
||||
result[extension.identifier] = extension(environment)
|
||||
|
||||
@ -126,9 +125,9 @@ def load_extensions(
|
||||
|
||||
def _environment_config_check(environment: _env_bound) -> _env_bound:
|
||||
"""Perform a sanity check on the environment."""
|
||||
assert issubclass(environment.undefined, Undefined), (
|
||||
"'undefined' must be a subclass of 'jinja2.Undefined'."
|
||||
)
|
||||
assert issubclass(
|
||||
environment.undefined, Undefined
|
||||
), "'undefined' must be a subclass of 'jinja2.Undefined'."
|
||||
assert (
|
||||
environment.block_start_string
|
||||
!= environment.variable_start_string
|
||||
@ -282,15 +281,15 @@ class Environment:
|
||||
|
||||
#: the class that is used for code generation. See
|
||||
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
|
||||
code_generator_class: type["CodeGenerator"] = CodeGenerator
|
||||
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: type[Context] = Context
|
||||
context_class: t.Type[Context] = Context
|
||||
|
||||
template_class: type["Template"]
|
||||
template_class: t.Type["Template"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -300,17 +299,17 @@ class Environment:
|
||||
variable_end_string: str = VARIABLE_END_STRING,
|
||||
comment_start_string: str = COMMENT_START_STRING,
|
||||
comment_end_string: str = COMMENT_END_STRING,
|
||||
line_statement_prefix: str | None = LINE_STATEMENT_PREFIX,
|
||||
line_comment_prefix: str | None = LINE_COMMENT_PREFIX,
|
||||
line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
|
||||
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
|
||||
trim_blocks: bool = TRIM_BLOCKS,
|
||||
lstrip_blocks: bool = LSTRIP_BLOCKS,
|
||||
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
|
||||
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
|
||||
extensions: t.Sequence[str | type["Extension"]] = (),
|
||||
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
|
||||
optimized: bool = True,
|
||||
undefined: type[Undefined] = Undefined,
|
||||
finalize: t.Callable[..., t.Any] | None = None,
|
||||
autoescape: bool | t.Callable[[str | None], bool] = False,
|
||||
undefined: t.Type[Undefined] = Undefined,
|
||||
finalize: t.Optional[t.Callable[..., t.Any]] = None,
|
||||
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
|
||||
loader: t.Optional["BaseLoader"] = None,
|
||||
cache_size: int = 400,
|
||||
auto_reload: bool = True,
|
||||
@ -343,7 +342,7 @@ class Environment:
|
||||
self.keep_trailing_newline = keep_trailing_newline
|
||||
|
||||
# runtime information
|
||||
self.undefined: type[Undefined] = undefined
|
||||
self.undefined: t.Type[Undefined] = undefined
|
||||
self.optimized = optimized
|
||||
self.finalize = finalize
|
||||
self.autoescape = autoescape
|
||||
@ -368,7 +367,7 @@ class Environment:
|
||||
self.is_async = enable_async
|
||||
_environment_config_check(self)
|
||||
|
||||
def add_extension(self, extension: str | type["Extension"]) -> None:
|
||||
def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None:
|
||||
"""Adds an extension after the environment was created.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
@ -392,17 +391,17 @@ class Environment:
|
||||
variable_end_string: str = missing,
|
||||
comment_start_string: str = missing,
|
||||
comment_end_string: str = missing,
|
||||
line_statement_prefix: str | None = missing,
|
||||
line_comment_prefix: str | None = missing,
|
||||
line_statement_prefix: t.Optional[str] = missing,
|
||||
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[str | type["Extension"]] = missing,
|
||||
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
|
||||
optimized: bool = missing,
|
||||
undefined: type[Undefined] = missing,
|
||||
finalize: t.Callable[..., t.Any] | None = missing,
|
||||
autoescape: bool | t.Callable[[str | None], bool] = missing,
|
||||
undefined: t.Type[Undefined] = missing,
|
||||
finalize: t.Optional[t.Callable[..., t.Any]] = missing,
|
||||
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing,
|
||||
loader: t.Optional["BaseLoader"] = missing,
|
||||
cache_size: int = missing,
|
||||
auto_reload: bool = missing,
|
||||
@ -464,7 +463,9 @@ class Environment:
|
||||
"""Iterates over the extensions by priority."""
|
||||
return iter(sorted(self.extensions.values(), key=lambda x: x.priority))
|
||||
|
||||
def getitem(self, obj: t.Any, argument: str | t.Any) -> t.Any | Undefined:
|
||||
def getitem(
|
||||
self, obj: t.Any, argument: t.Union[str, t.Any]
|
||||
) -> t.Union[t.Any, Undefined]:
|
||||
"""Get an item or attribute of an object but prefer the item."""
|
||||
try:
|
||||
return obj[argument]
|
||||
@ -496,12 +497,12 @@ class Environment:
|
||||
|
||||
def _filter_test_common(
|
||||
self,
|
||||
name: str | Undefined,
|
||||
name: t.Union[str, Undefined],
|
||||
value: t.Any,
|
||||
args: t.Sequence[t.Any] | None,
|
||||
kwargs: t.Mapping[str, t.Any] | None,
|
||||
context: Context | None,
|
||||
eval_ctx: EvalContext | None,
|
||||
args: t.Optional[t.Sequence[t.Any]],
|
||||
kwargs: t.Optional[t.Mapping[str, t.Any]],
|
||||
context: t.Optional[Context],
|
||||
eval_ctx: t.Optional[EvalContext],
|
||||
is_filter: bool,
|
||||
) -> t.Any:
|
||||
if is_filter:
|
||||
@ -552,10 +553,10 @@ class Environment:
|
||||
self,
|
||||
name: str,
|
||||
value: t.Any,
|
||||
args: t.Sequence[t.Any] | None = None,
|
||||
kwargs: t.Mapping[str, t.Any] | None = None,
|
||||
context: Context | None = None,
|
||||
eval_ctx: EvalContext | None = None,
|
||||
args: t.Optional[t.Sequence[t.Any]] = None,
|
||||
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
context: t.Optional[Context] = None,
|
||||
eval_ctx: t.Optional[EvalContext] = None,
|
||||
) -> t.Any:
|
||||
"""Invoke a filter on a value the same way the compiler does.
|
||||
|
||||
@ -573,10 +574,10 @@ class Environment:
|
||||
self,
|
||||
name: str,
|
||||
value: t.Any,
|
||||
args: t.Sequence[t.Any] | None = None,
|
||||
kwargs: t.Mapping[str, t.Any] | None = None,
|
||||
context: Context | None = None,
|
||||
eval_ctx: EvalContext | None = None,
|
||||
args: t.Optional[t.Sequence[t.Any]] = None,
|
||||
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
context: t.Optional[Context] = None,
|
||||
eval_ctx: t.Optional[EvalContext] = None,
|
||||
) -> t.Any:
|
||||
"""Invoke a test on a value the same way the compiler does.
|
||||
|
||||
@ -598,8 +599,8 @@ class Environment:
|
||||
def parse(
|
||||
self,
|
||||
source: str,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
) -> nodes.Template:
|
||||
"""Parse the sourcecode and return the abstract syntax tree. This
|
||||
tree of nodes is used by the compiler to convert the template into
|
||||
@ -615,7 +616,7 @@ class Environment:
|
||||
self.handle_exception(source=source)
|
||||
|
||||
def _parse(
|
||||
self, source: str, name: str | None, filename: str | None
|
||||
self, source: str, name: t.Optional[str], filename: t.Optional[str]
|
||||
) -> nodes.Template:
|
||||
"""Internal parsing function used by `parse` and `compile`."""
|
||||
return Parser(self, source, name, filename).parse()
|
||||
@ -623,9 +624,9 @@ class Environment:
|
||||
def lex(
|
||||
self,
|
||||
source: str,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
) -> t.Iterator[tuple[int, str, str]]:
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
) -> t.Iterator[t.Tuple[int, str, str]]:
|
||||
"""Lex the given sourcecode and return a generator that yields
|
||||
tokens as tuples in the form ``(lineno, token_type, value)``.
|
||||
This can be useful for :ref:`extension development <writing-extensions>`
|
||||
@ -644,8 +645,8 @@ class Environment:
|
||||
def preprocess(
|
||||
self,
|
||||
source: str,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
) -> str:
|
||||
"""Preprocesses the source with all extensions. This is automatically
|
||||
called for all parsing and compiling methods but *not* for :meth:`lex`
|
||||
@ -660,9 +661,9 @@ class Environment:
|
||||
def _tokenize(
|
||||
self,
|
||||
source: str,
|
||||
name: str | None,
|
||||
filename: str | None = None,
|
||||
state: str | None = None,
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str] = None,
|
||||
state: t.Optional[str] = None,
|
||||
) -> TokenStream:
|
||||
"""Called by the parser to do the preprocessing and filtering
|
||||
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
|
||||
@ -681,8 +682,8 @@ class Environment:
|
||||
def _generate(
|
||||
self,
|
||||
source: nodes.Template,
|
||||
name: str | None,
|
||||
filename: str | None,
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str],
|
||||
defer_init: bool = False,
|
||||
) -> str:
|
||||
"""Internal hook that can be overridden to hook a different generate
|
||||
@ -710,9 +711,9 @@ class Environment:
|
||||
@typing.overload
|
||||
def compile(
|
||||
self,
|
||||
source: str | nodes.Template,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
source: t.Union[str, nodes.Template],
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
raw: "te.Literal[False]" = False,
|
||||
defer_init: bool = False,
|
||||
) -> CodeType: ...
|
||||
@ -720,9 +721,9 @@ class Environment:
|
||||
@typing.overload
|
||||
def compile(
|
||||
self,
|
||||
source: str | nodes.Template,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
source: t.Union[str, nodes.Template],
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
raw: "te.Literal[True]" = ...,
|
||||
defer_init: bool = False,
|
||||
) -> str: ...
|
||||
@ -730,12 +731,12 @@ class Environment:
|
||||
@internalcode
|
||||
def compile(
|
||||
self,
|
||||
source: str | nodes.Template,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
source: t.Union[str, nodes.Template],
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
raw: bool = False,
|
||||
defer_init: bool = False,
|
||||
) -> str | CodeType:
|
||||
) -> t.Union[str, CodeType]:
|
||||
"""Compile a node or template source code. The `name` parameter is
|
||||
the load name of the template after it was joined using
|
||||
:meth:`join_path` if necessary, not the filename on the file system.
|
||||
@ -817,10 +818,10 @@ class Environment:
|
||||
def compile_templates(
|
||||
self,
|
||||
target: t.Union[str, "os.PathLike[str]"],
|
||||
extensions: t.Collection[str] | None = None,
|
||||
filter_func: t.Callable[[str], bool] | None = None,
|
||||
zip: str | None = "deflated",
|
||||
log_function: t.Callable[[str], None] | None = None,
|
||||
extensions: t.Optional[t.Collection[str]] = None,
|
||||
filter_func: t.Optional[t.Callable[[str], bool]] = None,
|
||||
zip: t.Optional[str] = "deflated",
|
||||
log_function: t.Optional[t.Callable[[str], None]] = None,
|
||||
ignore_errors: bool = True,
|
||||
) -> None:
|
||||
"""Finds all the templates the loader can find, compiles them
|
||||
@ -897,9 +898,9 @@ class Environment:
|
||||
|
||||
def list_templates(
|
||||
self,
|
||||
extensions: t.Collection[str] | None = None,
|
||||
filter_func: t.Callable[[str], bool] | None = None,
|
||||
) -> list[str]:
|
||||
extensions: t.Optional[t.Collection[str]] = None,
|
||||
filter_func: t.Optional[t.Callable[[str], bool]] = None,
|
||||
) -> t.List[str]:
|
||||
"""Returns a list of templates for this environment. This requires
|
||||
that the loader supports the loader's
|
||||
:meth:`~BaseLoader.list_templates` method.
|
||||
@ -932,7 +933,7 @@ class Environment:
|
||||
|
||||
return names
|
||||
|
||||
def handle_exception(self, source: str | None = None) -> "te.NoReturn":
|
||||
def handle_exception(self, source: t.Optional[str] = None) -> "te.NoReturn":
|
||||
"""Exception handling helper. This is used internally to either raise
|
||||
rewritten exceptions or return a rendered traceback for the template.
|
||||
"""
|
||||
@ -954,7 +955,7 @@ class Environment:
|
||||
|
||||
@internalcode
|
||||
def _load_template(
|
||||
self, name: str, globals: t.MutableMapping[str, t.Any] | None
|
||||
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")
|
||||
@ -981,8 +982,8 @@ class Environment:
|
||||
def get_template(
|
||||
self,
|
||||
name: t.Union[str, "Template"],
|
||||
parent: str | None = None,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
parent: t.Optional[str] = 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
|
||||
@ -1018,8 +1019,8 @@ class Environment:
|
||||
def select_template(
|
||||
self,
|
||||
names: t.Iterable[t.Union[str, "Template"]],
|
||||
parent: str | None = None,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
parent: t.Optional[str] = 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`
|
||||
@ -1071,9 +1072,11 @@ class Environment:
|
||||
@internalcode
|
||||
def get_or_select_template(
|
||||
self,
|
||||
template_name_or_list: t.Union[str, "Template", list[t.Union[str, "Template"]]],
|
||||
parent: str | None = None,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
template_name_or_list: t.Union[
|
||||
str, "Template", t.List[t.Union[str, "Template"]]
|
||||
],
|
||||
parent: t.Optional[str] = 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.
|
||||
@ -1088,9 +1091,9 @@ class Environment:
|
||||
|
||||
def from_string(
|
||||
self,
|
||||
source: str | nodes.Template,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
template_class: type["Template"] | None = None,
|
||||
source: t.Union[str, nodes.Template],
|
||||
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
|
||||
:attr:`loader`.
|
||||
@ -1108,7 +1111,7 @@ class Environment:
|
||||
return cls.from_code(self, self.compile(source), gs, None)
|
||||
|
||||
def make_globals(
|
||||
self, d: t.MutableMapping[str, t.Any] | None
|
||||
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`.
|
||||
@ -1149,38 +1152,38 @@ class Template:
|
||||
|
||||
#: Type of environment to create when creating a template directly
|
||||
#: rather than through an existing environment.
|
||||
environment_class: type[Environment] = Environment
|
||||
environment_class: t.Type[Environment] = Environment
|
||||
|
||||
environment: Environment
|
||||
globals: t.MutableMapping[str, t.Any]
|
||||
name: str | None
|
||||
filename: str | None
|
||||
blocks: dict[str, t.Callable[[Context], t.Iterator[str]]]
|
||||
name: t.Optional[str]
|
||||
filename: t.Optional[str]
|
||||
blocks: t.Dict[str, t.Callable[[Context], t.Iterator[str]]]
|
||||
root_render_func: t.Callable[[Context], t.Iterator[str]]
|
||||
_module: t.Optional["TemplateModule"]
|
||||
_debug_info: str
|
||||
_uptodate: t.Callable[[], bool] | None
|
||||
_uptodate: t.Optional[t.Callable[[], bool]]
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
source: str | nodes.Template,
|
||||
source: t.Union[str, nodes.Template],
|
||||
block_start_string: str = BLOCK_START_STRING,
|
||||
block_end_string: str = BLOCK_END_STRING,
|
||||
variable_start_string: str = VARIABLE_START_STRING,
|
||||
variable_end_string: str = VARIABLE_END_STRING,
|
||||
comment_start_string: str = COMMENT_START_STRING,
|
||||
comment_end_string: str = COMMENT_END_STRING,
|
||||
line_statement_prefix: str | None = LINE_STATEMENT_PREFIX,
|
||||
line_comment_prefix: str | None = LINE_COMMENT_PREFIX,
|
||||
line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
|
||||
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
|
||||
trim_blocks: bool = TRIM_BLOCKS,
|
||||
lstrip_blocks: bool = LSTRIP_BLOCKS,
|
||||
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
|
||||
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
|
||||
extensions: t.Sequence[str | type["Extension"]] = (),
|
||||
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
|
||||
optimized: bool = True,
|
||||
undefined: type[Undefined] = Undefined,
|
||||
finalize: t.Callable[..., t.Any] | None = None,
|
||||
autoescape: bool | t.Callable[[str | None], bool] = False,
|
||||
undefined: t.Type[Undefined] = Undefined,
|
||||
finalize: t.Optional[t.Callable[..., t.Any]] = None,
|
||||
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
|
||||
enable_async: bool = False,
|
||||
) -> t.Any: # it returns a `Template`, but this breaks the sphinx build...
|
||||
env = get_spontaneous_environment(
|
||||
@ -1216,7 +1219,7 @@ class Template:
|
||||
environment: Environment,
|
||||
code: CodeType,
|
||||
globals: t.MutableMapping[str, t.Any],
|
||||
uptodate: t.Callable[[], bool] | None = None,
|
||||
uptodate: t.Optional[t.Callable[[], bool]] = None,
|
||||
) -> "Template":
|
||||
"""Creates a template object from compiled code and the globals. This
|
||||
is used by the loaders and environment to create a template object.
|
||||
@ -1331,7 +1334,7 @@ class Template:
|
||||
if self.environment.is_async:
|
||||
import asyncio
|
||||
|
||||
async def to_list() -> list[str]:
|
||||
async def to_list() -> t.List[str]:
|
||||
return [x async for x in self.generate_async(*args, **kwargs)]
|
||||
|
||||
yield from asyncio.run(to_list())
|
||||
@ -1358,19 +1361,22 @@ class Template:
|
||||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
||||
try:
|
||||
agen: t.AsyncGenerator[str, None] = self.root_render_func(ctx) # type: ignore[assignment]
|
||||
|
||||
async with aclosing(agen):
|
||||
async for event in agen:
|
||||
agen = self.root_render_func(ctx)
|
||||
try:
|
||||
async for event in agen: # type: ignore
|
||||
yield event
|
||||
finally:
|
||||
# we can't use async with aclosing(...) because that's only
|
||||
# in 3.10+
|
||||
await agen.aclose() # type: ignore
|
||||
except Exception:
|
||||
yield self.environment.handle_exception()
|
||||
|
||||
def new_context(
|
||||
self,
|
||||
vars: dict[str, t.Any] | None = None,
|
||||
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
shared: bool = False,
|
||||
locals: t.Mapping[str, t.Any] | None = None,
|
||||
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
) -> Context:
|
||||
"""Create a new :class:`Context` for this template. The vars
|
||||
provided will be passed to the template. Per default the globals
|
||||
@ -1385,9 +1391,9 @@ class Template:
|
||||
|
||||
def make_module(
|
||||
self,
|
||||
vars: dict[str, t.Any] | None = None,
|
||||
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
shared: bool = False,
|
||||
locals: t.Mapping[str, t.Any] | None = None,
|
||||
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
) -> "TemplateModule":
|
||||
"""This method works like the :attr:`module` attribute when called
|
||||
without arguments but it will evaluate the template on every call
|
||||
@ -1400,9 +1406,9 @@ class Template:
|
||||
|
||||
async def make_module_async(
|
||||
self,
|
||||
vars: dict[str, t.Any] | None = None,
|
||||
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
shared: bool = False,
|
||||
locals: t.Mapping[str, t.Any] | None = None,
|
||||
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
) -> "TemplateModule":
|
||||
"""As template module creation can invoke template code for
|
||||
asynchronous executions this method must be used instead of the
|
||||
@ -1417,7 +1423,7 @@ class Template:
|
||||
)
|
||||
|
||||
@internalcode
|
||||
def _get_default_module(self, ctx: Context | None = None) -> "TemplateModule":
|
||||
def _get_default_module(self, ctx: t.Optional[Context] = None) -> "TemplateModule":
|
||||
"""If a context is passed in, this means that the template was
|
||||
imported. Imported templates have access to the current
|
||||
template's globals by default, but they can only be accessed via
|
||||
@ -1444,7 +1450,7 @@ class Template:
|
||||
return self._module
|
||||
|
||||
async def _get_default_module_async(
|
||||
self, ctx: Context | None = None
|
||||
self, ctx: t.Optional[Context] = None
|
||||
) -> "TemplateModule":
|
||||
if ctx is not None:
|
||||
keys = ctx.globals_keys - self.globals.keys()
|
||||
@ -1490,7 +1496,7 @@ class Template:
|
||||
return self._uptodate()
|
||||
|
||||
@property
|
||||
def debug_info(self) -> list[tuple[int, int]]:
|
||||
def debug_info(self) -> t.List[t.Tuple[int, int]]:
|
||||
"""The debug info mapping."""
|
||||
if self._debug_info:
|
||||
return [
|
||||
@ -1518,7 +1524,7 @@ class TemplateModule:
|
||||
self,
|
||||
template: Template,
|
||||
context: Context,
|
||||
body_stream: t.Iterable[str] | None = None,
|
||||
body_stream: t.Optional[t.Iterable[str]] = None,
|
||||
) -> None:
|
||||
if body_stream is None:
|
||||
if context.environment.is_async:
|
||||
@ -1558,7 +1564,7 @@ class TemplateExpression:
|
||||
self._template = template
|
||||
self._undefined_to_none = undefined_to_none
|
||||
|
||||
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any | None:
|
||||
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
|
||||
context = self._template.new_context(dict(*args, **kwargs))
|
||||
consume(self._template.root_render_func(context))
|
||||
rv = context.vars["result"]
|
||||
@ -1584,9 +1590,9 @@ class TemplateStream:
|
||||
|
||||
def dump(
|
||||
self,
|
||||
fp: str | t.IO[bytes],
|
||||
encoding: str | None = None,
|
||||
errors: str | None = "strict",
|
||||
fp: t.Union[str, t.IO[bytes]],
|
||||
encoding: t.Optional[str] = None,
|
||||
errors: t.Optional[str] = "strict",
|
||||
) -> None:
|
||||
"""Dump the complete stream into a file or file-like object.
|
||||
Per default strings are written, if you want to encode
|
||||
@ -1628,7 +1634,7 @@ class TemplateStream:
|
||||
self.buffered = False
|
||||
|
||||
def _buffered_generator(self, size: int) -> t.Iterator[str]:
|
||||
buf: list[str] = []
|
||||
buf: t.List[str] = []
|
||||
c_size = 0
|
||||
push = buf.append
|
||||
|
||||
|
||||
@ -7,11 +7,11 @@ if t.TYPE_CHECKING:
|
||||
class TemplateError(Exception):
|
||||
"""Baseclass for all template errors."""
|
||||
|
||||
def __init__(self, message: str | None = None) -> None:
|
||||
def __init__(self, message: t.Optional[str] = None) -> None:
|
||||
super().__init__(message)
|
||||
|
||||
@property
|
||||
def message(self) -> str | None:
|
||||
def message(self) -> t.Optional[str]:
|
||||
return self.args[0] if self.args else None
|
||||
|
||||
|
||||
@ -25,12 +25,12 @@ class TemplateNotFound(IOError, LookupError, TemplateError):
|
||||
|
||||
# Silence the Python warning about message being deprecated since
|
||||
# it's not valid here.
|
||||
message: str | None = None
|
||||
message: t.Optional[str] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: t.Union[str, "Undefined"] | None,
|
||||
message: str | None = None,
|
||||
name: t.Optional[t.Union[str, "Undefined"]],
|
||||
message: t.Optional[str] = None,
|
||||
) -> None:
|
||||
IOError.__init__(self, name)
|
||||
|
||||
@ -65,7 +65,7 @@ class TemplatesNotFound(TemplateNotFound):
|
||||
def __init__(
|
||||
self,
|
||||
names: t.Sequence[t.Union[str, "Undefined"]] = (),
|
||||
message: str | None = None,
|
||||
message: t.Optional[str] = None,
|
||||
) -> None:
|
||||
if message is None:
|
||||
from .runtime import Undefined
|
||||
@ -92,14 +92,14 @@ class TemplateSyntaxError(TemplateError):
|
||||
self,
|
||||
message: str,
|
||||
lineno: int,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(message)
|
||||
self.lineno = lineno
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.source: str | None = None
|
||||
self.source: t.Optional[str] = None
|
||||
|
||||
# this is set to True if the debug.translate_syntax_error
|
||||
# function translated the syntax error into a new traceback
|
||||
|
||||
@ -37,12 +37,12 @@ if t.TYPE_CHECKING:
|
||||
self, context: str, singular: str, plural: str, n: int
|
||||
) -> str: ...
|
||||
|
||||
_SupportedTranslations = _TranslationsBasic | _TranslationsContext
|
||||
_SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
|
||||
|
||||
|
||||
# I18N functions available in Jinja templates. If the I18N library
|
||||
# provides ugettext, it will be assigned to gettext.
|
||||
GETTEXT_FUNCTIONS: tuple[str, ...] = (
|
||||
GETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
|
||||
"_",
|
||||
"gettext",
|
||||
"ngettext",
|
||||
@ -77,7 +77,7 @@ class Extension:
|
||||
cls.identifier = f"{cls.__module__}.{cls.__name__}"
|
||||
|
||||
#: if this extension parses this is the list of tags it's listening to.
|
||||
tags: set[str] = set()
|
||||
tags: t.Set[str] = set()
|
||||
|
||||
#: the priority of that extension. This is especially useful for
|
||||
#: extensions that preprocess values. A lower value means higher
|
||||
@ -97,7 +97,7 @@ class Extension:
|
||||
return rv
|
||||
|
||||
def preprocess(
|
||||
self, source: str, name: str | None, filename: str | None = None
|
||||
self, source: str, name: t.Optional[str], filename: t.Optional[str] = None
|
||||
) -> str:
|
||||
"""This method is called before the actual lexing and can be used to
|
||||
preprocess the source. The `filename` is optional. The return value
|
||||
@ -115,7 +115,7 @@ class Extension:
|
||||
"""
|
||||
return stream
|
||||
|
||||
def parse(self, parser: "Parser") -> nodes.Node | list[nodes.Node]:
|
||||
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||
"""If any of the :attr:`tags` matched this method is called with the
|
||||
parser as first argument. The token the parser stream is pointing at
|
||||
is the name token that matched. This method has to return one or a
|
||||
@ -123,7 +123,9 @@ class Extension:
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def attr(self, name: str, lineno: int | None = None) -> nodes.ExtensionAttribute:
|
||||
def attr(
|
||||
self, name: str, lineno: t.Optional[int] = None
|
||||
) -> nodes.ExtensionAttribute:
|
||||
"""Return an attribute node for the current extension. This is useful
|
||||
to pass constants on extensions to generated template code.
|
||||
|
||||
@ -136,11 +138,11 @@ class Extension:
|
||||
def call_method(
|
||||
self,
|
||||
name: str,
|
||||
args: list[nodes.Expr] | None = None,
|
||||
kwargs: list[nodes.Keyword] | None = None,
|
||||
dyn_args: nodes.Expr | None = None,
|
||||
dyn_kwargs: nodes.Expr | None = None,
|
||||
lineno: int | None = None,
|
||||
args: t.Optional[t.List[nodes.Expr]] = None,
|
||||
kwargs: t.Optional[t.List[nodes.Keyword]] = None,
|
||||
dyn_args: t.Optional[nodes.Expr] = None,
|
||||
dyn_kwargs: t.Optional[nodes.Expr] = None,
|
||||
lineno: t.Optional[int] = None,
|
||||
) -> nodes.Call:
|
||||
"""Call a method of the extension. This is a shortcut for
|
||||
:meth:`attr` + :class:`jinja2.nodes.Call`.
|
||||
@ -162,7 +164,7 @@ class Extension:
|
||||
@pass_context
|
||||
def _gettext_alias(
|
||||
__context: Context, *args: t.Any, **kwargs: t.Any
|
||||
) -> t.Any | Undefined:
|
||||
) -> t.Union[t.Any, Undefined]:
|
||||
return __context.call(__context.resolve("gettext"), *args, **kwargs)
|
||||
|
||||
|
||||
@ -266,7 +268,7 @@ class InternationalizationExtension(Extension):
|
||||
)
|
||||
|
||||
def _install(
|
||||
self, translations: "_SupportedTranslations", newstyle: bool | None = None
|
||||
self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
|
||||
) -> None:
|
||||
# ugettext and ungettext are preferred in case the I18N library
|
||||
# is providing compatibility with older Python versions.
|
||||
@ -283,25 +285,41 @@ class InternationalizationExtension(Extension):
|
||||
gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
|
||||
)
|
||||
|
||||
def _install_null(self, newstyle: bool | None = None) -> None:
|
||||
def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
|
||||
import gettext
|
||||
|
||||
translations = gettext.NullTranslations()
|
||||
|
||||
if hasattr(translations, "pgettext"):
|
||||
# Python < 3.8
|
||||
pgettext = translations.pgettext
|
||||
else:
|
||||
|
||||
def pgettext(c: str, s: str) -> str: # type: ignore[misc]
|
||||
return s
|
||||
|
||||
if hasattr(translations, "npgettext"):
|
||||
npgettext = translations.npgettext
|
||||
else:
|
||||
|
||||
def npgettext(c: str, s: str, p: str, n: int) -> str: # type: ignore[misc]
|
||||
return s if n == 1 else p
|
||||
|
||||
self._install_callables(
|
||||
gettext=translations.gettext,
|
||||
ngettext=translations.ngettext,
|
||||
newstyle=newstyle,
|
||||
pgettext=translations.pgettext,
|
||||
npgettext=translations.npgettext,
|
||||
pgettext=pgettext,
|
||||
npgettext=npgettext,
|
||||
)
|
||||
|
||||
def _install_callables(
|
||||
self,
|
||||
gettext: t.Callable[[str], str],
|
||||
ngettext: t.Callable[[str, str, int], str],
|
||||
newstyle: bool | None = None,
|
||||
pgettext: t.Callable[[str, str], str] | None = None,
|
||||
npgettext: t.Callable[[str, str, str, int], str] | None = None,
|
||||
newstyle: t.Optional[bool] = None,
|
||||
pgettext: t.Optional[t.Callable[[str, str], str]] = None,
|
||||
npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
|
||||
) -> None:
|
||||
if newstyle is not None:
|
||||
self.environment.newstyle_gettext = newstyle # type: ignore
|
||||
@ -325,14 +343,16 @@ class InternationalizationExtension(Extension):
|
||||
|
||||
def _extract(
|
||||
self,
|
||||
source: str | nodes.Template,
|
||||
source: t.Union[str, nodes.Template],
|
||||
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
||||
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...]]]:
|
||||
) -> t.Iterator[
|
||||
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
||||
]:
|
||||
if isinstance(source, str):
|
||||
source = self.environment.parse(source)
|
||||
return extract_from_ast(source, gettext_functions)
|
||||
|
||||
def parse(self, parser: "Parser") -> nodes.Node | list[nodes.Node]:
|
||||
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||
"""Parse a translatable tag."""
|
||||
lineno = next(parser.stream).lineno
|
||||
|
||||
@ -345,10 +365,10 @@ class InternationalizationExtension(Extension):
|
||||
# find all the variables referenced. Additionally a variable can be
|
||||
# defined in the body of the trans block too, but this is checked at
|
||||
# a later state.
|
||||
plural_expr: nodes.Expr | None = None
|
||||
plural_expr_assignment: nodes.Assign | None = None
|
||||
plural_expr: t.Optional[nodes.Expr] = None
|
||||
plural_expr_assignment: t.Optional[nodes.Assign] = None
|
||||
num_called_num = False
|
||||
variables: dict[str, nodes.Expr] = {}
|
||||
variables: t.Dict[str, nodes.Expr] = {}
|
||||
trimmed = None
|
||||
while parser.stream.current.type != "block_end":
|
||||
if variables:
|
||||
@ -459,7 +479,7 @@ class InternationalizationExtension(Extension):
|
||||
|
||||
def _parse_block(
|
||||
self, parser: "Parser", allow_pluralize: bool
|
||||
) -> tuple[list[str], str]:
|
||||
) -> t.Tuple[t.List[str], str]:
|
||||
"""Parse until the next block tag with a given name."""
|
||||
referenced = []
|
||||
buf = []
|
||||
@ -507,10 +527,10 @@ class InternationalizationExtension(Extension):
|
||||
def _make_node(
|
||||
self,
|
||||
singular: str,
|
||||
plural: str | None,
|
||||
context: str | None,
|
||||
variables: dict[str, nodes.Expr],
|
||||
plural_expr: nodes.Expr | None,
|
||||
plural: t.Optional[str],
|
||||
context: t.Optional[str],
|
||||
variables: t.Dict[str, nodes.Expr],
|
||||
plural_expr: t.Optional[nodes.Expr],
|
||||
vars_referenced: bool,
|
||||
num_called_num: bool,
|
||||
) -> nodes.Output:
|
||||
@ -526,7 +546,7 @@ class InternationalizationExtension(Extension):
|
||||
plural = plural.replace("%%", "%")
|
||||
|
||||
func_name = "gettext"
|
||||
func_args: list[nodes.Expr] = [nodes.Const(singular)]
|
||||
func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
|
||||
|
||||
if context is not None:
|
||||
func_args.insert(0, nodes.Const(context))
|
||||
@ -585,7 +605,7 @@ class LoopControlExtension(Extension):
|
||||
|
||||
tags = {"break", "continue"}
|
||||
|
||||
def parse(self, parser: "Parser") -> nodes.Break | nodes.Continue:
|
||||
def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
|
||||
token = next(parser.stream)
|
||||
if token.value == "break":
|
||||
return nodes.Break(lineno=token.lineno)
|
||||
@ -636,7 +656,9 @@ def extract_from_ast(
|
||||
ast: nodes.Template,
|
||||
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
||||
babel_style: bool = True,
|
||||
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...]]]:
|
||||
) -> t.Iterator[
|
||||
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
||||
]:
|
||||
"""Extract localizable strings from the given template node. Per
|
||||
default this function returns matches in babel style that means non string
|
||||
parameters as well as keyword arguments are returned as `None`. This
|
||||
@ -671,7 +693,7 @@ def extract_from_ast(
|
||||
to extract any comments. For comment support you have to use the babel
|
||||
extraction interface or extract comments yourself.
|
||||
"""
|
||||
out: str | None | tuple[str | None, ...]
|
||||
out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]
|
||||
|
||||
for node in ast.find_all(nodes.Call):
|
||||
if (
|
||||
@ -680,7 +702,7 @@ def extract_from_ast(
|
||||
):
|
||||
continue
|
||||
|
||||
strings: list[str | None] = []
|
||||
strings: t.List[t.Optional[str]] = []
|
||||
|
||||
for arg in node.args:
|
||||
if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
|
||||
@ -717,14 +739,14 @@ class _CommentFinder:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, tokens: t.Sequence[tuple[int, str, str]], comment_tags: t.Sequence[str]
|
||||
self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
|
||||
) -> None:
|
||||
self.tokens = tokens
|
||||
self.comment_tags = comment_tags
|
||||
self.offset = 0
|
||||
self.last_lineno = 0
|
||||
|
||||
def find_backwards(self, offset: int) -> list[str]:
|
||||
def find_backwards(self, offset: int) -> t.List[str]:
|
||||
try:
|
||||
for _, token_type, token_value in reversed(
|
||||
self.tokens[self.offset : offset]
|
||||
@ -740,7 +762,7 @@ class _CommentFinder:
|
||||
finally:
|
||||
self.offset = offset
|
||||
|
||||
def find_comments(self, lineno: int) -> list[str]:
|
||||
def find_comments(self, lineno: int) -> t.List[str]:
|
||||
if not self.comment_tags or self.last_lineno > lineno:
|
||||
return []
|
||||
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
|
||||
@ -753,8 +775,12 @@ def babel_extract(
|
||||
fileobj: t.BinaryIO,
|
||||
keywords: t.Sequence[str],
|
||||
comment_tags: t.Sequence[str],
|
||||
options: dict[str, t.Any],
|
||||
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...], list[str]]]:
|
||||
options: t.Dict[str, t.Any],
|
||||
) -> t.Iterator[
|
||||
t.Tuple[
|
||||
int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
|
||||
]
|
||||
]:
|
||||
"""Babel extraction method for Jinja templates.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
@ -782,7 +808,7 @@ def babel_extract(
|
||||
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
|
||||
(comments will be empty currently)
|
||||
"""
|
||||
extensions: dict[type[Extension], None] = {}
|
||||
extensions: t.Dict[t.Type[Extension], None] = {}
|
||||
|
||||
for extension_name in options.get("extensions", "").split(","):
|
||||
extension_name = extension_name.strip()
|
||||
|
||||
@ -57,9 +57,9 @@ def ignore_case(value: V) -> V:
|
||||
|
||||
def make_attrgetter(
|
||||
environment: "Environment",
|
||||
attribute: str | int | None,
|
||||
postprocess: t.Callable[[t.Any], t.Any] | None = None,
|
||||
default: t.Any | None = None,
|
||||
attribute: t.Optional[t.Union[str, int]],
|
||||
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
|
||||
default: t.Optional[t.Any] = None,
|
||||
) -> t.Callable[[t.Any], t.Any]:
|
||||
"""Returns a callable that looks up the given attribute from a
|
||||
passed object with the rules of the environment. Dots are allowed
|
||||
@ -85,9 +85,9 @@ def make_attrgetter(
|
||||
|
||||
def make_multi_attrgetter(
|
||||
environment: "Environment",
|
||||
attribute: str | int | None,
|
||||
postprocess: t.Callable[[t.Any], t.Any] | None = None,
|
||||
) -> t.Callable[[t.Any], list[t.Any]]:
|
||||
attribute: t.Optional[t.Union[str, int]],
|
||||
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
|
||||
) -> t.Callable[[t.Any], t.List[t.Any]]:
|
||||
"""Returns a callable that looks up the given comma separated
|
||||
attributes from a passed object with the rules of the environment.
|
||||
Dots are allowed to access attributes of each attribute. Integer
|
||||
@ -99,13 +99,13 @@ def make_multi_attrgetter(
|
||||
Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
|
||||
"""
|
||||
if isinstance(attribute, str):
|
||||
split: t.Sequence[str | int | None] = attribute.split(",")
|
||||
split: t.Sequence[t.Union[str, int, None]] = attribute.split(",")
|
||||
else:
|
||||
split = [attribute]
|
||||
|
||||
parts = [_prepare_attribute_parts(item) for item in split]
|
||||
|
||||
def attrgetter(item: t.Any) -> list[t.Any]:
|
||||
def attrgetter(item: t.Any) -> t.List[t.Any]:
|
||||
items = [None] * len(parts)
|
||||
|
||||
for i, attribute_part in enumerate(parts):
|
||||
@ -125,8 +125,8 @@ def make_multi_attrgetter(
|
||||
|
||||
|
||||
def _prepare_attribute_parts(
|
||||
attr: str | int | None,
|
||||
) -> list[str | int]:
|
||||
attr: t.Optional[t.Union[str, int]],
|
||||
) -> t.List[t.Union[str, int]]:
|
||||
if attr is None:
|
||||
return []
|
||||
|
||||
@ -136,7 +136,7 @@ def _prepare_attribute_parts(
|
||||
return [attr]
|
||||
|
||||
|
||||
def do_forceescape(value: "str | HasHTML") -> Markup:
|
||||
def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup:
|
||||
"""Enforce HTML escaping. This will probably double escape variables."""
|
||||
if hasattr(value, "__html__"):
|
||||
value = t.cast("HasHTML", value).__html__()
|
||||
@ -145,7 +145,7 @@ def do_forceescape(value: "str | HasHTML") -> Markup:
|
||||
|
||||
|
||||
def do_urlencode(
|
||||
value: str | t.Mapping[str, t.Any] | t.Iterable[tuple[str, t.Any]],
|
||||
value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]],
|
||||
) -> str:
|
||||
"""Quote data for use in a URL path or query using UTF-8.
|
||||
|
||||
@ -166,7 +166,7 @@ def do_urlencode(
|
||||
return url_quote(value)
|
||||
|
||||
if isinstance(value, dict):
|
||||
items: t.Iterable[tuple[str, t.Any]] = value.items()
|
||||
items: t.Iterable[t.Tuple[str, t.Any]] = value.items()
|
||||
else:
|
||||
items = value # type: ignore
|
||||
|
||||
@ -177,7 +177,7 @@ def do_urlencode(
|
||||
|
||||
@pass_eval_context
|
||||
def do_replace(
|
||||
eval_ctx: "EvalContext", s: str, old: str, new: str, count: int | None = None
|
||||
eval_ctx: "EvalContext", s: str, old: str, new: str, count: t.Optional[int] = None
|
||||
) -> str:
|
||||
"""Return a copy of the value with all occurrences of a substring
|
||||
replaced with a new one. The first argument is the substring
|
||||
@ -221,7 +221,7 @@ def do_lower(s: str) -> str:
|
||||
return soft_str(s).lower()
|
||||
|
||||
|
||||
def do_items(value: t.Mapping[K, V] | Undefined) -> t.Iterator[tuple[K, V]]:
|
||||
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
|
||||
@ -346,7 +346,7 @@ def do_dictsort(
|
||||
case_sensitive: bool = False,
|
||||
by: 'te.Literal["key", "value"]' = "key",
|
||||
reverse: bool = False,
|
||||
) -> list[tuple[K, V]]:
|
||||
) -> t.List[t.Tuple[K, V]]:
|
||||
"""Sort a dict and yield (key, value) pairs. Python dicts may not
|
||||
be in the order you want to display them in, so sort them first.
|
||||
|
||||
@ -371,7 +371,7 @@ def do_dictsort(
|
||||
else:
|
||||
raise FilterArgumentError('You can only sort by either "key" or "value"')
|
||||
|
||||
def sort_func(item: tuple[t.Any, t.Any]) -> t.Any:
|
||||
def sort_func(item: t.Tuple[t.Any, t.Any]) -> t.Any:
|
||||
value = item[pos]
|
||||
|
||||
if not case_sensitive:
|
||||
@ -388,8 +388,8 @@ def do_sort(
|
||||
value: "t.Iterable[V]",
|
||||
reverse: bool = False,
|
||||
case_sensitive: bool = False,
|
||||
attribute: str | int | None = None,
|
||||
) -> "list[V]":
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> "t.List[V]":
|
||||
"""Sort an iterable using Python's :func:`sorted`.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
@ -443,7 +443,7 @@ def sync_do_unique(
|
||||
environment: "Environment",
|
||||
value: "t.Iterable[V]",
|
||||
case_sensitive: bool = False,
|
||||
attribute: str | int | None = None,
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> "t.Iterator[V]":
|
||||
"""Returns a list of unique items from the given iterable.
|
||||
|
||||
@ -474,9 +474,9 @@ def sync_do_unique(
|
||||
@async_variant(sync_do_unique) # type: ignore
|
||||
async def do_unique(
|
||||
environment: "Environment",
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
case_sensitive: bool = False,
|
||||
attribute: str | int | None = None,
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> "t.Iterator[V]":
|
||||
return sync_do_unique(
|
||||
environment, await auto_to_list(value), case_sensitive, attribute
|
||||
@ -488,8 +488,8 @@ def _min_or_max(
|
||||
value: "t.Iterable[V]",
|
||||
func: "t.Callable[..., V]",
|
||||
case_sensitive: bool,
|
||||
attribute: str | int | None,
|
||||
) -> "V | Undefined":
|
||||
attribute: t.Optional[t.Union[str, int]],
|
||||
) -> "t.Union[V, Undefined]":
|
||||
it = iter(value)
|
||||
|
||||
try:
|
||||
@ -508,8 +508,8 @@ def do_min(
|
||||
environment: "Environment",
|
||||
value: "t.Iterable[V]",
|
||||
case_sensitive: bool = False,
|
||||
attribute: str | int | None = None,
|
||||
) -> "V | Undefined":
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> "t.Union[V, Undefined]":
|
||||
"""Return the smallest item from the sequence.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
@ -528,8 +528,8 @@ def do_max(
|
||||
environment: "Environment",
|
||||
value: "t.Iterable[V]",
|
||||
case_sensitive: bool = False,
|
||||
attribute: str | int | None = None,
|
||||
) -> "V | Undefined":
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> "t.Union[V, Undefined]":
|
||||
"""Return the largest item from the sequence.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
@ -581,7 +581,7 @@ def sync_do_join(
|
||||
eval_ctx: "EvalContext",
|
||||
value: t.Iterable[t.Any],
|
||||
d: str = "",
|
||||
attribute: str | int | None = None,
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> str:
|
||||
"""Return a string which is the concatenation of the strings in the
|
||||
sequence. The separator between elements is an empty string per
|
||||
@ -637,9 +637,9 @@ def sync_do_join(
|
||||
@async_variant(sync_do_join) # type: ignore
|
||||
async def do_join(
|
||||
eval_ctx: "EvalContext",
|
||||
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
d: str = "",
|
||||
attribute: str | int | None = None,
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> str:
|
||||
return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute)
|
||||
|
||||
@ -650,7 +650,9 @@ def do_center(value: str, width: int = 80) -> str:
|
||||
|
||||
|
||||
@pass_environment
|
||||
def sync_do_first(environment: "Environment", seq: "t.Iterable[V]") -> "V | Undefined":
|
||||
def sync_do_first(
|
||||
environment: "Environment", seq: "t.Iterable[V]"
|
||||
) -> "t.Union[V, Undefined]":
|
||||
"""Return the first item of a sequence."""
|
||||
try:
|
||||
return next(iter(seq))
|
||||
@ -660,8 +662,8 @@ def sync_do_first(environment: "Environment", seq: "t.Iterable[V]") -> "V | Unde
|
||||
|
||||
@async_variant(sync_do_first) # type: ignore
|
||||
async def do_first(
|
||||
environment: "Environment", seq: "t.AsyncIterable[V] | t.Iterable[V]"
|
||||
) -> "V | Undefined":
|
||||
environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]"
|
||||
) -> "t.Union[V, Undefined]":
|
||||
try:
|
||||
return await auto_aiter(seq).__anext__()
|
||||
except StopAsyncIteration:
|
||||
@ -669,7 +671,9 @@ async def do_first(
|
||||
|
||||
|
||||
@pass_environment
|
||||
def do_last(environment: "Environment", seq: "t.Reversible[V]") -> "V | Undefined":
|
||||
def do_last(
|
||||
environment: "Environment", seq: "t.Reversible[V]"
|
||||
) -> "t.Union[V, Undefined]":
|
||||
"""Return the last item of a sequence.
|
||||
|
||||
Note: Does not work with generators. You may want to explicitly
|
||||
@ -689,7 +693,7 @@ def do_last(environment: "Environment", seq: "t.Reversible[V]") -> "V | Undefine
|
||||
|
||||
|
||||
@pass_context
|
||||
def do_random(context: "Context", seq: "t.Sequence[V]") -> "V | Undefined":
|
||||
def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]":
|
||||
"""Return a random item from the sequence."""
|
||||
try:
|
||||
return random.choice(seq)
|
||||
@ -697,7 +701,7 @@ def do_random(context: "Context", seq: "t.Sequence[V]") -> "V | Undefined":
|
||||
return context.environment.undefined("No random item, sequence was empty.")
|
||||
|
||||
|
||||
def do_filesizeformat(value: str | float | int, binary: bool = False) -> str:
|
||||
def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str:
|
||||
"""Format the value like a 'human-readable' file size (i.e. 13 kB,
|
||||
4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
|
||||
Giga, etc.), if the second parameter is set to `True` the binary
|
||||
@ -742,11 +746,11 @@ _uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$")
|
||||
def do_urlize(
|
||||
eval_ctx: "EvalContext",
|
||||
value: str,
|
||||
trim_url_limit: int | None = None,
|
||||
trim_url_limit: t.Optional[int] = None,
|
||||
nofollow: bool = False,
|
||||
target: str | None = None,
|
||||
rel: str | None = None,
|
||||
extra_schemes: t.Iterable[str] | None = None,
|
||||
target: t.Optional[str] = None,
|
||||
rel: t.Optional[str] = None,
|
||||
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
||||
) -> str:
|
||||
"""Convert URLs in text into clickable links.
|
||||
|
||||
@ -819,7 +823,7 @@ def do_urlize(
|
||||
|
||||
|
||||
def do_indent(
|
||||
s: str, width: int | str = 4, first: bool = False, blank: bool = False
|
||||
s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False
|
||||
) -> str:
|
||||
"""Return a copy of the string with each line indented by 4 spaces. The
|
||||
first line and blank lines are not indented by default.
|
||||
@ -873,7 +877,7 @@ def do_truncate(
|
||||
length: int = 255,
|
||||
killwords: bool = False,
|
||||
end: str = "...",
|
||||
leeway: int | None = None,
|
||||
leeway: t.Optional[int] = None,
|
||||
) -> str:
|
||||
"""Return a truncated copy of the string. The length is specified
|
||||
with the first parameter which defaults to ``255``. If the second
|
||||
@ -920,7 +924,7 @@ def do_wordwrap(
|
||||
s: str,
|
||||
width: int = 79,
|
||||
break_long_words: bool = True,
|
||||
wrapstring: str | None = None,
|
||||
wrapstring: t.Optional[str] = None,
|
||||
break_on_hyphens: bool = True,
|
||||
) -> str:
|
||||
"""Wrap a string to the given width. Existing newlines are treated
|
||||
@ -1039,12 +1043,12 @@ def do_format(value: str, *args: t.Any, **kwargs: t.Any) -> str:
|
||||
return soft_str(value) % (kwargs or args)
|
||||
|
||||
|
||||
def do_trim(value: str, chars: str | None = None) -> str:
|
||||
def do_trim(value: str, chars: t.Optional[str] = None) -> str:
|
||||
"""Strip leading and trailing characters, by default whitespace."""
|
||||
return soft_str(value).strip(chars)
|
||||
|
||||
|
||||
def do_striptags(value: "str | HasHTML") -> str:
|
||||
def do_striptags(value: "t.Union[str, HasHTML]") -> str:
|
||||
"""Strip SGML/XML tags and replace adjacent whitespace by one space."""
|
||||
if hasattr(value, "__html__"):
|
||||
value = t.cast("HasHTML", value).__html__()
|
||||
@ -1053,8 +1057,8 @@ def do_striptags(value: "str | HasHTML") -> str:
|
||||
|
||||
|
||||
def sync_do_slice(
|
||||
value: "t.Collection[V]", slices: int, fill_with: "V | None" = None
|
||||
) -> "t.Iterator[list[V]]":
|
||||
value: "t.Collection[V]", slices: int, fill_with: "t.Optional[V]" = None
|
||||
) -> "t.Iterator[t.List[V]]":
|
||||
"""Slice an iterator and return a list of lists containing
|
||||
those items. Useful if you want to create a div containing
|
||||
three ul tags that represent columns:
|
||||
@ -1097,16 +1101,16 @@ def sync_do_slice(
|
||||
|
||||
@async_variant(sync_do_slice) # type: ignore
|
||||
async def do_slice(
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
slices: int,
|
||||
fill_with: t.Any | None = None,
|
||||
) -> "t.Iterator[list[V]]":
|
||||
fill_with: t.Optional[t.Any] = None,
|
||||
) -> "t.Iterator[t.List[V]]":
|
||||
return sync_do_slice(await auto_to_list(value), slices, fill_with)
|
||||
|
||||
|
||||
def do_batch(
|
||||
value: "t.Iterable[V]", linecount: int, fill_with: "V | None" = None
|
||||
) -> "t.Iterator[list[V]]":
|
||||
value: "t.Iterable[V]", linecount: int, fill_with: "t.Optional[V]" = None
|
||||
) -> "t.Iterator[t.List[V]]":
|
||||
"""
|
||||
A filter that batches items. It works pretty much like `slice`
|
||||
just the other way round. It returns a list of lists with the
|
||||
@ -1125,7 +1129,7 @@ def do_batch(
|
||||
{%- endfor %}
|
||||
</table>
|
||||
"""
|
||||
tmp: list[V] = []
|
||||
tmp: t.List[V] = []
|
||||
|
||||
for item in value:
|
||||
if len(tmp) == linecount:
|
||||
@ -1183,7 +1187,7 @@ def do_round(
|
||||
|
||||
class _GroupTuple(t.NamedTuple):
|
||||
grouper: t.Any
|
||||
list: list[t.Any]
|
||||
list: t.List[t.Any]
|
||||
|
||||
# Use the regular tuple repr to hide this subclass if users print
|
||||
# out the value during debugging.
|
||||
@ -1198,10 +1202,10 @@ class _GroupTuple(t.NamedTuple):
|
||||
def sync_do_groupby(
|
||||
environment: "Environment",
|
||||
value: "t.Iterable[V]",
|
||||
attribute: str | int,
|
||||
default: t.Any | None = None,
|
||||
attribute: t.Union[str, int],
|
||||
default: t.Optional[t.Any] = None,
|
||||
case_sensitive: bool = False,
|
||||
) -> "list[_GroupTuple]":
|
||||
) -> "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``,
|
||||
@ -1281,11 +1285,11 @@ def sync_do_groupby(
|
||||
@async_variant(sync_do_groupby) # type: ignore
|
||||
async def do_groupby(
|
||||
environment: "Environment",
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
attribute: str | int,
|
||||
default: t.Any | None = None,
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
attribute: t.Union[str, int],
|
||||
default: t.Optional[t.Any] = None,
|
||||
case_sensitive: bool = False,
|
||||
) -> "list[_GroupTuple]":
|
||||
) -> "t.List[_GroupTuple]":
|
||||
expr = make_attrgetter(
|
||||
environment,
|
||||
attribute,
|
||||
@ -1309,7 +1313,7 @@ async def do_groupby(
|
||||
def sync_do_sum(
|
||||
environment: "Environment",
|
||||
iterable: "t.Iterable[V]",
|
||||
attribute: str | int | None = None,
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
start: V = 0, # type: ignore
|
||||
) -> V:
|
||||
"""Returns the sum of a sequence of numbers plus the value of parameter
|
||||
@ -1335,8 +1339,8 @@ def sync_do_sum(
|
||||
@async_variant(sync_do_sum) # type: ignore
|
||||
async def do_sum(
|
||||
environment: "Environment",
|
||||
iterable: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
attribute: str | int | None = None,
|
||||
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
start: V = 0, # type: ignore
|
||||
) -> V:
|
||||
rv = start
|
||||
@ -1354,7 +1358,7 @@ async def do_sum(
|
||||
return rv
|
||||
|
||||
|
||||
def sync_do_list(value: "t.Iterable[V]") -> "list[V]":
|
||||
def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]":
|
||||
"""Convert the value into a list. If it was a string the returned list
|
||||
will be a list of characters.
|
||||
"""
|
||||
@ -1362,7 +1366,7 @@ def sync_do_list(value: "t.Iterable[V]") -> "list[V]":
|
||||
|
||||
|
||||
@async_variant(sync_do_list) # type: ignore
|
||||
async def do_list(value: "t.AsyncIterable[V] | t.Iterable[V]") -> "list[V]":
|
||||
async def do_list(value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]") -> "t.List[V]":
|
||||
return await auto_to_list(value)
|
||||
|
||||
|
||||
@ -1386,7 +1390,7 @@ def do_reverse(value: str) -> str: ...
|
||||
def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]": ...
|
||||
|
||||
|
||||
def do_reverse(value: str | t.Iterable[V]) -> str | t.Iterable[V]:
|
||||
def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]]:
|
||||
"""Reverse the object or return an iterator that iterates over it the other
|
||||
way round.
|
||||
"""
|
||||
@ -1405,7 +1409,9 @@ def do_reverse(value: str | t.Iterable[V]) -> str | t.Iterable[V]:
|
||||
|
||||
|
||||
@pass_environment
|
||||
def do_attr(environment: "Environment", obj: t.Any, name: str) -> Undefined | t.Any:
|
||||
def do_attr(
|
||||
environment: "Environment", obj: t.Any, name: str
|
||||
) -> t.Union[Undefined, t.Any]:
|
||||
"""Get an attribute of an object. ``foo|attr("bar")`` works like
|
||||
``foo.bar``, but returns undefined instead of falling back to ``foo["bar"]``
|
||||
if the attribute doesn't exist.
|
||||
@ -1443,7 +1449,7 @@ def sync_do_map(
|
||||
value: t.Iterable[t.Any],
|
||||
*,
|
||||
attribute: str = ...,
|
||||
default: t.Any | None = None,
|
||||
default: t.Optional[t.Any] = None,
|
||||
) -> t.Iterable[t.Any]: ...
|
||||
|
||||
|
||||
@ -1500,7 +1506,7 @@ def sync_do_map(
|
||||
@typing.overload
|
||||
def do_map(
|
||||
context: "Context",
|
||||
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
name: str,
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
@ -1510,17 +1516,17 @@ def do_map(
|
||||
@typing.overload
|
||||
def do_map(
|
||||
context: "Context",
|
||||
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
*,
|
||||
attribute: str = ...,
|
||||
default: t.Any | None = None,
|
||||
default: t.Optional[t.Any] = None,
|
||||
) -> t.Iterable[t.Any]: ...
|
||||
|
||||
|
||||
@async_variant(sync_do_map) # type: ignore
|
||||
async def do_map(
|
||||
context: "Context",
|
||||
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> t.AsyncIterable[t.Any]:
|
||||
@ -1565,7 +1571,7 @@ def sync_do_select(
|
||||
@async_variant(sync_do_select) # type: ignore
|
||||
async def do_select(
|
||||
context: "Context",
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> "t.AsyncIterator[V]":
|
||||
@ -1601,7 +1607,7 @@ def sync_do_reject(
|
||||
@async_variant(sync_do_reject) # type: ignore
|
||||
async def do_reject(
|
||||
context: "Context",
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> "t.AsyncIterator[V]":
|
||||
@ -1641,7 +1647,7 @@ def sync_do_selectattr(
|
||||
@async_variant(sync_do_selectattr) # type: ignore
|
||||
async def do_selectattr(
|
||||
context: "Context",
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> "t.AsyncIterator[V]":
|
||||
@ -1679,7 +1685,7 @@ def sync_do_rejectattr(
|
||||
@async_variant(sync_do_rejectattr) # type: ignore
|
||||
async def do_rejectattr(
|
||||
context: "Context",
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> "t.AsyncIterator[V]":
|
||||
@ -1688,7 +1694,7 @@ async def do_rejectattr(
|
||||
|
||||
@pass_eval_context
|
||||
def do_tojson(
|
||||
eval_ctx: "EvalContext", value: t.Any, indent: int | None = None
|
||||
eval_ctx: "EvalContext", value: t.Any, indent: t.Optional[int] = None
|
||||
) -> Markup:
|
||||
"""Serialize an object to a string of JSON, and mark it safe to
|
||||
render in HTML. This filter is only for use in HTML documents.
|
||||
@ -1716,7 +1722,7 @@ def do_tojson(
|
||||
|
||||
|
||||
def prepare_map(
|
||||
context: "Context", args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
|
||||
context: "Context", args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any]
|
||||
) -> t.Callable[[t.Any], t.Any]:
|
||||
if not args and "attribute" in kwargs:
|
||||
attribute = kwargs.pop("attribute")
|
||||
@ -1745,8 +1751,8 @@ def prepare_map(
|
||||
|
||||
def prepare_select_or_reject(
|
||||
context: "Context",
|
||||
args: tuple[t.Any, ...],
|
||||
kwargs: dict[str, t.Any],
|
||||
args: t.Tuple[t.Any, ...],
|
||||
kwargs: t.Dict[str, t.Any],
|
||||
modfunc: t.Callable[[t.Any], t.Any],
|
||||
lookup_attr: bool,
|
||||
) -> t.Callable[[t.Any], t.Any]:
|
||||
@ -1780,8 +1786,8 @@ def prepare_select_or_reject(
|
||||
def select_or_reject(
|
||||
context: "Context",
|
||||
value: "t.Iterable[V]",
|
||||
args: tuple[t.Any, ...],
|
||||
kwargs: dict[str, t.Any],
|
||||
args: t.Tuple[t.Any, ...],
|
||||
kwargs: t.Dict[str, t.Any],
|
||||
modfunc: t.Callable[[t.Any], t.Any],
|
||||
lookup_attr: bool,
|
||||
) -> "t.Iterator[V]":
|
||||
@ -1795,9 +1801,9 @@ def select_or_reject(
|
||||
|
||||
async def async_select_or_reject(
|
||||
context: "Context",
|
||||
value: "t.AsyncIterable[V] | t.Iterable[V]",
|
||||
args: tuple[t.Any, ...],
|
||||
kwargs: dict[str, t.Any],
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
args: t.Tuple[t.Any, ...],
|
||||
kwargs: t.Dict[str, t.Any],
|
||||
modfunc: t.Callable[[t.Any], t.Any],
|
||||
lookup_attr: bool,
|
||||
) -> "t.AsyncIterator[V]":
|
||||
|
||||
@ -32,7 +32,7 @@ def symbols_for_node(
|
||||
|
||||
class Symbols:
|
||||
def __init__(
|
||||
self, parent: t.Optional["Symbols"] = None, level: int | None = None
|
||||
self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
|
||||
) -> None:
|
||||
if level is None:
|
||||
if parent is None:
|
||||
@ -42,22 +42,24 @@ class Symbols:
|
||||
|
||||
self.level: int = level
|
||||
self.parent = parent
|
||||
self.refs: dict[str, str] = {}
|
||||
self.loads: dict[str, t.Any] = {}
|
||||
self.stores: set[str] = set()
|
||||
self.refs: t.Dict[str, str] = {}
|
||||
self.loads: t.Dict[str, t.Any] = {}
|
||||
self.stores: t.Set[str] = set()
|
||||
|
||||
def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
|
||||
visitor = RootVisitor(self)
|
||||
visitor.visit(node, **kwargs)
|
||||
|
||||
def _define_ref(self, name: str, load: tuple[str, str | None] | None = None) -> str:
|
||||
def _define_ref(
|
||||
self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None
|
||||
) -> str:
|
||||
ident = f"l_{self.level}_{name}"
|
||||
self.refs[name] = ident
|
||||
if load is not None:
|
||||
self.loads[ident] = load
|
||||
return ident
|
||||
|
||||
def find_load(self, target: str) -> t.Any | None:
|
||||
def find_load(self, target: str) -> t.Optional[t.Any]:
|
||||
if target in self.loads:
|
||||
return self.loads[target]
|
||||
|
||||
@ -66,7 +68,7 @@ class Symbols:
|
||||
|
||||
return None
|
||||
|
||||
def find_ref(self, name: str) -> str | None:
|
||||
def find_ref(self, name: str) -> t.Optional[str]:
|
||||
if name in self.refs:
|
||||
return self.refs[name]
|
||||
|
||||
@ -119,7 +121,7 @@ class Symbols:
|
||||
self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
|
||||
|
||||
def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
|
||||
stores: set[str] = set()
|
||||
stores: t.Set[str] = set()
|
||||
|
||||
for branch in branch_symbols:
|
||||
stores.update(branch.stores)
|
||||
@ -142,9 +144,9 @@ class Symbols:
|
||||
continue
|
||||
self.loads[target] = (VAR_LOAD_RESOLVE, name)
|
||||
|
||||
def dump_stores(self) -> dict[str, str]:
|
||||
rv: dict[str, str] = {}
|
||||
node: Symbols | None = self
|
||||
def dump_stores(self) -> t.Dict[str, str]:
|
||||
rv: t.Dict[str, str] = {}
|
||||
node: t.Optional[Symbols] = self
|
||||
|
||||
while node is not None:
|
||||
for name in sorted(node.stores):
|
||||
@ -155,9 +157,9 @@ class Symbols:
|
||||
|
||||
return rv
|
||||
|
||||
def dump_param_targets(self) -> set[str]:
|
||||
def dump_param_targets(self) -> t.Set[str]:
|
||||
rv = set()
|
||||
node: Symbols | None = self
|
||||
node: t.Optional[Symbols] = self
|
||||
|
||||
while node is not None:
|
||||
for target, (instr, _) in self.loads.items():
|
||||
|
||||
@ -21,7 +21,7 @@ if t.TYPE_CHECKING:
|
||||
|
||||
# cache for the lexers. Exists in order to be able to have multiple
|
||||
# environments with the same lexer
|
||||
_lexer_cache: t.MutableMapping[tuple, "Lexer"] = LRUCache(50) # type: ignore
|
||||
_lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore
|
||||
|
||||
# static regular expressions
|
||||
whitespace_re = re.compile(r"\s+")
|
||||
@ -210,7 +210,7 @@ def count_newlines(value: str) -> int:
|
||||
return len(newline_re.findall(value))
|
||||
|
||||
|
||||
def compile_rules(environment: "Environment") -> list[tuple[str, str]]:
|
||||
def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]:
|
||||
"""Compiles all the rules from the environment into a list of rules."""
|
||||
e = re.escape
|
||||
rules = [
|
||||
@ -257,12 +257,12 @@ class Failure:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, message: str, cls: type[TemplateSyntaxError] = TemplateSyntaxError
|
||||
self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError
|
||||
) -> None:
|
||||
self.message = message
|
||||
self.error_class = cls
|
||||
|
||||
def __call__(self, lineno: int, filename: str | None) -> "te.NoReturn":
|
||||
def __call__(self, lineno: int, filename: t.Optional[str]) -> "te.NoReturn":
|
||||
raise self.error_class(self.message, lineno, filename)
|
||||
|
||||
|
||||
@ -325,11 +325,11 @@ class TokenStream:
|
||||
def __init__(
|
||||
self,
|
||||
generator: t.Iterable[Token],
|
||||
name: str | None,
|
||||
filename: str | None,
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str],
|
||||
):
|
||||
self._iter = iter(generator)
|
||||
self._pushed: deque[Token] = deque()
|
||||
self._pushed: te.Deque[Token] = deque()
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.closed = False
|
||||
@ -364,7 +364,7 @@ class TokenStream:
|
||||
for _ in range(n):
|
||||
next(self)
|
||||
|
||||
def next_if(self, expr: str) -> Token | None:
|
||||
def next_if(self, expr: str) -> t.Optional[Token]:
|
||||
"""Perform the token test and return the token if it matched.
|
||||
Otherwise the return value is `None`.
|
||||
"""
|
||||
@ -464,8 +464,8 @@ class OptionalLStrip(tuple): # type: ignore[type-arg]
|
||||
|
||||
class _Rule(t.NamedTuple):
|
||||
pattern: t.Pattern[str]
|
||||
tokens: str | tuple[str, ...] | tuple[Failure]
|
||||
command: str | None
|
||||
tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]]
|
||||
command: t.Optional[str]
|
||||
|
||||
|
||||
class Lexer:
|
||||
@ -484,7 +484,7 @@ class Lexer:
|
||||
return re.compile(x, re.M | re.S)
|
||||
|
||||
# lexing rules for tags
|
||||
tag_rules: list[_Rule] = [
|
||||
tag_rules: t.List[_Rule] = [
|
||||
_Rule(whitespace_re, TOKEN_WHITESPACE, None),
|
||||
_Rule(float_re, TOKEN_FLOAT, None),
|
||||
_Rule(integer_re, TOKEN_INTEGER, None),
|
||||
@ -523,7 +523,7 @@ class Lexer:
|
||||
)
|
||||
|
||||
# global lexing rules
|
||||
self.rules: dict[str, list[_Rule]] = {
|
||||
self.rules: t.Dict[str, t.List[_Rule]] = {
|
||||
"root": [
|
||||
# directives
|
||||
_Rule(
|
||||
@ -604,9 +604,9 @@ class Lexer:
|
||||
def tokenize(
|
||||
self,
|
||||
source: str,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
state: str | None = None,
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
state: t.Optional[str] = None,
|
||||
) -> TokenStream:
|
||||
"""Calls tokeniter + tokenize and wraps it in a token stream."""
|
||||
stream = self.tokeniter(source, name, filename, state)
|
||||
@ -614,9 +614,9 @@ class Lexer:
|
||||
|
||||
def wrap(
|
||||
self,
|
||||
stream: t.Iterable[tuple[int, str, str]],
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
stream: t.Iterable[t.Tuple[int, str, str]],
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
) -> t.Iterator[Token]:
|
||||
"""This is called with the stream as returned by `tokenize` and wraps
|
||||
every token in a :class:`Token` and converts the value.
|
||||
@ -669,10 +669,10 @@ class Lexer:
|
||||
def tokeniter(
|
||||
self,
|
||||
source: str,
|
||||
name: str | None,
|
||||
filename: str | None = None,
|
||||
state: str | None = None,
|
||||
) -> t.Iterator[tuple[int, str, str]]:
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str] = None,
|
||||
state: t.Optional[str] = None,
|
||||
) -> t.Iterator[t.Tuple[int, str, str]]:
|
||||
"""This method tokenizes the text and returns the tokens in a
|
||||
generator. Use this method if you just want to tokenize a template.
|
||||
|
||||
@ -696,7 +696,7 @@ class Lexer:
|
||||
|
||||
statetokens = self.rules[stack[-1]]
|
||||
source_length = len(source)
|
||||
balancing_stack: list[str] = []
|
||||
balancing_stack: t.List[str] = []
|
||||
newlines_stripped = 0
|
||||
line_starting = True
|
||||
|
||||
|
||||
@ -22,14 +22,14 @@ if t.TYPE_CHECKING:
|
||||
from .environment import Template
|
||||
|
||||
|
||||
def split_template_path(template: str) -> list[str]:
|
||||
def split_template_path(template: str) -> t.List[str]:
|
||||
"""Split a path into segments and perform a sanity check. If it detects
|
||||
'..' in the path it will raise a `TemplateNotFound` error.
|
||||
"""
|
||||
pieces = []
|
||||
for piece in template.split("/"):
|
||||
if (
|
||||
os.sep in piece
|
||||
os.path.sep in piece
|
||||
or (os.path.altsep and os.path.altsep in piece)
|
||||
or piece == os.path.pardir
|
||||
):
|
||||
@ -74,7 +74,7 @@ class BaseLoader:
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
"""Get the template source, filename and reload helper for a template.
|
||||
It's passed the environment and template name and has to return a
|
||||
tuple in the form ``(source, filename, uptodate)`` or raise a
|
||||
@ -98,7 +98,7 @@ class BaseLoader:
|
||||
)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self) -> list[str]:
|
||||
def list_templates(self) -> t.List[str]:
|
||||
"""Iterates over all templates. If the loader does not support that
|
||||
it should raise a :exc:`TypeError` which is the default behavior.
|
||||
"""
|
||||
@ -109,7 +109,7 @@ class BaseLoader:
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
"""Loads a template. This method looks up the template in the cache
|
||||
or loads one by calling :meth:`get_source`. Subclasses should not
|
||||
@ -193,7 +193,7 @@ class FileSystemLoader(BaseLoader):
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> tuple[str, str, t.Callable[[], bool]]:
|
||||
) -> t.Tuple[str, str, t.Callable[[], bool]]:
|
||||
pieces = split_template_path(template)
|
||||
|
||||
for searchpath in self.searchpath:
|
||||
@ -225,7 +225,7 @@ class FileSystemLoader(BaseLoader):
|
||||
# Use normpath to convert Windows altsep to sep.
|
||||
return contents, os.path.normpath(filename), uptodate
|
||||
|
||||
def list_templates(self) -> list[str]:
|
||||
def list_templates(self) -> t.List[str]:
|
||||
found = set()
|
||||
for searchpath in self.searchpath:
|
||||
walk_dir = os.walk(searchpath, followlinks=self.followlinks)
|
||||
@ -233,8 +233,8 @@ class FileSystemLoader(BaseLoader):
|
||||
for filename in filenames:
|
||||
template = (
|
||||
os.path.join(dirpath, filename)[len(searchpath) :]
|
||||
.strip(os.sep)
|
||||
.replace(os.sep, "/")
|
||||
.strip(os.path.sep)
|
||||
.replace(os.path.sep, "/")
|
||||
)
|
||||
if template[:2] == "./":
|
||||
template = template[2:]
|
||||
@ -245,23 +245,24 @@ class FileSystemLoader(BaseLoader):
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
|
||||
def _get_zipimporter_files(z: t.Any) -> dict[str, object]:
|
||||
def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
|
||||
try:
|
||||
get_files = z._get_files
|
||||
except AttributeError as e:
|
||||
raise TypeError(
|
||||
"This zip import does not have the required metadata to list templates."
|
||||
"This zip import does not have the required"
|
||||
" metadata to list templates."
|
||||
) from e
|
||||
return get_files()
|
||||
|
||||
else:
|
||||
|
||||
def _get_zipimporter_files(z: t.Any) -> dict[str, object]:
|
||||
def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
|
||||
try:
|
||||
files = z._files
|
||||
except AttributeError as e:
|
||||
raise TypeError(
|
||||
"This zip import does not have the required metadata to list templates."
|
||||
"This zip import does not have the required"
|
||||
" metadata to list templates."
|
||||
) from e
|
||||
return files # type: ignore[no-any-return]
|
||||
|
||||
@ -305,12 +306,12 @@ class PackageLoader(BaseLoader):
|
||||
package_path: "str" = "templates",
|
||||
encoding: str = "utf-8",
|
||||
) -> None:
|
||||
package_path = os.path.normpath(package_path).rstrip(os.sep)
|
||||
package_path = os.path.normpath(package_path).rstrip(os.path.sep)
|
||||
|
||||
# normpath preserves ".", which isn't valid in zip paths.
|
||||
if package_path == os.path.curdir:
|
||||
package_path = ""
|
||||
elif package_path[:2] == os.path.curdir + os.sep:
|
||||
elif package_path[:2] == os.path.curdir + os.path.sep:
|
||||
package_path = package_path[2:]
|
||||
|
||||
self.package_path = package_path
|
||||
@ -330,9 +331,9 @@ class PackageLoader(BaseLoader):
|
||||
if isinstance(loader, zipimport.zipimporter):
|
||||
self._archive = loader.archive
|
||||
pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore
|
||||
template_root = os.path.join(pkgdir, package_path).rstrip(os.sep)
|
||||
template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
|
||||
else:
|
||||
roots: list[str] = []
|
||||
roots: t.List[str] = []
|
||||
|
||||
# One element for regular packages, multiple for namespace
|
||||
# packages, or None for single module file.
|
||||
@ -364,14 +365,14 @@ class PackageLoader(BaseLoader):
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> tuple[str, str, t.Callable[[], bool] | None]:
|
||||
) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
|
||||
# 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.Callable[[], bool] | None
|
||||
up_to_date: t.Optional[t.Callable[[], bool]]
|
||||
|
||||
if self._archive is None:
|
||||
# Package is a directory.
|
||||
@ -400,30 +401,33 @@ class PackageLoader(BaseLoader):
|
||||
|
||||
return source.decode(self.encoding), p, up_to_date
|
||||
|
||||
def list_templates(self) -> list[str]:
|
||||
results: list[str] = []
|
||||
def list_templates(self) -> t.List[str]:
|
||||
results: t.List[str] = []
|
||||
|
||||
if self._archive is None:
|
||||
# Package is a directory.
|
||||
offset = len(self._template_root)
|
||||
|
||||
for dirpath, _, filenames in os.walk(self._template_root):
|
||||
dirpath = dirpath[offset:].lstrip(os.sep)
|
||||
dirpath = dirpath[offset:].lstrip(os.path.sep)
|
||||
results.extend(
|
||||
os.path.join(dirpath, name).replace(os.sep, "/")
|
||||
os.path.join(dirpath, name).replace(os.path.sep, "/")
|
||||
for name in filenames
|
||||
)
|
||||
else:
|
||||
files = _get_zipimporter_files(self._loader)
|
||||
|
||||
# Package is a zip file.
|
||||
prefix = self._template_root[len(self._archive) :].lstrip(os.sep) + os.sep
|
||||
prefix = (
|
||||
self._template_root[len(self._archive) :].lstrip(os.path.sep)
|
||||
+ os.path.sep
|
||||
)
|
||||
offset = len(prefix)
|
||||
|
||||
for name in files:
|
||||
# Find names under the templates directory that aren't directories.
|
||||
if name.startswith(prefix) and name[-1] != os.sep:
|
||||
results.append(name[offset:].replace(os.sep, "/"))
|
||||
if name.startswith(prefix) and name[-1] != os.path.sep:
|
||||
results.append(name[offset:].replace(os.path.sep, "/"))
|
||||
|
||||
results.sort()
|
||||
return results
|
||||
@ -443,13 +447,13 @@ class DictLoader(BaseLoader):
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> tuple[str, None, t.Callable[[], bool]]:
|
||||
) -> t.Tuple[str, None, t.Callable[[], bool]]:
|
||||
if template in self.mapping:
|
||||
source = self.mapping[template]
|
||||
return source, None, lambda: source == self.mapping.get(template)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self) -> list[str]:
|
||||
def list_templates(self) -> t.List[str]:
|
||||
return sorted(self.mapping)
|
||||
|
||||
|
||||
@ -475,14 +479,18 @@ class FunctionLoader(BaseLoader):
|
||||
self,
|
||||
load_func: t.Callable[
|
||||
[str],
|
||||
str | tuple[str, str | None, t.Callable[[], bool] | None] | None,
|
||||
t.Optional[
|
||||
t.Union[
|
||||
str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
|
||||
]
|
||||
],
|
||||
],
|
||||
) -> None:
|
||||
self.load_func = load_func
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
rv = self.load_func(template)
|
||||
|
||||
if rv is None:
|
||||
@ -515,7 +523,7 @@ class PrefixLoader(BaseLoader):
|
||||
self.mapping = mapping
|
||||
self.delimiter = delimiter
|
||||
|
||||
def get_loader(self, template: str) -> tuple[BaseLoader, str]:
|
||||
def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
|
||||
try:
|
||||
prefix, name = template.split(self.delimiter, 1)
|
||||
loader = self.mapping[prefix]
|
||||
@ -525,7 +533,7 @@ class PrefixLoader(BaseLoader):
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
loader, name = self.get_loader(template)
|
||||
try:
|
||||
return loader.get_source(environment, name)
|
||||
@ -539,7 +547,7 @@ class PrefixLoader(BaseLoader):
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
loader, local_name = self.get_loader(name)
|
||||
try:
|
||||
@ -549,7 +557,7 @@ class PrefixLoader(BaseLoader):
|
||||
# (the one that includes the prefix)
|
||||
raise TemplateNotFound(name) from e
|
||||
|
||||
def list_templates(self) -> list[str]:
|
||||
def list_templates(self) -> t.List[str]:
|
||||
result = []
|
||||
for prefix, loader in self.mapping.items():
|
||||
for template in loader.list_templates():
|
||||
@ -576,7 +584,7 @@ class ChoiceLoader(BaseLoader):
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.get_source(environment, template)
|
||||
@ -589,7 +597,7 @@ class ChoiceLoader(BaseLoader):
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
@ -598,7 +606,7 @@ class ChoiceLoader(BaseLoader):
|
||||
pass
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
def list_templates(self) -> list[str]:
|
||||
def list_templates(self) -> t.List[str]:
|
||||
found = set()
|
||||
for loader in self.loaders:
|
||||
found.update(loader.list_templates())
|
||||
@ -661,7 +669,7 @@ class ModuleLoader(BaseLoader):
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
key = self.get_template_key(name)
|
||||
module = f"{self.package_name}.{key}"
|
||||
|
||||
@ -17,7 +17,7 @@ class TrackingCodeGenerator(CodeGenerator):
|
||||
|
||||
def __init__(self, environment: "Environment") -> None:
|
||||
super().__init__(environment, "<introspection>", "<introspection>")
|
||||
self.undeclared_identifiers: set[str] = set()
|
||||
self.undeclared_identifiers: t.Set[str] = set()
|
||||
|
||||
def write(self, x: str) -> None:
|
||||
"""Don't write."""
|
||||
@ -31,7 +31,7 @@ class TrackingCodeGenerator(CodeGenerator):
|
||||
self.undeclared_identifiers.add(param)
|
||||
|
||||
|
||||
def find_undeclared_variables(ast: nodes.Template) -> set[str]:
|
||||
def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:
|
||||
"""Returns a set of all variables in the AST that will be looked up from
|
||||
the context at runtime. Because at compile time it's not known which
|
||||
variables will be used depending on the path the execution takes at
|
||||
@ -56,10 +56,10 @@ def find_undeclared_variables(ast: nodes.Template) -> set[str]:
|
||||
|
||||
|
||||
_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
|
||||
_RefType = nodes.Extends | nodes.FromImport | nodes.Import | nodes.Include
|
||||
_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]
|
||||
|
||||
|
||||
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[str | None]:
|
||||
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
|
||||
"""Finds all the referenced templates from the AST. This will return an
|
||||
iterator over all the hardcoded template extensions, inclusions and
|
||||
imports. If dynamic inheritance or inclusion is used, `None` will be
|
||||
|
||||
@ -13,7 +13,7 @@ from .environment import Environment
|
||||
from .environment import Template
|
||||
|
||||
|
||||
def native_concat(values: t.Iterable[t.Any]) -> t.Any | None:
|
||||
def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
|
||||
"""Return a native Python type from the list of compiled nodes. If
|
||||
the result is a single node, its value is returned. Otherwise, the
|
||||
nodes are concatenated as strings. If the result can be parsed with
|
||||
|
||||
@ -19,7 +19,7 @@ if t.TYPE_CHECKING:
|
||||
|
||||
_NodeBound = t.TypeVar("_NodeBound", bound="Node")
|
||||
|
||||
_binop_to_func: dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
_binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
"*": operator.mul,
|
||||
"/": operator.truediv,
|
||||
"//": operator.floordiv,
|
||||
@ -29,13 +29,13 @@ _binop_to_func: dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
"-": operator.sub,
|
||||
}
|
||||
|
||||
_uaop_to_func: dict[str, t.Callable[[t.Any], t.Any]] = {
|
||||
_uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
|
||||
"not": operator.not_,
|
||||
"+": operator.pos,
|
||||
"-": operator.neg,
|
||||
}
|
||||
|
||||
_cmpop_to_func: dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
_cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
"eq": operator.eq,
|
||||
"ne": operator.ne,
|
||||
"gt": operator.gt,
|
||||
@ -58,7 +58,7 @@ class NodeType(type):
|
||||
|
||||
def __new__(mcs, name, bases, d): # type: ignore
|
||||
for attr in "fields", "attributes":
|
||||
storage: list[tuple[str, ...]] = []
|
||||
storage: t.List[t.Tuple[str, ...]] = []
|
||||
storage.extend(getattr(bases[0] if bases else object, attr, ()))
|
||||
storage.extend(d.get(attr, ()))
|
||||
assert len(bases) <= 1, "multiple inheritance not allowed"
|
||||
@ -74,7 +74,7 @@ class EvalContext:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, environment: "Environment", template_name: str | None = None
|
||||
self, environment: "Environment", template_name: t.Optional[str] = None
|
||||
) -> None:
|
||||
self.environment = environment
|
||||
if callable(environment.autoescape):
|
||||
@ -91,7 +91,7 @@ class EvalContext:
|
||||
self.__dict__.update(old)
|
||||
|
||||
|
||||
def get_eval_context(node: "Node", ctx: EvalContext | None) -> EvalContext:
|
||||
def get_eval_context(node: "Node", ctx: t.Optional[EvalContext]) -> EvalContext:
|
||||
if ctx is None:
|
||||
if node.environment is None:
|
||||
raise RuntimeError(
|
||||
@ -119,8 +119,8 @@ class Node(metaclass=NodeType):
|
||||
all nodes automatically.
|
||||
"""
|
||||
|
||||
fields: tuple[str, ...] = ()
|
||||
attributes: tuple[str, ...] = ("lineno", "environment")
|
||||
fields: t.Tuple[str, ...] = ()
|
||||
attributes: t.Tuple[str, ...] = ("lineno", "environment")
|
||||
abstract = True
|
||||
|
||||
lineno: int
|
||||
@ -137,7 +137,7 @@ class Node(metaclass=NodeType):
|
||||
f"{type(self).__name__!r} takes 0 or {len(self.fields)}"
|
||||
f" argument{'s' if len(self.fields) != 1 else ''}"
|
||||
)
|
||||
for name, arg in zip(self.fields, fields, strict=False):
|
||||
for name, arg in zip(self.fields, fields):
|
||||
setattr(self, name, arg)
|
||||
for attr in self.attributes:
|
||||
setattr(self, attr, attributes.pop(attr, None))
|
||||
@ -146,9 +146,9 @@ class Node(metaclass=NodeType):
|
||||
|
||||
def iter_fields(
|
||||
self,
|
||||
exclude: t.Container[str] | None = None,
|
||||
only: t.Container[str] | None = None,
|
||||
) -> t.Iterator[tuple[str, t.Any]]:
|
||||
exclude: t.Optional[t.Container[str]] = None,
|
||||
only: t.Optional[t.Container[str]] = None,
|
||||
) -> t.Iterator[t.Tuple[str, t.Any]]:
|
||||
"""This method iterates over all fields that are defined and yields
|
||||
``(key, value)`` tuples. Per default all fields are returned, but
|
||||
it's possible to limit that to some fields by providing the `only`
|
||||
@ -168,8 +168,8 @@ class Node(metaclass=NodeType):
|
||||
|
||||
def iter_child_nodes(
|
||||
self,
|
||||
exclude: t.Container[str] | None = None,
|
||||
only: t.Container[str] | None = None,
|
||||
exclude: t.Optional[t.Container[str]] = None,
|
||||
only: t.Optional[t.Container[str]] = None,
|
||||
) -> t.Iterator["Node"]:
|
||||
"""Iterates over all direct child nodes of the node. This iterates
|
||||
over all fields and yields the values of they are nodes. If the value
|
||||
@ -183,7 +183,7 @@ class Node(metaclass=NodeType):
|
||||
elif isinstance(item, Node):
|
||||
yield item
|
||||
|
||||
def find(self, node_type: type[_NodeBound]) -> _NodeBound | None:
|
||||
def find(self, node_type: t.Type[_NodeBound]) -> t.Optional[_NodeBound]:
|
||||
"""Find the first node of a given type. If no such node exists the
|
||||
return value is `None`.
|
||||
"""
|
||||
@ -193,7 +193,7 @@ class Node(metaclass=NodeType):
|
||||
return None
|
||||
|
||||
def find_all(
|
||||
self, node_type: type[_NodeBound] | tuple[type[_NodeBound], ...]
|
||||
self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]]
|
||||
) -> t.Iterator[_NodeBound]:
|
||||
"""Find all the nodes of a given type. If the type is a tuple,
|
||||
the check is performed for any of the tuple items.
|
||||
@ -250,7 +250,7 @@ class Node(metaclass=NodeType):
|
||||
return f"{type(self).__name__}({args_str})"
|
||||
|
||||
def dump(self) -> str:
|
||||
def _dump(node: Node | t.Any) -> None:
|
||||
def _dump(node: t.Union[Node, t.Any]) -> None:
|
||||
if not isinstance(node, Node):
|
||||
buf.append(repr(node))
|
||||
return
|
||||
@ -274,7 +274,7 @@ class Node(metaclass=NodeType):
|
||||
_dump(value)
|
||||
buf.append(")")
|
||||
|
||||
buf: list[str] = []
|
||||
buf: t.List[str] = []
|
||||
_dump(self)
|
||||
return "".join(buf)
|
||||
|
||||
@ -297,7 +297,7 @@ class Template(Node):
|
||||
"""
|
||||
|
||||
fields = ("body",)
|
||||
body: list[Node]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
class Output(Stmt):
|
||||
@ -306,7 +306,7 @@ class Output(Stmt):
|
||||
"""
|
||||
|
||||
fields = ("nodes",)
|
||||
nodes: list["Expr"]
|
||||
nodes: t.List["Expr"]
|
||||
|
||||
|
||||
class Extends(Stmt):
|
||||
@ -328,9 +328,9 @@ class For(Stmt):
|
||||
fields = ("target", "iter", "body", "else_", "test", "recursive")
|
||||
target: Node
|
||||
iter: Node
|
||||
body: list[Node]
|
||||
else_: list[Node]
|
||||
test: Node | None
|
||||
body: t.List[Node]
|
||||
else_: t.List[Node]
|
||||
test: t.Optional[Node]
|
||||
recursive: bool
|
||||
|
||||
|
||||
@ -339,9 +339,9 @@ class If(Stmt):
|
||||
|
||||
fields = ("test", "body", "elif_", "else_")
|
||||
test: Node
|
||||
body: list[Node]
|
||||
elif_: list["If"]
|
||||
else_: list[Node]
|
||||
body: t.List[Node]
|
||||
elif_: t.List["If"]
|
||||
else_: t.List[Node]
|
||||
|
||||
|
||||
class Macro(Stmt):
|
||||
@ -352,9 +352,9 @@ class Macro(Stmt):
|
||||
|
||||
fields = ("name", "args", "defaults", "body")
|
||||
name: str
|
||||
args: list["Name"]
|
||||
defaults: list["Expr"]
|
||||
body: list[Node]
|
||||
args: t.List["Name"]
|
||||
defaults: t.List["Expr"]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
class CallBlock(Stmt):
|
||||
@ -364,16 +364,16 @@ class CallBlock(Stmt):
|
||||
|
||||
fields = ("call", "args", "defaults", "body")
|
||||
call: "Call"
|
||||
args: list["Name"]
|
||||
defaults: list["Expr"]
|
||||
body: list[Node]
|
||||
args: t.List["Name"]
|
||||
defaults: t.List["Expr"]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
class FilterBlock(Stmt):
|
||||
"""Node for filter sections."""
|
||||
|
||||
fields = ("body", "filter")
|
||||
body: list[Node]
|
||||
body: t.List[Node]
|
||||
filter: "Filter"
|
||||
|
||||
|
||||
@ -385,9 +385,9 @@ class With(Stmt):
|
||||
"""
|
||||
|
||||
fields = ("targets", "values", "body")
|
||||
targets: list["Expr"]
|
||||
values: list["Expr"]
|
||||
body: list[Node]
|
||||
targets: t.List["Expr"]
|
||||
values: t.List["Expr"]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
class Block(Stmt):
|
||||
@ -399,7 +399,7 @@ class Block(Stmt):
|
||||
|
||||
fields = ("name", "body", "scoped", "required")
|
||||
name: str
|
||||
body: list[Node]
|
||||
body: t.List[Node]
|
||||
scoped: bool
|
||||
required: bool
|
||||
|
||||
@ -436,7 +436,7 @@ class FromImport(Stmt):
|
||||
|
||||
fields = ("template", "names", "with_context")
|
||||
template: "Expr"
|
||||
names: list[str | tuple[str, str]]
|
||||
names: t.List[t.Union[str, t.Tuple[str, str]]]
|
||||
with_context: bool
|
||||
|
||||
|
||||
@ -461,7 +461,7 @@ class AssignBlock(Stmt):
|
||||
fields = ("target", "filter", "body")
|
||||
target: "Expr"
|
||||
filter: t.Optional["Filter"]
|
||||
body: list[Node]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
class Expr(Node):
|
||||
@ -469,7 +469,7 @@ class Expr(Node):
|
||||
|
||||
abstract = True
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
"""Return the value of the expression as constant or raise
|
||||
:exc:`Impossible` if this was not possible.
|
||||
|
||||
@ -496,7 +496,7 @@ class BinExpr(Expr):
|
||||
operator: str
|
||||
abstract = True
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
|
||||
# intercepted operators cannot be folded at compile time
|
||||
@ -520,7 +520,7 @@ class UnaryExpr(Expr):
|
||||
operator: str
|
||||
abstract = True
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
|
||||
# intercepted operators cannot be folded at compile time
|
||||
@ -584,15 +584,15 @@ class Const(Literal):
|
||||
fields = ("value",)
|
||||
value: t.Any
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_untrusted(
|
||||
cls,
|
||||
value: t.Any,
|
||||
lineno: int | None = None,
|
||||
environment: "Environment | None" = None,
|
||||
lineno: t.Optional[int] = None,
|
||||
environment: "t.Optional[Environment]" = None,
|
||||
) -> "Const":
|
||||
"""Return a const object if the value is representable as
|
||||
constant value in the generated code, otherwise it will raise
|
||||
@ -611,7 +611,7 @@ class TemplateData(Literal):
|
||||
fields = ("data",)
|
||||
data: str
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> str:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if eval_ctx.volatile:
|
||||
raise Impossible()
|
||||
@ -627,10 +627,10 @@ class Tuple(Literal):
|
||||
"""
|
||||
|
||||
fields = ("items", "ctx")
|
||||
items: list[Expr]
|
||||
items: t.List[Expr]
|
||||
ctx: str
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> tuple[t.Any, ...]:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return tuple(x.as_const(eval_ctx) for x in self.items)
|
||||
|
||||
@ -645,9 +645,9 @@ class List(Literal):
|
||||
"""Any list literal such as ``[1, 2, 3]``"""
|
||||
|
||||
fields = ("items",)
|
||||
items: list[Expr]
|
||||
items: t.List[Expr]
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> list[t.Any]:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return [x.as_const(eval_ctx) for x in self.items]
|
||||
|
||||
@ -658,9 +658,11 @@ class Dict(Literal):
|
||||
"""
|
||||
|
||||
fields = ("items",)
|
||||
items: list["Pair"]
|
||||
items: t.List["Pair"]
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> dict[t.Any, t.Any]:
|
||||
def as_const(
|
||||
self, eval_ctx: t.Optional[EvalContext] = None
|
||||
) -> t.Dict[t.Any, t.Any]:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return dict(x.as_const(eval_ctx) for x in self.items)
|
||||
|
||||
@ -672,7 +674,9 @@ class Pair(Helper):
|
||||
key: Expr
|
||||
value: Expr
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> tuple[t.Any, t.Any]:
|
||||
def as_const(
|
||||
self, eval_ctx: t.Optional[EvalContext] = None
|
||||
) -> t.Tuple[t.Any, t.Any]:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
|
||||
|
||||
@ -684,7 +688,7 @@ class Keyword(Helper):
|
||||
key: str
|
||||
value: Expr
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> tuple[str, t.Any]:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.key, self.value.as_const(eval_ctx)
|
||||
|
||||
@ -697,9 +701,9 @@ class CondExpr(Expr):
|
||||
fields = ("test", "expr1", "expr2")
|
||||
test: Expr
|
||||
expr1: Expr
|
||||
expr2: Expr | None
|
||||
expr2: t.Optional[Expr]
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if self.test.as_const(eval_ctx):
|
||||
return self.expr1.as_const(eval_ctx)
|
||||
@ -712,8 +716,8 @@ class CondExpr(Expr):
|
||||
|
||||
|
||||
def args_as_const(
|
||||
node: t.Union["_FilterTestCommon", "Call"], eval_ctx: EvalContext | None
|
||||
) -> tuple[list[t.Any], dict[t.Any, t.Any]]:
|
||||
node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext]
|
||||
) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]:
|
||||
args = [x.as_const(eval_ctx) for x in node.args]
|
||||
kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
|
||||
|
||||
@ -736,14 +740,14 @@ class _FilterTestCommon(Expr):
|
||||
fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs")
|
||||
node: Expr
|
||||
name: str
|
||||
args: list[Expr]
|
||||
kwargs: list[Pair]
|
||||
dyn_args: Expr | None
|
||||
dyn_kwargs: Expr | None
|
||||
args: t.List[Expr]
|
||||
kwargs: t.List[Pair]
|
||||
dyn_args: t.Optional[Expr]
|
||||
dyn_kwargs: t.Optional[Expr]
|
||||
abstract = True
|
||||
_is_filter = True
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
|
||||
if eval_ctx.volatile:
|
||||
@ -788,9 +792,9 @@ class Filter(_FilterTestCommon):
|
||||
and is applied to the content of the block.
|
||||
"""
|
||||
|
||||
node: Expr | None # type: ignore
|
||||
node: t.Optional[Expr] # type: ignore
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
if self.node is None:
|
||||
raise Impossible()
|
||||
|
||||
@ -820,10 +824,10 @@ class Call(Expr):
|
||||
|
||||
fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs")
|
||||
node: Expr
|
||||
args: list[Expr]
|
||||
kwargs: list[Keyword]
|
||||
dyn_args: Expr | None
|
||||
dyn_kwargs: Expr | None
|
||||
args: t.List[Expr]
|
||||
kwargs: t.List[Keyword]
|
||||
dyn_args: t.Optional[Expr]
|
||||
dyn_kwargs: t.Optional[Expr]
|
||||
|
||||
|
||||
class Getitem(Expr):
|
||||
@ -834,7 +838,7 @@ class Getitem(Expr):
|
||||
arg: Expr
|
||||
ctx: str
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
if self.ctx != "load":
|
||||
raise Impossible()
|
||||
|
||||
@ -858,7 +862,7 @@ class Getattr(Expr):
|
||||
attr: str
|
||||
ctx: str
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
if self.ctx != "load":
|
||||
raise Impossible()
|
||||
|
||||
@ -876,14 +880,14 @@ class Slice(Expr):
|
||||
"""
|
||||
|
||||
fields = ("start", "stop", "step")
|
||||
start: Expr | None
|
||||
stop: Expr | None
|
||||
step: Expr | None
|
||||
start: t.Optional[Expr]
|
||||
stop: t.Optional[Expr]
|
||||
step: t.Optional[Expr]
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> slice:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
|
||||
def const(obj: Expr | None) -> t.Any | None:
|
||||
def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]:
|
||||
if obj is None:
|
||||
return None
|
||||
return obj.as_const(eval_ctx)
|
||||
@ -897,9 +901,9 @@ class Concat(Expr):
|
||||
"""
|
||||
|
||||
fields = ("nodes",)
|
||||
nodes: list[Expr]
|
||||
nodes: t.List[Expr]
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> str:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)
|
||||
|
||||
@ -911,9 +915,9 @@ class Compare(Expr):
|
||||
|
||||
fields = ("expr", "ops")
|
||||
expr: Expr
|
||||
ops: list["Operand"]
|
||||
ops: t.List["Operand"]
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
result = value = self.expr.as_const(eval_ctx)
|
||||
|
||||
@ -989,7 +993,7 @@ class And(BinExpr):
|
||||
|
||||
operator = "and"
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
|
||||
|
||||
@ -999,7 +1003,7 @@ class Or(BinExpr):
|
||||
|
||||
operator = "or"
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
|
||||
|
||||
@ -1082,7 +1086,7 @@ class MarkSafe(Expr):
|
||||
fields = ("expr",)
|
||||
expr: Expr
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> Markup:
|
||||
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> Markup:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return Markup(self.expr.as_const(eval_ctx))
|
||||
|
||||
@ -1097,7 +1101,9 @@ class MarkSafeIfAutoescape(Expr):
|
||||
fields = ("expr",)
|
||||
expr: Expr
|
||||
|
||||
def as_const(self, eval_ctx: EvalContext | None = None) -> Markup | t.Any:
|
||||
def as_const(
|
||||
self, eval_ctx: t.Optional[EvalContext] = None
|
||||
) -> t.Union[Markup, t.Any]:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if eval_ctx.volatile:
|
||||
raise Impossible()
|
||||
@ -1146,7 +1152,7 @@ class Scope(Stmt):
|
||||
"""An artificial scope."""
|
||||
|
||||
fields = ("body",)
|
||||
body: list[Node]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
class OverlayScope(Stmt):
|
||||
@ -1165,7 +1171,7 @@ class OverlayScope(Stmt):
|
||||
|
||||
fields = ("context", "body")
|
||||
context: Expr
|
||||
body: list[Node]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
class EvalContextModifier(Stmt):
|
||||
@ -1178,7 +1184,7 @@ class EvalContextModifier(Stmt):
|
||||
"""
|
||||
|
||||
fields = ("options",)
|
||||
options: list[Keyword]
|
||||
options: t.List[Keyword]
|
||||
|
||||
|
||||
class ScopedEvalContextModifier(EvalContextModifier):
|
||||
@ -1188,7 +1194,7 @@ class ScopedEvalContextModifier(EvalContextModifier):
|
||||
"""
|
||||
|
||||
fields = ("body",)
|
||||
body: list[Node]
|
||||
body: t.List[Node]
|
||||
|
||||
|
||||
# make sure nobody creates custom nodes
|
||||
|
||||
@ -25,7 +25,7 @@ def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node:
|
||||
|
||||
|
||||
class Optimizer(NodeTransformer):
|
||||
def __init__(self, environment: "Environment | None") -> None:
|
||||
def __init__(self, environment: "t.Optional[Environment]") -> None:
|
||||
self.environment = environment
|
||||
|
||||
def generic_visit(
|
||||
|
||||
@ -35,7 +35,7 @@ _statement_keywords = frozenset(
|
||||
)
|
||||
_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])
|
||||
|
||||
_math_nodes: dict[str, type[nodes.Expr]] = {
|
||||
_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = {
|
||||
"add": nodes.Add,
|
||||
"sub": nodes.Sub,
|
||||
"mul": nodes.Mul,
|
||||
@ -54,30 +54,30 @@ class Parser:
|
||||
self,
|
||||
environment: "Environment",
|
||||
source: str,
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
state: str | None = None,
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
state: t.Optional[str] = None,
|
||||
) -> None:
|
||||
self.environment = environment
|
||||
self.stream = environment._tokenize(source, name, filename, state)
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.closed = False
|
||||
self.extensions: dict[
|
||||
str, t.Callable[[Parser], nodes.Node | list[nodes.Node]]
|
||||
self.extensions: t.Dict[
|
||||
str, t.Callable[[Parser], t.Union[nodes.Node, t.List[nodes.Node]]]
|
||||
] = {}
|
||||
for extension in environment.iter_extensions():
|
||||
for tag in extension.tags:
|
||||
self.extensions[tag] = extension.parse
|
||||
self._last_identifier = 0
|
||||
self._tag_stack: list[str] = []
|
||||
self._end_token_stack: list[tuple[str, ...]] = []
|
||||
self._tag_stack: t.List[str] = []
|
||||
self._end_token_stack: t.List[t.Tuple[str, ...]] = []
|
||||
|
||||
def fail(
|
||||
self,
|
||||
msg: str,
|
||||
lineno: int | None = None,
|
||||
exc: type[TemplateSyntaxError] = TemplateSyntaxError,
|
||||
lineno: t.Optional[int] = None,
|
||||
exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError,
|
||||
) -> "te.NoReturn":
|
||||
"""Convenience method that raises `exc` with the message, passed
|
||||
line number or last line number as well as the current name and
|
||||
@ -89,15 +89,15 @@ class Parser:
|
||||
|
||||
def _fail_ut_eof(
|
||||
self,
|
||||
name: str | None,
|
||||
end_token_stack: list[tuple[str, ...]],
|
||||
lineno: int | None,
|
||||
name: t.Optional[str],
|
||||
end_token_stack: t.List[t.Tuple[str, ...]],
|
||||
lineno: t.Optional[int],
|
||||
) -> "te.NoReturn":
|
||||
expected: set[str] = set()
|
||||
expected: t.Set[str] = set()
|
||||
for exprs in end_token_stack:
|
||||
expected.update(map(describe_token_expr, exprs))
|
||||
if end_token_stack:
|
||||
currently_looking: str | None = " or ".join(
|
||||
currently_looking: t.Optional[str] = " or ".join(
|
||||
map(repr, map(describe_token_expr, end_token_stack[-1]))
|
||||
)
|
||||
else:
|
||||
@ -127,7 +127,9 @@ class Parser:
|
||||
|
||||
self.fail(" ".join(message), lineno)
|
||||
|
||||
def fail_unknown_tag(self, name: str, lineno: int | None = None) -> "te.NoReturn":
|
||||
def fail_unknown_tag(
|
||||
self, name: str, lineno: t.Optional[int] = None
|
||||
) -> "te.NoReturn":
|
||||
"""Called if the parser encounters an unknown tag. Tries to fail
|
||||
with a human readable error message that could help to identify
|
||||
the problem.
|
||||
@ -136,8 +138,8 @@ class Parser:
|
||||
|
||||
def fail_eof(
|
||||
self,
|
||||
end_tokens: tuple[str, ...] | None = None,
|
||||
lineno: int | None = None,
|
||||
end_tokens: t.Optional[t.Tuple[str, ...]] = None,
|
||||
lineno: t.Optional[int] = None,
|
||||
) -> "te.NoReturn":
|
||||
"""Like fail_unknown_tag but for end of template situations."""
|
||||
stack = list(self._end_token_stack)
|
||||
@ -145,7 +147,9 @@ class Parser:
|
||||
stack.append(end_tokens)
|
||||
self._fail_ut_eof(None, stack, lineno)
|
||||
|
||||
def is_tuple_end(self, extra_end_rules: tuple[str, ...] | None = None) -> bool:
|
||||
def is_tuple_end(
|
||||
self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None
|
||||
) -> bool:
|
||||
"""Are we at the end of a tuple?"""
|
||||
if self.stream.current.type in ("variable_end", "block_end", "rparen"):
|
||||
return True
|
||||
@ -153,14 +157,14 @@ class Parser:
|
||||
return self.stream.current.test_any(extra_end_rules) # type: ignore
|
||||
return False
|
||||
|
||||
def free_identifier(self, lineno: int | None = None) -> nodes.InternalName:
|
||||
def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName:
|
||||
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
|
||||
self._last_identifier += 1
|
||||
rv = object.__new__(nodes.InternalName)
|
||||
nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
|
||||
return rv
|
||||
|
||||
def parse_statement(self) -> nodes.Node | list[nodes.Node]:
|
||||
def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||
"""Parse a single statement."""
|
||||
token = self.stream.current
|
||||
if token.type != "name":
|
||||
@ -190,8 +194,8 @@ class Parser:
|
||||
self._tag_stack.pop()
|
||||
|
||||
def parse_statements(
|
||||
self, end_tokens: tuple[str, ...], drop_needle: bool = False
|
||||
) -> list[nodes.Node]:
|
||||
self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False
|
||||
) -> t.List[nodes.Node]:
|
||||
"""Parse multiple statements into a list until one of the end tokens
|
||||
is reached. This is used to parse the body of statements as it also
|
||||
parses template data if appropriate. The parser checks first if the
|
||||
@ -218,7 +222,7 @@ class Parser:
|
||||
next(self.stream)
|
||||
return result
|
||||
|
||||
def parse_set(self) -> nodes.Assign | nodes.AssignBlock:
|
||||
def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]:
|
||||
"""Parse an assign statement."""
|
||||
lineno = next(self.stream).lineno
|
||||
target = self.parse_assign_target(with_namespace=True)
|
||||
@ -268,8 +272,8 @@ class Parser:
|
||||
|
||||
def parse_with(self) -> nodes.With:
|
||||
node = nodes.With(lineno=next(self.stream).lineno)
|
||||
targets: list[nodes.Expr] = []
|
||||
values: list[nodes.Expr] = []
|
||||
targets: t.List[nodes.Expr] = []
|
||||
values: t.List[nodes.Expr] = []
|
||||
while self.stream.current.type != "block_end":
|
||||
if targets:
|
||||
self.stream.expect("comma")
|
||||
@ -462,17 +466,17 @@ class Parser:
|
||||
self,
|
||||
with_tuple: bool = True,
|
||||
name_only: bool = False,
|
||||
extra_end_rules: tuple[str, ...] | None = None,
|
||||
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
||||
with_namespace: bool = False,
|
||||
) -> nodes.NSRef | nodes.Name | nodes.Tuple: ...
|
||||
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: ...
|
||||
|
||||
def parse_assign_target(
|
||||
self,
|
||||
with_tuple: bool = True,
|
||||
name_only: bool = False,
|
||||
extra_end_rules: tuple[str, ...] | None = None,
|
||||
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
||||
with_namespace: bool = False,
|
||||
) -> nodes.NSRef | nodes.Name | nodes.Tuple:
|
||||
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
|
||||
"""Parse an assignment target. As Jinja allows assignments to
|
||||
tuples, this function can parse all allowed assignment targets. Per
|
||||
default assignments to tuples are parsed, that can be disable however
|
||||
@ -517,7 +521,7 @@ class Parser:
|
||||
def parse_condexpr(self) -> nodes.Expr:
|
||||
lineno = self.stream.current.lineno
|
||||
expr1 = self.parse_or()
|
||||
expr3: nodes.Expr | None
|
||||
expr3: t.Optional[nodes.Expr]
|
||||
|
||||
while self.stream.skip_if("name:if"):
|
||||
expr2 = self.parse_or()
|
||||
@ -682,10 +686,10 @@ class Parser:
|
||||
self,
|
||||
simplified: bool = False,
|
||||
with_condexpr: bool = True,
|
||||
extra_end_rules: tuple[str, ...] | None = None,
|
||||
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
||||
explicit_parentheses: bool = False,
|
||||
with_namespace: bool = False,
|
||||
) -> nodes.Tuple | nodes.Expr:
|
||||
) -> t.Union[nodes.Tuple, nodes.Expr]:
|
||||
"""Works like `parse_expression` but if multiple expressions are
|
||||
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
|
||||
This method could also return a regular expression instead of a tuple
|
||||
@ -716,7 +720,7 @@ class Parser:
|
||||
def parse() -> nodes.Expr:
|
||||
return self.parse_expression(with_condexpr=with_condexpr)
|
||||
|
||||
args: list[nodes.Expr] = []
|
||||
args: t.List[nodes.Expr] = []
|
||||
is_tuple = False
|
||||
|
||||
while True:
|
||||
@ -749,7 +753,7 @@ class Parser:
|
||||
|
||||
def parse_list(self) -> nodes.List:
|
||||
token = self.stream.expect("lbracket")
|
||||
items: list[nodes.Expr] = []
|
||||
items: t.List[nodes.Expr] = []
|
||||
while self.stream.current.type != "rbracket":
|
||||
if items:
|
||||
self.stream.expect("comma")
|
||||
@ -761,7 +765,7 @@ class Parser:
|
||||
|
||||
def parse_dict(self) -> nodes.Dict:
|
||||
token = self.stream.expect("lbrace")
|
||||
items: list[nodes.Pair] = []
|
||||
items: t.List[nodes.Pair] = []
|
||||
while self.stream.current.type != "rbrace":
|
||||
if items:
|
||||
self.stream.expect("comma")
|
||||
@ -802,7 +806,9 @@ class Parser:
|
||||
break
|
||||
return node
|
||||
|
||||
def parse_subscript(self, node: nodes.Expr) -> nodes.Getattr | nodes.Getitem:
|
||||
def parse_subscript(
|
||||
self, node: nodes.Expr
|
||||
) -> t.Union[nodes.Getattr, nodes.Getitem]:
|
||||
token = next(self.stream)
|
||||
arg: nodes.Expr
|
||||
|
||||
@ -818,7 +824,7 @@ class Parser:
|
||||
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
|
||||
return nodes.Getitem(node, arg, "load", lineno=token.lineno)
|
||||
if token.type == "lbracket":
|
||||
args: list[nodes.Expr] = []
|
||||
args: t.List[nodes.Expr] = []
|
||||
while self.stream.current.type != "rbracket":
|
||||
if args:
|
||||
self.stream.expect("comma")
|
||||
@ -833,7 +839,7 @@ class Parser:
|
||||
|
||||
def parse_subscribed(self) -> nodes.Expr:
|
||||
lineno = self.stream.current.lineno
|
||||
args: list[nodes.Expr | None]
|
||||
args: t.List[t.Optional[nodes.Expr]]
|
||||
|
||||
if self.stream.current.type == "colon":
|
||||
next(self.stream)
|
||||
@ -865,11 +871,11 @@ class Parser:
|
||||
|
||||
def parse_call_args(
|
||||
self,
|
||||
) -> tuple[
|
||||
list[nodes.Expr],
|
||||
list[nodes.Keyword],
|
||||
nodes.Expr | None,
|
||||
nodes.Expr | None,
|
||||
) -> t.Tuple[
|
||||
t.List[nodes.Expr],
|
||||
t.List[nodes.Keyword],
|
||||
t.Optional[nodes.Expr],
|
||||
t.Optional[nodes.Expr],
|
||||
]:
|
||||
token = self.stream.expect("lparen")
|
||||
args = []
|
||||
@ -927,8 +933,8 @@ class Parser:
|
||||
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)
|
||||
|
||||
def parse_filter(
|
||||
self, node: nodes.Expr | None, start_inline: bool = False
|
||||
) -> nodes.Expr | None:
|
||||
self, node: t.Optional[nodes.Expr], start_inline: bool = False
|
||||
) -> t.Optional[nodes.Expr]:
|
||||
while self.stream.current.type == "pipe" or start_inline:
|
||||
if not start_inline:
|
||||
next(self.stream)
|
||||
@ -961,7 +967,7 @@ class Parser:
|
||||
next(self.stream)
|
||||
name += "." + self.stream.expect("name").value
|
||||
dyn_args = dyn_kwargs = None
|
||||
kwargs: list[nodes.Keyword] = []
|
||||
kwargs: t.List[nodes.Keyword] = []
|
||||
if self.stream.current.type == "lparen":
|
||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
||||
elif self.stream.current.type in {
|
||||
@ -987,9 +993,11 @@ class Parser:
|
||||
node = nodes.Not(node, lineno=token.lineno)
|
||||
return node
|
||||
|
||||
def subparse(self, end_tokens: tuple[str, ...] | None = None) -> list[nodes.Node]:
|
||||
body: list[nodes.Node] = []
|
||||
data_buffer: list[nodes.Node] = []
|
||||
def subparse(
|
||||
self, end_tokens: t.Optional[t.Tuple[str, ...]] = None
|
||||
) -> t.List[nodes.Node]:
|
||||
body: t.List[nodes.Node] = []
|
||||
data_buffer: t.List[nodes.Node] = []
|
||||
add_data = data_buffer.append
|
||||
|
||||
if end_tokens is not None:
|
||||
|
||||
@ -92,12 +92,12 @@ def str_join(seq: t.Iterable[t.Any]) -> str:
|
||||
|
||||
def new_context(
|
||||
environment: "Environment",
|
||||
template_name: str | None,
|
||||
blocks: dict[str, t.Callable[["Context"], t.Iterator[str]]],
|
||||
vars: dict[str, t.Any] | None = None,
|
||||
template_name: t.Optional[str],
|
||||
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
|
||||
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
shared: bool = False,
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
locals: t.Mapping[str, t.Any] | None = None,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
||||
) -> "Context":
|
||||
"""Internal helper for context creation."""
|
||||
if vars is None:
|
||||
@ -165,16 +165,16 @@ class Context:
|
||||
def __init__(
|
||||
self,
|
||||
environment: "Environment",
|
||||
parent: dict[str, t.Any],
|
||||
name: str | None,
|
||||
blocks: dict[str, t.Callable[["Context"], t.Iterator[str]]],
|
||||
globals: t.MutableMapping[str, t.Any] | None = None,
|
||||
parent: t.Dict[str, t.Any],
|
||||
name: t.Optional[str],
|
||||
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
):
|
||||
self.parent = parent
|
||||
self.vars: dict[str, t.Any] = {}
|
||||
self.vars: t.Dict[str, t.Any] = {}
|
||||
self.environment: Environment = environment
|
||||
self.eval_ctx = EvalContext(self.environment, name)
|
||||
self.exported_vars: set[str] = set()
|
||||
self.exported_vars: t.Set[str] = set()
|
||||
self.name = name
|
||||
self.globals_keys = set() if globals is None else set(globals)
|
||||
|
||||
@ -244,11 +244,11 @@ class Context:
|
||||
|
||||
return missing
|
||||
|
||||
def get_exported(self) -> dict[str, t.Any]:
|
||||
def get_exported(self) -> t.Dict[str, t.Any]:
|
||||
"""Get a new dict with the exported variables."""
|
||||
return {k: self.vars[k] for k in self.exported_vars}
|
||||
|
||||
def get_all(self) -> dict[str, t.Any]:
|
||||
def get_all(self) -> t.Dict[str, t.Any]:
|
||||
"""Return the complete context as dict including the exported
|
||||
variables. For optimizations reasons this might not return an
|
||||
actual copy so be careful with using it.
|
||||
@ -261,10 +261,10 @@ class Context:
|
||||
|
||||
@internalcode
|
||||
def call(
|
||||
__self, # noqa: B902
|
||||
__self,
|
||||
__obj: t.Callable[..., t.Any],
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
**kwargs: t.Any, # noqa: B902
|
||||
) -> t.Union[t.Any, "Undefined"]:
|
||||
"""Call the callable with the arguments and keyword arguments
|
||||
provided but inject the active context or environment as first
|
||||
@ -307,7 +307,7 @@ class Context:
|
||||
" StopIteration exception"
|
||||
)
|
||||
|
||||
def derived(self, locals: dict[str, t.Any] | None = None) -> "Context":
|
||||
def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> "Context":
|
||||
"""Internal helper function to create a derived context. This is
|
||||
used in situations where the system needs a new context in the same
|
||||
template that is independent.
|
||||
@ -348,7 +348,7 @@ class BlockReference:
|
||||
self,
|
||||
name: str,
|
||||
context: "Context",
|
||||
stack: list[t.Callable[["Context"], t.Iterator[str]]],
|
||||
stack: t.List[t.Callable[["Context"], t.Iterator[str]]],
|
||||
depth: int,
|
||||
) -> None:
|
||||
self.name = name
|
||||
@ -399,7 +399,7 @@ class LoopContext:
|
||||
#: Current iteration of the loop, starting at 0.
|
||||
index0 = -1
|
||||
|
||||
_length: int | None = None
|
||||
_length: t.Optional[int] = None
|
||||
_after: t.Any = missing
|
||||
_current: t.Any = missing
|
||||
_before: t.Any = missing
|
||||
@ -408,7 +408,7 @@ class LoopContext:
|
||||
def __init__(
|
||||
self,
|
||||
iterable: t.Iterable[V],
|
||||
undefined: type["Undefined"],
|
||||
undefined: t.Type["Undefined"],
|
||||
recurse: t.Optional["LoopRenderFunc"] = None,
|
||||
depth0: int = 0,
|
||||
) -> None:
|
||||
@ -558,7 +558,7 @@ class LoopContext:
|
||||
def __iter__(self) -> "LoopContext":
|
||||
return self
|
||||
|
||||
def __next__(self) -> tuple[t.Any, "LoopContext"]:
|
||||
def __next__(self) -> t.Tuple[t.Any, "LoopContext"]:
|
||||
if self._after is not missing:
|
||||
rv = self._after
|
||||
self._after = missing
|
||||
@ -593,7 +593,7 @@ class AsyncLoopContext(LoopContext):
|
||||
|
||||
@staticmethod
|
||||
def _to_iterator( # type: ignore
|
||||
iterable: t.Iterable[V] | t.AsyncIterable[V],
|
||||
iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]],
|
||||
) -> t.AsyncIterator[V]:
|
||||
return auto_aiter(iterable)
|
||||
|
||||
@ -646,7 +646,7 @@ class AsyncLoopContext(LoopContext):
|
||||
def __aiter__(self) -> "AsyncLoopContext":
|
||||
return self
|
||||
|
||||
async def __anext__(self) -> tuple[t.Any, "AsyncLoopContext"]:
|
||||
async def __anext__(self) -> t.Tuple[t.Any, "AsyncLoopContext"]:
|
||||
if self._after is not missing:
|
||||
rv = self._after
|
||||
self._after = missing
|
||||
@ -667,11 +667,11 @@ class Macro:
|
||||
environment: "Environment",
|
||||
func: t.Callable[..., str],
|
||||
name: str,
|
||||
arguments: list[str],
|
||||
arguments: t.List[str],
|
||||
catch_kwargs: bool,
|
||||
catch_varargs: bool,
|
||||
caller: bool,
|
||||
default_autoescape: bool | None = None,
|
||||
default_autoescape: t.Optional[bool] = None,
|
||||
):
|
||||
self._environment = environment
|
||||
self._func = func
|
||||
@ -769,7 +769,7 @@ class Macro:
|
||||
|
||||
return self._invoke(arguments, autoescape)
|
||||
|
||||
async def _async_invoke(self, arguments: list[t.Any], autoescape: bool) -> str:
|
||||
async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:
|
||||
rv = await self._func(*arguments) # type: ignore
|
||||
|
||||
if autoescape:
|
||||
@ -777,7 +777,7 @@ class Macro:
|
||||
|
||||
return rv # type: ignore
|
||||
|
||||
def _invoke(self, arguments: list[t.Any], autoescape: bool) -> str:
|
||||
def _invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:
|
||||
if self._environment.is_async:
|
||||
return self._async_invoke(arguments, autoescape) # type: ignore
|
||||
|
||||
@ -817,10 +817,10 @@ class Undefined:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hint: str | None = None,
|
||||
hint: t.Optional[str] = None,
|
||||
obj: t.Any = missing,
|
||||
name: str | None = None,
|
||||
exc: type[TemplateRuntimeError] = UndefinedError,
|
||||
name: t.Optional[str] = None,
|
||||
exc: t.Type[TemplateRuntimeError] = UndefinedError,
|
||||
) -> None:
|
||||
self._undefined_hint = hint
|
||||
self._undefined_obj = obj
|
||||
@ -910,8 +910,8 @@ class Undefined:
|
||||
|
||||
|
||||
def make_logging_undefined(
|
||||
logger: t.Optional["logging.Logger"] = None, base: type[Undefined] = Undefined
|
||||
) -> type[Undefined]:
|
||||
logger: t.Optional["logging.Logger"] = None, base: t.Type[Undefined] = Undefined
|
||||
) -> t.Type[Undefined]:
|
||||
"""Given a logger object this returns a new undefined class that will
|
||||
log certain failures. It will log iterations and printing. If no
|
||||
logger is given a default logger is created.
|
||||
|
||||
@ -25,10 +25,10 @@ F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
MAX_RANGE = 100000
|
||||
|
||||
#: Unsafe function attributes.
|
||||
UNSAFE_FUNCTION_ATTRIBUTES: set[str] = set()
|
||||
UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
|
||||
|
||||
#: Unsafe method attributes. Function attributes are unsafe for methods too.
|
||||
UNSAFE_METHOD_ATTRIBUTES: set[str] = set()
|
||||
UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
|
||||
|
||||
#: unsafe generator attributes.
|
||||
UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
|
||||
@ -39,7 +39,7 @@ UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
|
||||
#: unsafe attributes on async generators
|
||||
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
|
||||
|
||||
_mutable_spec: tuple[tuple[type[t.Any], frozenset[str]], ...] = (
|
||||
_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = (
|
||||
(
|
||||
abc.MutableSet,
|
||||
frozenset(
|
||||
@ -190,7 +190,7 @@ class SandboxedEnvironment(Environment):
|
||||
#: default callback table for the binary operators. A copy of this is
|
||||
#: available on each instance of a sandboxed environment as
|
||||
#: :attr:`binop_table`
|
||||
default_binop_table: dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
"+": operator.add,
|
||||
"-": operator.sub,
|
||||
"*": operator.mul,
|
||||
@ -203,7 +203,7 @@ class SandboxedEnvironment(Environment):
|
||||
#: default callback table for the unary operators. A copy of this is
|
||||
#: available on each instance of a sandboxed environment as
|
||||
#: :attr:`unop_table`
|
||||
default_unop_table: dict[str, t.Callable[[t.Any], t.Any]] = {
|
||||
default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
|
||||
"+": operator.pos,
|
||||
"-": operator.neg,
|
||||
}
|
||||
@ -222,7 +222,7 @@ class SandboxedEnvironment(Environment):
|
||||
#: interested in.
|
||||
#:
|
||||
#: .. versionadded:: 2.6
|
||||
intercepted_binops: frozenset[str] = frozenset()
|
||||
intercepted_binops: t.FrozenSet[str] = frozenset()
|
||||
|
||||
#: a set of unary operators that should be intercepted. Each operator
|
||||
#: that is added to this set (empty by default) is delegated to the
|
||||
@ -237,7 +237,7 @@ class SandboxedEnvironment(Environment):
|
||||
#: interested in.
|
||||
#:
|
||||
#: .. versionadded:: 2.6
|
||||
intercepted_unops: frozenset[str] = frozenset()
|
||||
intercepted_unops: t.FrozenSet[str] = frozenset()
|
||||
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -285,7 +285,9 @@ class SandboxedEnvironment(Environment):
|
||||
"""
|
||||
return self.unop_table[operator](arg)
|
||||
|
||||
def getitem(self, obj: t.Any, argument: str | t.Any) -> t.Any | Undefined:
|
||||
def getitem(
|
||||
self, obj: t.Any, argument: t.Union[str, t.Any]
|
||||
) -> t.Union[t.Any, Undefined]:
|
||||
"""Subscribe an object from sandboxed code."""
|
||||
try:
|
||||
return obj[argument]
|
||||
@ -309,7 +311,7 @@ class SandboxedEnvironment(Environment):
|
||||
return self.unsafe_undefined(obj, argument)
|
||||
return self.undefined(obj=obj, name=argument)
|
||||
|
||||
def getattr(self, obj: t.Any, attribute: str) -> t.Any | Undefined:
|
||||
def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
|
||||
"""Subscribe an object from sandboxed code and prefer the
|
||||
attribute. The attribute passed *must* be a bytestring.
|
||||
"""
|
||||
@ -339,7 +341,7 @@ class SandboxedEnvironment(Environment):
|
||||
exc=SecurityError,
|
||||
)
|
||||
|
||||
def wrap_str_format(self, value: t.Any) -> t.Callable[..., str] | None:
|
||||
def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]:
|
||||
"""If the given value is a ``str.format`` or ``str.format_map`` method,
|
||||
return a new function than handles sandboxing. This is done at access
|
||||
rather than in :meth:`call`, so that calls made without ``call`` are
|
||||
@ -355,7 +357,7 @@ class SandboxedEnvironment(Environment):
|
||||
if not isinstance(f_self, str):
|
||||
return None
|
||||
|
||||
str_type: type[str] = type(f_self)
|
||||
str_type: t.Type[str] = type(f_self)
|
||||
is_format_map = value.__name__ == "format_map"
|
||||
formatter: SandboxedFormatter
|
||||
|
||||
@ -419,7 +421,7 @@ class SandboxedFormatter(Formatter):
|
||||
|
||||
def get_field(
|
||||
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
|
||||
) -> tuple[t.Any, str]:
|
||||
) -> t.Tuple[t.Any, str]:
|
||||
first, rest = formatter_field_name_split(field_name)
|
||||
obj = self.get_value(first, args, kwargs)
|
||||
for is_attr, i in rest:
|
||||
|
||||
@ -161,7 +161,7 @@ def import_string(import_name: str, silent: bool = False) -> t.Any:
|
||||
raise
|
||||
|
||||
|
||||
def open_if_exists(filename: str, mode: str = "rb") -> t.IO[t.Any] | None:
|
||||
def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]:
|
||||
"""Returns a file descriptor for the filename if that file exists,
|
||||
otherwise ``None``.
|
||||
"""
|
||||
@ -229,10 +229,10 @@ _email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")
|
||||
|
||||
def urlize(
|
||||
text: str,
|
||||
trim_url_limit: int | None = None,
|
||||
rel: str | None = None,
|
||||
target: str | None = None,
|
||||
extra_schemes: t.Iterable[str] | None = None,
|
||||
trim_url_limit: t.Optional[int] = None,
|
||||
rel: t.Optional[str] = None,
|
||||
target: t.Optional[str] = None,
|
||||
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
||||
) -> str:
|
||||
"""Convert URLs in text into clickable links.
|
||||
|
||||
@ -438,8 +438,8 @@ class LRUCache:
|
||||
|
||||
def __init__(self, capacity: int) -> None:
|
||||
self.capacity = capacity
|
||||
self._mapping: dict[t.Any, t.Any] = {}
|
||||
self._queue: deque[t.Any] = deque()
|
||||
self._mapping: t.Dict[t.Any, t.Any] = {}
|
||||
self._queue: te.Deque[t.Any] = deque()
|
||||
self._postinit()
|
||||
|
||||
def _postinit(self) -> None:
|
||||
@ -461,7 +461,7 @@ class LRUCache:
|
||||
self.__dict__.update(d)
|
||||
self._postinit()
|
||||
|
||||
def __getnewargs__(self) -> tuple[t.Any, ...]:
|
||||
def __getnewargs__(self) -> t.Tuple[t.Any, ...]:
|
||||
return (self.capacity,)
|
||||
|
||||
def copy(self) -> "te.Self":
|
||||
@ -552,7 +552,7 @@ class LRUCache:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def items(self) -> t.Iterable[tuple[t.Any, t.Any]]:
|
||||
def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:
|
||||
"""Return a list of items."""
|
||||
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
||||
result.reverse()
|
||||
@ -583,7 +583,7 @@ def select_autoescape(
|
||||
disabled_extensions: t.Collection[str] = (),
|
||||
default_for_string: bool = True,
|
||||
default: bool = False,
|
||||
) -> t.Callable[[str | None], bool]:
|
||||
) -> t.Callable[[t.Optional[str]], bool]:
|
||||
"""Intelligently sets the initial value of autoescaping based on the
|
||||
filename of the template. This is the recommended way to configure
|
||||
autoescaping if you do not want to write a custom function yourself.
|
||||
@ -621,7 +621,7 @@ def select_autoescape(
|
||||
enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
|
||||
disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
|
||||
|
||||
def autoescape(template_name: str | None) -> bool:
|
||||
def autoescape(template_name: t.Optional[str]) -> bool:
|
||||
if template_name is None:
|
||||
return default_for_string
|
||||
template_name = template_name.lower()
|
||||
@ -635,7 +635,7 @@ def select_autoescape(
|
||||
|
||||
|
||||
def htmlsafe_json_dumps(
|
||||
obj: t.Any, dumps: t.Callable[..., str] | None = None, **kwargs: t.Any
|
||||
obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
|
||||
) -> markupsafe.Markup:
|
||||
"""Serialize an object to a string of JSON with :func:`json.dumps`,
|
||||
then replace HTML-unsafe characters with Unicode escapes and mark
|
||||
|
||||
@ -25,7 +25,7 @@ class NodeVisitor:
|
||||
(return value `None`) the `generic_visit` visitor is used instead.
|
||||
"""
|
||||
|
||||
def get_visitor(self, node: Node) -> "VisitCallable | None":
|
||||
def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]":
|
||||
"""Return the visitor function for this node or `None` if no visitor
|
||||
exists for this node. In that case the generic visit function is
|
||||
used instead.
|
||||
@ -80,7 +80,7 @@ class NodeTransformer(NodeVisitor):
|
||||
setattr(node, field, new_node)
|
||||
return node
|
||||
|
||||
def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> list[Node]:
|
||||
def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.List[Node]:
|
||||
"""As transformers may return lists in some places this method
|
||||
can be used to enforce a list as return value.
|
||||
"""
|
||||
|
||||
@ -1,22 +1,11 @@
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import trio
|
||||
|
||||
from jinja2 import loaders
|
||||
from jinja2.environment import Environment
|
||||
|
||||
|
||||
def _asyncio_run(async_fn, *args):
|
||||
return asyncio.run(async_fn(*args))
|
||||
|
||||
|
||||
@pytest.fixture(params=[_asyncio_run, trio.run], ids=["asyncio", "trio"])
|
||||
def run_async_fn(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def env():
|
||||
"""returns a new environment."""
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
import trio
|
||||
|
||||
from jinja2 import ChainableUndefined
|
||||
from jinja2 import DictLoader
|
||||
@ -11,6 +14,15 @@ from jinja2.exceptions import UndefinedError
|
||||
from jinja2.nativetypes import NativeEnvironment
|
||||
|
||||
|
||||
def _asyncio_run(async_fn, *args):
|
||||
return asyncio.run(async_fn(*args))
|
||||
|
||||
|
||||
@pytest.fixture(params=[_asyncio_run, trio.run], ids=["asyncio", "trio"])
|
||||
def run_async_fn(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def test_basic_async(run_async_fn):
|
||||
t = Template(
|
||||
"{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True
|
||||
@ -449,7 +461,9 @@ class TestAsyncForLoop:
|
||||
|
||||
def test_reversed_bug(self, test_env_async):
|
||||
tmpl = test_env_async.from_string(
|
||||
"{% for i in items %}{{ i }}{% if not loop.last %},{% endif %}{% endfor %}"
|
||||
"{% for i in items %}{{ i }}"
|
||||
"{% if not loop.last %}"
|
||||
",{% endif %}{% endfor %}"
|
||||
)
|
||||
assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3"
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
|
||||
import pytest
|
||||
import trio
|
||||
from markupsafe import Markup
|
||||
|
||||
from jinja2 import Environment
|
||||
@ -27,6 +29,15 @@ def env_async():
|
||||
return Environment(enable_async=True)
|
||||
|
||||
|
||||
def _asyncio_run(async_fn, *args):
|
||||
return asyncio.run(async_fn(*args))
|
||||
|
||||
|
||||
@pytest.fixture(params=[_asyncio_run, trio.run], ids=["asyncio", "trio"])
|
||||
def run_async_fn(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def closing_factory():
|
||||
async with contextlib.AsyncExitStack() as stack:
|
||||
|
||||
@ -191,7 +191,9 @@ class TestForLoop:
|
||||
|
||||
def test_reversed_bug(self, env):
|
||||
tmpl = env.from_string(
|
||||
"{% for i in items %}{{ i }}{% if not loop.last %},{% endif %}{% endfor %}"
|
||||
"{% for i in items %}{{ i }}"
|
||||
"{% if not loop.last %}"
|
||||
",{% endif %}{% endfor %}"
|
||||
)
|
||||
assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3"
|
||||
|
||||
|
||||
@ -23,9 +23,9 @@ class TestDebug:
|
||||
|
||||
tb = format_exception(exc_info.type, exc_info.value, exc_info.tb)
|
||||
m = re.search(expected_tb.strip(), "".join(tb))
|
||||
assert m is not None, (
|
||||
f"Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}"
|
||||
)
|
||||
assert (
|
||||
m is not None
|
||||
), f"Traceback did not match:\n\n{''.join(tb)}\nexpected:\n{expected_tb}"
|
||||
|
||||
def test_runtime_error(self, fs_env):
|
||||
def test():
|
||||
|
||||
@ -554,7 +554,8 @@ class TestNewstyleInternationalization:
|
||||
newstyle=True,
|
||||
)
|
||||
t = env.from_string(
|
||||
'{% autoescape ae %}{{ gettext("foo", name="<test>") }}{% endautoescape %}'
|
||||
'{% autoescape ae %}{{ gettext("foo", name='
|
||||
'"<test>") }}{% endautoescape %}'
|
||||
)
|
||||
assert t.render(ae=True) == "<strong>Wert: <test></strong>"
|
||||
assert t.render(ae=False) == "<strong>Wert: <test></strong>"
|
||||
|
||||
@ -357,7 +357,7 @@ class TestFilter:
|
||||
def test_urlize(self, env):
|
||||
tmpl = env.from_string('{{ "foo example.org bar"|urlize }}')
|
||||
assert tmpl.render() == (
|
||||
'foo <a href="https://example.org" rel="noopener">example.org</a> bar'
|
||||
'foo <a href="https://example.org" rel="noopener">' "example.org</a> bar"
|
||||
)
|
||||
tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
|
||||
assert tmpl.render() == (
|
||||
|
||||
@ -43,7 +43,8 @@ class TestTokenStream:
|
||||
class TestLexer:
|
||||
def test_raw1(self, env):
|
||||
tmpl = env.from_string(
|
||||
"{% raw %}foo{% endraw %}|{%raw%}{{ bar }}|{% baz %}{% endraw %}"
|
||||
"{% raw %}foo{% endraw %}|"
|
||||
"{%raw%}{{ bar }}|{% baz %}{% endraw %}"
|
||||
)
|
||||
assert tmpl.render() == "foo|{{ bar }}|{% baz %}"
|
||||
|
||||
|
||||
@ -13,11 +13,6 @@ def env():
|
||||
return NativeEnvironment()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def async_native_env():
|
||||
return NativeEnvironment(enable_async=True)
|
||||
|
||||
|
||||
def test_is_defined_native_return(env):
|
||||
t = env.from_string("{{ missing is defined }}")
|
||||
assert not t.render()
|
||||
@ -127,18 +122,6 @@ def test_string_top_level(env):
|
||||
assert result == "Jinja"
|
||||
|
||||
|
||||
def test_string_concatenation(async_native_env, run_async_fn):
|
||||
async def async_render():
|
||||
t = async_native_env.from_string(
|
||||
"{%- macro x(y) -%}{{ y }}{%- endmacro -%}{{- x('not') }} {{ x('bad') -}}"
|
||||
)
|
||||
result = await t.render_async()
|
||||
assert isinstance(result, str)
|
||||
assert result == "not bad"
|
||||
|
||||
run_async_fn(async_render)
|
||||
|
||||
|
||||
def test_tuple_of_variable_strings(env):
|
||||
t = env.from_string("'{{ a }}', 'data', '{{ b }}', b'{{ c }}'")
|
||||
result = t.render(a=1, b=2, c="bytes")
|
||||
|
||||
63
tox.ini
Normal file
63
tox.ini
Normal file
@ -0,0 +1,63 @@
|
||||
[tox]
|
||||
envlist =
|
||||
py3{13,12,11,10,9,8,7}
|
||||
pypy310
|
||||
style
|
||||
typing
|
||||
docs
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
package = wheel
|
||||
wheel_build_env = .pkg
|
||||
constrain_package_deps = true
|
||||
use_frozen_constraints = true
|
||||
deps = -r requirements/tests.txt
|
||||
commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs}
|
||||
|
||||
[testenv:py37,py3.7]
|
||||
deps = -r requirements/tests37.txt
|
||||
|
||||
[testenv:style]
|
||||
deps = pre-commit
|
||||
skip_install = true
|
||||
commands = pre-commit run --all-files
|
||||
|
||||
[testenv:typing]
|
||||
deps = -r requirements/typing.txt
|
||||
commands = mypy
|
||||
|
||||
[testenv:docs]
|
||||
deps = -r requirements/docs.txt
|
||||
commands = sphinx-build -E -W -b dirhtml docs docs/_build/dirhtml
|
||||
|
||||
[testenv:update-actions]
|
||||
labels = update
|
||||
deps = gha-update
|
||||
commands = gha-update
|
||||
|
||||
[testenv:update-pre_commit]
|
||||
labels = update
|
||||
deps = pre-commit
|
||||
skip_install = true
|
||||
commands = pre-commit autoupdate -j4
|
||||
|
||||
[testenv:update-requirements]
|
||||
labels = update
|
||||
deps = pip-tools
|
||||
skip_install = true
|
||||
change_dir = requirements
|
||||
commands =
|
||||
pip-compile build.in -q {posargs:-U}
|
||||
pip-compile docs.in -q {posargs:-U}
|
||||
pip-compile tests.in -q {posargs:-U}
|
||||
pip-compile typing.in -q {posargs:-U}
|
||||
pip-compile dev.in -q {posargs:-U}
|
||||
|
||||
[testenv:update-requirements37]
|
||||
base_python = 3.7
|
||||
labels = update
|
||||
deps = pip-tools
|
||||
skip_install = true
|
||||
change_dir = requirements
|
||||
commands = pip-compile tests.in -q -o tests37.txt {posargs:-U}
|
||||
Loading…
Reference in New Issue
Block a user