Compare commits

..

158 Commits
stable ... main

Author SHA1 Message Date
David Lord
5ef70112a1
Merge branch 'stable' 2025-06-14 13:34:58 -07:00
David Lord
0514dce509
Merge branch 'stable' 2025-06-12 13:59:39 -07:00
David Lord
77092a882a
Merge branch 'stable' 2025-06-11 10:38:29 -07:00
David Lord
10304827e4
update identifier pattern for Python 3.10 (#2099) 2025-05-28 12:14:31 -07:00
David Lord
574565b1ef
update identifier pattern for Python 3.10 2025-05-28 12:10:09 -07:00
David Lord
0547cd6c58
add Python version comment 2025-05-28 12:09:42 -07:00
David Lord
9c11dd65ff
fix typo 2025-05-28 11:44:50 -07:00
David Lord
9e49736ae0
deprecate __version__ (#2098) 2025-05-28 11:43:08 -07:00
David Lord
b7ce542db1
deprecate __version__ 2025-05-28 11:39:42 -07:00
David Lord
dfe82ade3d
bump minimum versions of dependencies (#2097) 2025-05-28 11:20:08 -07:00
David Lord
9508c57faa
bump minimum versions of dependencies 2025-05-28 11:17:40 -07:00
David Lord
49c48a0b31
drop end of life python versions (#2096) 2025-05-28 11:15:29 -07:00
David Lord
ece7c271f3
fix ruff pyupgrade findings 2025-05-28 11:13:57 -07:00
David Lord
0cc6ff9051
drop end of life python versions 2025-05-28 11:13:57 -07:00
David Lord
05f5d74849
Updated dependency management to uv (#2093) 2025-05-28 10:10:27 -07:00
Adam Englander
a3dce7bb64
fix type/lint/format findings 2025-05-28 10:08:39 -07:00
Adam Englander
51dbd8977e
use uv for dependency management
drop python 3.7 and 3.8
move tox config into pyproject
use new license metadata
2025-05-28 10:08:29 -07:00
David Lord
220e67ae99
Merge branch 'stable' 2025-03-05 12:15:40 -08:00
David Lord
6aeab5d1da
Merge branch 'stable' 2024-12-21 10:47:46 -08:00
Ronan Amicel
d680a95932
Fix nl2br example in documentation (#2054) 2024-12-07 11:04:28 -05:00
David Lord
ada0a9a6fc
update test workflow trigger 2024-10-24 14:16:05 -07:00
David Lord
ee6c734e9b
Merge branch 'stable' 2024-10-24 14:14:07 -07:00
David Lord
eb0df049de
set up pre-commit lite workflow
Committed via https://github.com/asottile/all-repos
2024-09-01 09:04:14 -07:00
David Lord
180816e571
set up pre-commit lite workflow
Committed via https://github.com/asottile/all-repos
2024-09-01 08:48:02 -07:00
David Lord
4c49d2322c
set up pre-commit lite workflow
Committed via https://github.com/asottile/all-repos
2024-09-01 08:32:27 -07:00
David Lord
cd74006a9b
remove pre-commit.ci update 2024-08-23 18:06:04 -07:00
David Lord
9949b49808
Merge branch '3.1.x' 2024-08-23 17:17:11 -07:00
David Lord
7f0fc0ad2c
Merge branch '3.1.x' 2024-08-23 16:52:48 -07:00
David Lord
b490da6b23
[pre-commit.ci] pre-commit autoupdate (#2012) 2024-08-06 08:30:44 -07:00
pre-commit-ci[bot]
295b284b7c
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.6)
2024-08-05 23:09:20 +00:00
David Lord
68d75132c4
Bump the python-requirements group in /requirements with 3 updates (#2007) 2024-08-03 07:29:33 -07:00
David Lord
0464cf88a0
Bump the github-actions group with 3 updates (#2008) 2024-08-03 07:28:10 -07:00
dependabot[bot]
293c8abe93
Bump the github-actions group with 3 updates
Bumps the github-actions group with 3 updates: [actions/setup-python](https://github.com/actions/setup-python), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/setup-python` from 5.1.0 to 5.1.1
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](82c7e631bb...39cd14951b)

Updates `actions/upload-artifact` from 4.3.3 to 4.3.4
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65462800fd...0b2256b8c0)

Updates `actions/download-artifact` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](65a9edc588...fa0a91b85d)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 07:36:57 +00:00
dependabot[bot]
ae8b5354e0
Bump the python-requirements group in /requirements with 3 updates
Bumps the python-requirements group in /requirements with 3 updates: [sphinx](https://github.com/sphinx-doc/sphinx), [trio](https://github.com/python-trio/trio) and [pre-commit](https://github.com/pre-commit/pre-commit).


Updates `sphinx` from 7.3.7 to 8.0.2
- [Release notes](https://github.com/sphinx-doc/sphinx/releases)
- [Changelog](https://github.com/sphinx-doc/sphinx/blob/v8.0.2/CHANGES.rst)
- [Commits](https://github.com/sphinx-doc/sphinx/compare/v7.3.7...v8.0.2)

Updates `trio` from 0.25.1 to 0.26.0
- [Release notes](https://github.com/python-trio/trio/releases)
- [Commits](https://github.com/python-trio/trio/compare/v0.25.1...v0.26.0)

Updates `pre-commit` from 3.7.1 to 3.8.0
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.7.1...v3.8.0)

---
updated-dependencies:
- dependency-name: sphinx
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: python-requirements
- dependency-name: trio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-requirements
- dependency-name: pre-commit
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: python-requirements
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 07:18:20 +00:00
James Addison
f8323cf404
Additional test coverage for async rendering of native type templates (#1807) 2024-07-10 09:14:52 -07:00
David Lord
d7225e65f3
[pre-commit.ci] pre-commit autoupdate (#2001) 2024-07-02 04:17:37 -07:00
pre-commit-ci[bot]
4e04e110e7
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.5.0)
2024-07-02 00:15:40 +00:00
David Lord
0087c5fe00
Bump the python-requirements group in /requirements with 2 updates (#1998) 2024-07-01 16:22:58 -07:00
David Lord
e72c1825d4
Bump the github-actions group with 2 updates (#1999) 2024-07-01 12:55:36 -07:00
dependabot[bot]
64b54f2189
Bump the github-actions group with 2 updates
Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish).


Updates `actions/checkout` from 4.1.6 to 4.1.7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](a5ac7e51b4...692973e3d9)

Updates `pypa/gh-action-pypi-publish` from 1.8.14 to 1.9.0
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](81e9d935c8...ec4db0b4dd)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 07:37:23 +00:00
dependabot[bot]
64e6151474
Bump the python-requirements group in /requirements with 2 updates
Bumps the python-requirements group in /requirements with 2 updates: [trio](https://github.com/python-trio/trio) and [tox](https://github.com/tox-dev/tox).


Updates `trio` from 0.22.2 to 0.25.1
- [Release notes](https://github.com/python-trio/trio/releases)
- [Commits](https://github.com/python-trio/trio/compare/v0.22.2...v0.25.1)

Updates `tox` from 4.15.0 to 4.15.1
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.15.0...4.15.1)

---
updated-dependencies:
- dependency-name: trio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-requirements
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: python-requirements
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 07:16:24 +00:00
David Lord
9c6c319899
[pre-commit.ci] pre-commit autoupdate (#1993) 2024-06-03 15:25:15 -07:00
pre-commit-ci[bot]
4b6dac1b6b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-06-03 22:10:44 +00:00
pre-commit-ci[bot]
951868f355
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.4.7)
2024-06-03 22:10:36 +00:00
David Lord
afb577b313
Bump actions/checkout from 4.1.4 to 4.1.6 in the github-actions group (#1990) 2024-06-01 06:02:39 -07:00
dependabot[bot]
71e374d895
Bump actions/checkout from 4.1.4 to 4.1.6 in the github-actions group
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4.1.4 to 4.1.6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](0ad4b8fada...a5ac7e51b4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-01 07:12:42 +00:00
David Lord
8710cabd4f
Convert rST code block to Markdown in README (#1981) 2024-05-19 09:48:28 -04:00
Timothée Mazzucotelli
90750800d4
Convert rST code block to Markdown in README 2024-05-19 15:32:56 +02:00
David Lord
b002d9c6c3
Merge branch '3.1.x' 2024-05-13 08:47:27 -07:00
David Lord
a516a99bab
[pre-commit.ci] pre-commit autoupdate (#1975) 2024-05-06 15:29:13 -07:00
pre-commit-ci[bot]
a89ed5fe0f
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.1...v0.4.3)
2024-05-06 22:05:37 +00:00
David Lord
11550f9df9
Merge branch '3.1.x' 2024-05-05 16:45:28 -07:00
David Lord
bbd5bcee7b
Merge branch '3.1.x' 2024-05-02 09:18:48 -07:00
David Lord
c6dd4bac24
Bump the python-requirements group in /requirements with 5 updates (#1973) 2024-05-01 06:43:12 -07:00
David Lord
6fcf463011
fix mypy findings 2024-05-01 06:41:20 -07:00
David Lord
27ea85b001
Bump the github-actions group with 2 updates (#1972) 2024-05-01 05:54:56 -07:00
dependabot[bot]
2e8bbca767
Bump the python-requirements group in /requirements with 5 updates
Bumps the python-requirements group in /requirements with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [pytest](https://github.com/pytest-dev/pytest) | `8.1.1` | `8.2.0` |
| [pallets-sphinx-themes](https://github.com/pallets/pallets-sphinx-themes) | `2.1.2` | `2.1.3` |
| [mypy](https://github.com/python/mypy) | `1.9.0` | `1.10.0` |
| [pyright](https://github.com/RobertCraigie/pyright-python) | `1.1.359` | `1.1.360` |
| [tox](https://github.com/tox-dev/tox) | `4.14.2` | `4.15.0` |


Updates `pytest` from 8.1.1 to 8.2.0
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.1.1...8.2.0)

Updates `pallets-sphinx-themes` from 2.1.2 to 2.1.3
- [Release notes](https://github.com/pallets/pallets-sphinx-themes/releases)
- [Changelog](https://github.com/pallets/pallets-sphinx-themes/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/pallets-sphinx-themes/compare/2.1.2...2.1.3)

Updates `mypy` from 1.9.0 to 1.10.0
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.0)

Updates `pyright` from 1.1.359 to 1.1.360
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.359...v1.1.360)

Updates `tox` from 4.14.2 to 4.15.0
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.14.2...4.15.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-requirements
- dependency-name: pallets-sphinx-themes
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: python-requirements
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-requirements
- dependency-name: pyright
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: python-requirements
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: python-requirements
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-01 07:30:56 +00:00
dependabot[bot]
4a7a153a48
Bump the github-actions group with 2 updates
Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/checkout` from 4.1.3 to 4.1.4
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](1d96c772d1...0ad4b8fada)

Updates `actions/download-artifact` from 4.1.6 to 4.1.7
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](9c19ed7fe5...65a9edc588)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-01 07:10:08 +00:00
David Lord
2a17038fca
Revert "upload/download-artifact v4"
This reverts commit c8aca74587.
2024-04-23 16:22:41 -07:00
David Lord
c8aca74587
upload/download-artifact v4 2024-04-23 15:48:42 -07:00
David Lord
9b33637538
Bump the github-actions group across 1 directory with 4 updates (#1970) 2024-04-23 12:35:10 -07:00
dependabot[bot]
2e3e3774a9
Bump the github-actions group across 1 directory with 4 updates
Bumps the github-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/upload-artifact](https://github.com/actions/upload-artifact), [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/checkout` from 4.1.2 to 4.1.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](9bb56186c3...1d96c772d1)

Updates `actions/upload-artifact` from 3.1.3 to 4.3.3
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](a8a3f3ad30...65462800fd)

Updates `slsa-framework/slsa-github-generator` from 1.10.0 to 2.0.0
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v1.10.0...v2.0.0)

Updates `actions/download-artifact` from 3.0.2 to 4.1.6
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](9bc31d5ccc...9c19ed7fe5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: slsa-framework/slsa-github-generator
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-23 19:33:07 +00:00
David Lord
6d6a6c2546
unignore upload/download-artifact 2024-04-23 12:32:15 -07:00
David Lord
a2438d20b0
update dev dependencies 2024-04-23 12:23:07 -07:00
David Lord
fcd3d3bbf3
drop support for Python 3.7 2024-04-23 12:22:59 -07:00
David Lord
de6131232a
Merge branch '3.1.x' 2024-04-23 12:07:24 -07:00
David Lord
3fd91e4d11
Merge branch '3.1.x' 2024-01-10 15:22:02 -08:00
David Lord
7f8fb54782
use trusted publishing 2024-01-10 14:37:08 -08:00
David Lord
d594969d72
Bump slsa-framework/slsa-github-generator from 1.7.0 to 1.9.0 (#1885) 2023-09-06 08:59:20 -07:00
David Lord
ec22f25312
Bump pypa/gh-action-pypi-publish from 1.8.8 to 1.8.10 (#1884) 2023-09-06 08:59:11 -07:00
David Lord
21fa43ca01
Bump actions/checkout from 3.5.3 to 3.6.0 (#1883) 2023-09-06 08:59:00 -07:00
dependabot[bot]
938e7ca5bb
Bump slsa-framework/slsa-github-generator from 1.7.0 to 1.9.0
Bumps [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) from 1.7.0 to 1.9.0.
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v1.7.0...v1.9.0)

---
updated-dependencies:
- dependency-name: slsa-framework/slsa-github-generator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-01 16:03:13 +00:00
dependabot[bot]
f0685845e1
Bump pypa/gh-action-pypi-publish from 1.8.8 to 1.8.10
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.8 to 1.8.10.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](f8c70e705f...b7f401de30)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-01 16:03:08 +00:00
dependabot[bot]
fcafd5087b
Bump actions/checkout from 3.5.3 to 3.6.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](c85c95e3d7...f43a0e5ff2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-01 16:03:03 +00:00
David Lord
86f28a9df0
Bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8 (#1877) 2023-08-01 09:50:36 -07:00
David Lord
f272b6d8b6
Bump actions/setup-python from 4.6.1 to 4.7.0 (#1876) 2023-08-01 09:50:28 -07:00
dependabot[bot]
9db787b566
Bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.7 to 1.8.8.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](f5622bde02...f8c70e705f)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-01 16:43:19 +00:00
dependabot[bot]
f575dc7385
Bump actions/setup-python from 4.6.1 to 4.7.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.6.1 to 4.7.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](bd6b4b6205...61a6322f88)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-01 16:43:15 +00:00
David Lord
2a2bfb7f95
Bump actions/checkout from 3.5.2 to 3.5.3 (#1867) 2023-07-01 09:21:29 -07:00
David Lord
ec2649688d
Bump slsa-framework/slsa-github-generator from 1.6.0 to 1.7.0 (#1866) 2023-07-01 09:21:14 -07:00
David Lord
859039244a
Bump dessant/lock-threads from 4.0.0 to 4.0.1 (#1865) 2023-07-01 09:21:06 -07:00
David Lord
890c8a9519
Bump pypa/gh-action-pypi-publish from 1.8.6 to 1.8.7 (#1864) 2023-07-01 09:20:56 -07:00
dependabot[bot]
505effc995
Bump actions/checkout from 3.5.2 to 3.5.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8e5e7e5ab8...c85c95e3d7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 16:13:15 +00:00
dependabot[bot]
956b1f1ce9
Bump slsa-framework/slsa-github-generator from 1.6.0 to 1.7.0
Bumps [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: slsa-framework/slsa-github-generator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 16:13:11 +00:00
dependabot[bot]
c4b8d066d6
Bump dessant/lock-threads from 4.0.0 to 4.0.1
Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md)
- [Commits](c1b35aecc5...be8aa5be94)

---
updated-dependencies:
- dependency-name: dessant/lock-threads
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 16:13:08 +00:00
dependabot[bot]
5258c9d27d
Bump pypa/gh-action-pypi-publish from 1.8.6 to 1.8.7
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.6 to 1.8.7.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](a56da0b891...f5622bde02)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 16:13:06 +00:00
David Lord
534c9e13ed
switch to flit build backend (#1863) 2023-06-28 07:44:58 -07:00
David Lord
8310b2bbef
switch to flit build backend 2023-06-28 07:31:12 -07:00
David Lord
fed2d0808f
Merge branch '3.1.x' 2023-06-27 08:01:35 -07:00
David Lord
c0e4f69ff3
Merge branch '3.1.x' 2023-06-27 07:37:41 -07:00
David Lord
7b48764688
Bump actions/setup-python from 4.6.0 to 4.6.1 (#1856) 2023-06-01 11:49:14 -07:00
David Lord
4b18fd4f1f
Bump pypa/gh-action-pypi-publish from 1.8.5 to 1.8.6 (#1855) 2023-06-01 11:47:50 -07:00
David Lord
3914664578
Bump slsa-framework/slsa-github-generator from 1.5.0 to 1.6.0 (#1857) 2023-06-01 11:46:45 -07:00
David Lord
81a23847cd
Merge branch '3.1.x' 2023-06-01 10:50:38 -07:00
dependabot[bot]
23aab8330c
Bump slsa-framework/slsa-github-generator from 1.5.0 to 1.6.0
Bumps [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: slsa-framework/slsa-github-generator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-01 16:57:05 +00:00
dependabot[bot]
7e03bef475
Bump actions/setup-python from 4.6.0 to 4.6.1
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](57ded4d7d5...bd6b4b6205)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-01 16:57:00 +00:00
dependabot[bot]
b364f26a11
Bump pypa/gh-action-pypi-publish from 1.8.5 to 1.8.6
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.5 to 1.8.6.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](0bf742be3e...a56da0b891)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-01 16:56:55 +00:00
David Lord
953acd65b2
Bump pypa/gh-action-pypi-publish from 1.8.4 to 1.8.5 (#1843) 2023-05-01 10:21:49 -07:00
David Lord
c5685b6dc4
Bump actions/checkout from 3.5.0 to 3.5.2 (#1844) 2023-05-01 10:19:49 -07:00
dependabot[bot]
7018e3fc76
Bump actions/checkout from 3.5.0 to 3.5.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8f4b7f8486...8e5e7e5ab8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 17:17:29 +00:00
David Lord
2cd9ed2ad9
Bump actions/setup-python from 4.5.0 to 4.6.0 (#1845) 2023-05-01 10:16:55 -07:00
dependabot[bot]
1e357f34ff
Bump actions/setup-python from 4.5.0 to 4.6.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](d27e3f3d7c...57ded4d7d5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 16:57:11 +00:00
dependabot[bot]
055bbfd1fe
Bump pypa/gh-action-pypi-publish from 1.8.4 to 1.8.5
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.4 to 1.8.5.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](29930c9cf5...0bf742be3e)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 16:57:04 +00:00
David Lord
090a5a9e22
fix flake8 bugbear error 2023-04-07 12:19:07 -07:00
David Lord
85e5ad85d1
Bump pypa/gh-action-pypi-publish from 1.6.4 to 1.8.4 (#1830) 2023-04-03 06:13:54 -07:00
David Lord
3067df7261
Bump actions/cache from 3.2.6 to 3.3.1 (#1829) 2023-04-03 06:13:47 -07:00
David Lord
23e5cec554
Bump actions/checkout from 3.3.0 to 3.5.0 (#1828) 2023-04-03 06:13:34 -07:00
dependabot[bot]
c48f131143
Bump pypa/gh-action-pypi-publish from 1.6.4 to 1.8.4
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.6.4 to 1.8.4.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](c7f29f7ade...29930c9cf5)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 16:57:53 +00:00
dependabot[bot]
4863d6534f
Bump actions/cache from 3.2.6 to 3.3.1
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.6 to 3.3.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](69d9d449ac...88522ab9f3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 16:57:47 +00:00
dependabot[bot]
80f4e5586a
Bump actions/checkout from 3.3.0 to 3.5.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](ac59398561...8f4b7f8486)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 16:57:43 +00:00
David Lord
9dad679695
Bump slsa-framework/slsa-github-generator from 1.4.0 to 1.5.0 (#1816) 2023-03-01 09:04:52 -08:00
David Lord
47957d571c
Bump actions/cache from 3.2.4 to 3.2.6 (#1815) 2023-03-01 09:02:41 -08:00
dependabot[bot]
7b3cb76e71
Bump slsa-framework/slsa-github-generator from 1.4.0 to 1.5.0
Bumps [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: slsa-framework/slsa-github-generator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-01 16:57:30 +00:00
dependabot[bot]
02e058df6c
Bump actions/cache from 3.2.4 to 3.2.6
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.4 to 3.2.6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](627f0f41f6...69d9d449ac)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-01 16:57:27 +00:00
David Lord
b7cb6ee667
Merge branch '3.1.x' 2023-02-07 07:20:32 -08:00
David Lord
37561cead6
Bump actions/cache from 3.2.3 to 3.2.4 (#1799) 2023-02-01 08:59:04 -08:00
David Lord
56f7c4e083
Bump actions/setup-python from 4.4.0 to 4.5.0 (#1798) 2023-02-01 08:58:53 -08:00
dependabot[bot]
c01f51b345
Bump actions/cache from 3.2.3 to 3.2.4
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](58c146cc91...627f0f41f6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 16:01:29 +00:00
dependabot[bot]
1cc0b63e2f
Bump actions/setup-python from 4.4.0 to 4.5.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](5ccb29d877...d27e3f3d7c)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 16:01:23 +00:00
David Lord
96a83e9014
Merge branch '3.1.x' 2023-01-20 13:35:30 -08:00
David Lord
05a5f8120a
switch to pyproject.toml (#1793) 2023-01-19 18:22:50 -08:00
David Lord
8ed8e1d0ed
fix mypy strict findings 2023-01-19 18:17:03 -08:00
David Lord
614b045fab
ignore bugbear opinion 2023-01-19 18:17:03 -08:00
David Lord
a9c8111d24
switch to pyproject.toml 2023-01-19 18:17:03 -08:00
David Lord
048a068697
Merge remote-tracking branch 'origin/3.1.x' 2023-01-19 17:18:57 -08:00
David Lord
89eec1c5ee
set workflow permissions 2023-01-09 14:51:55 -08:00
David Lord
495b889b7c
Merge branch '3.1.x' 2023-01-09 14:51:38 -08:00
David Lord
80e7a83235
Bump actions/cache from 3.0.11 to 3.2.2 (#1787) 2023-01-03 08:23:02 -07:00
David Lord
e46be11e3e
Bump dessant/lock-threads from 3 to 4 (#1786) 2023-01-03 08:22:53 -07:00
dependabot[bot]
45c23ea56d
Bump actions/cache from 3.0.11 to 3.2.2
Bumps [actions/cache](https://github.com/actions/cache) from 3.0.11 to 3.2.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.0.11...v3.2.2)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-01 16:01:00 +00:00
dependabot[bot]
e026c72c19
Bump dessant/lock-threads from 3 to 4
Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 3 to 4.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dessant/lock-threads/compare/v3...v4)

---
updated-dependencies:
- dependency-name: dessant/lock-threads
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-01 16:00:56 +00:00
David Lord
36b601f24b
Merge branch '3.1.x' 2022-12-29 10:39:27 -08:00
dependabot[bot]
ae53ea5350
Bump actions/cache from 3.0.9 to 3.0.11 (#1750) 2022-11-01 10:23:17 -07:00
David Lord
5d9ece6d65
Merge branch '3.1.x' 2022-10-13 09:12:06 -07:00
dependabot[bot]
e740cc65d5
Bump actions/cache from 3.0.8 to 3.0.9 (#1729) 2022-10-02 07:03:32 -07:00
dependabot[bot]
15e4959a2e
Bump actions/cache from 3.0.5 to 3.0.8 (#1713)
Bumps [actions/cache](https://github.com/actions/cache) from 3.0.5 to 3.0.8.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.0.5...v3.0.8)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 09:09:06 -07:00
Tilman Klaeger
997f7f5243
Replacing os.path.sep with os.sep in loader (#1698) 2022-08-01 17:44:14 -07:00
David Lord
7f936deac0
Merge pull request #1699 from pallets/dependabot/github_actions/actions/cache-3.0.5
Bump actions/cache from 3.0.4 to 3.0.5
2022-08-01 09:08:36 -07:00
dependabot[bot]
769921b12b
Bump actions/cache from 3.0.4 to 3.0.5
Bumps [actions/cache](https://github.com/actions/cache) from 3.0.4 to 3.0.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.0.4...v3.0.5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 16:05:50 +00:00
David Lord
7fb13bf944
Merge pull request #1679 from pallets/dependabot/github_actions/actions/cache-3.0.4
Bump actions/cache from 3.0.3 to 3.0.4
2022-07-04 15:56:42 -07:00
David Lord
32708a5ce8
Merge pull request #1678 from pallets/dependabot/github_actions/actions/setup-python-4
Bump actions/setup-python from 3 to 4
2022-07-04 15:56:36 -07:00
David Lord
94fd2d9241
Merge branch '3.1.x' 2022-07-04 07:40:11 -07:00
dependabot[bot]
bd07dfbf18
Bump actions/cache from 3.0.3 to 3.0.4
Bumps [actions/cache](https://github.com/actions/cache) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.0.3...v3.0.4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 16:06:00 +00:00
dependabot[bot]
b8b2c6b445
Bump actions/setup-python from 3 to 4
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3 to 4.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 16:05:58 +00:00
David Lord
0d177809e2
Merge pull request #1670 from pallets/dependabot/github_actions/actions/cache-3.0.3
Bump actions/cache from 3.0.2 to 3.0.3
2022-06-01 09:17:20 -07:00
dependabot[bot]
5cede152e8
Bump actions/cache from 3.0.2 to 3.0.3
Bumps [actions/cache](https://github.com/actions/cache) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.0.2...v3.0.3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 16:06:19 +00:00
David Lord
c9593aa388
Merge pull request #1662 from pallets/dependabot/github_actions/actions/cache-3.0.2
Bump actions/cache from 3.0.1 to 3.0.2
2022-05-01 10:16:36 -06:00
dependabot[bot]
47493d082c
Bump actions/cache from 3.0.1 to 3.0.2
Bumps [actions/cache](https://github.com/actions/cache) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.0.1...v3.0.2)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-01 16:02:59 +00:00
David Lord
1b714c7e82
Merge branch '3.1.x' 2022-04-28 10:22:02 -07:00
David Lord
52843b5cbf
Merge branch '3.1.x' 2022-04-28 07:08:26 -07:00
David Lord
a0dd7753d0
Merge branch '3.1.x' 2022-04-25 12:41:39 -07:00
David Lord
c3a61d6ef6
Merge branch '3.1.x' 2022-04-01 12:33:53 -07:00
David Lord
2c8e84db29
Merge pull request #1643 from pallets/dependabot/github_actions/actions/checkout-3
Bump actions/checkout from 2 to 3
2022-04-01 09:08:09 -07:00
David Lord
4f5630f43b
Merge pull request #1644 from pallets/dependabot/github_actions/actions/cache-3.0.1
Bump actions/cache from 2 to 3.0.1
2022-04-01 09:07:10 -07:00
dependabot[bot]
d6b4900742
Bump actions/cache from 2 to 3.0.1
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v2...v3.0.1)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 16:04:12 +00:00
dependabot[bot]
a7eedafa2a
Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 16:04:09 +00:00
David Lord
6f79daafe8
Merge branch '3.1.x' 2022-03-25 15:34:33 -07:00
David Lord
e542e10002
start version 3.2.0 2022-03-24 07:43:09 -07:00
58 changed files with 2128 additions and 1195 deletions

View File

@ -1,7 +1,17 @@
#!/bin/bash
set -e
python3 -m venv --upgrade-deps .venv
. .venv/bin/activate
pip install -r requirements/dev.txt
pip install -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..."
pre-commit install --install-hooks

View File

@ -10,6 +10,7 @@ on:
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: lock
jobs:

25
.github/workflows/pre-commit.yaml vendored Normal file
View File

@ -0,0 +1,25 @@
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() }}

View File

@ -1,23 +1,22 @@
name: Publish
on:
push:
tags:
- '*'
tags: ['*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
with:
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.
enable-cache: true
prune-cache: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version-file: pyproject.toml
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- run: python -m build
- uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
- run: uv build
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
path: ./dist
create-release:
@ -26,7 +25,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- name: create release
run: >
gh release create --draft --repo ${{ github.repository }}
@ -35,8 +34,6 @@ 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 }}
@ -44,7 +41,7 @@ jobs:
permissions:
id-token: write
steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
- uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
packages-dir: artifact/

View File

@ -1,10 +1,10 @@
name: Tests
on:
pull_request:
paths-ignore: ['docs/**', 'README.md']
push:
branches: [main, stable]
paths-ignore: ['docs/**', '*.md', '*.rst']
pull_request:
paths-ignore: [ 'docs/**', '*.md', '*.rst' ]
paths-ignore: ['docs/**', 'README.md']
jobs:
tests:
name: ${{ matrix.name || matrix.python }}
@ -14,38 +14,36 @@ 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'}
- {python: '3.9'}
- {python: '3.8'}
- {python: '3.7'}
- {name: PyPy, python: 'pypy-3.10', tox: pypy310}
- {name: PyPy, python: 'pypy-3.11', tox: pypy3.11}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
with:
enable-cache: true
prune-cache: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ 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) }}
- run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
typing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
with:
python-version: '3.x'
cache: pip
cache-dependency-path: requirements*/*.txt
enable-cache: true
prune-cache: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version-file: pyproject.toml
- name: cache mypy
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ./.mypy_cache
key: mypy|${{ hashFiles('pyproject.toml') }}
- run: pip install tox
- run: tox run -e typing
- run: uv run --locked tox run -e typing

2
.gitignore vendored
View File

@ -1,7 +1,5 @@
.idea/
.vscode/
.venv*/
venv*/
__pycache__/
dist/
.coverage*

View File

@ -1,13 +1,15 @@
ci:
autoupdate_schedule: monthly
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
rev: 76e47323a83cd9795e4ff9a1de1c0d2eef610f17 # frozen: v0.11.11
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: v5.0.0
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0
hooks:
- id: check-merge-conflict
- id: debug-statements

View File

@ -1,14 +1,10 @@
version: 2
build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: '3.12'
python:
install:
- requirements: requirements/docs.txt
- method: pip
path: .
sphinx:
configuration: docs/conf.py
builder: dirhtml
fail_on_warning: true
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

View File

@ -1,5 +1,20 @@
.. 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
-------------

View File

@ -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 autoescape else result
return Markup(result) if eval_ctx.autoescape else result
.. _writing-tests:

View File

@ -30,7 +30,7 @@ Installation
------------
We recommend using the latest version of Python. Jinja supports Python
3.7 and newer. We also recommend using a `virtual environment`_ in order
3.10 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

View File

@ -1,36 +1,65 @@
[project]
name = "Jinja2"
version = "3.2.0.dev"
description = "A very fast and expressive template engine."
readme = "README.md"
license = {file = "LICENSE.txt"}
license = "BSD-3-Clause"
license-files = ["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.7"
dependencies = ["MarkupSafe>=2.0"]
dynamic = ["version"]
requires-python = ">=3.10"
dependencies = ["MarkupSafe>=3.0"]
[project.urls]
Donate = "https://palletsprojects.com/donate"
Documentation = "https://jinja.palletsprojects.com/"
Changes = "https://jinja.palletsprojects.com/changes/"
Changes = "https://jinja.palletsprojects.com/page/changes/"
Source = "https://github.com/pallets/jinja/"
Chat = "https://discord.gg/pallets"
[project.optional-dependencies]
i18n = ["Babel>=2.7"]
i18n = ["Babel>=2.17"]
[project.entry-points."babel.extractors"]
jinja2 = "jinja2.ext:babel_extract[i18n]"
[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",
]
[build-system]
requires = ["flit_core<4"]
@ -42,15 +71,18 @@ name = "jinja2"
[tool.flit.sdist]
include = [
"docs/",
"requirements/",
"examples/",
"tests/",
"CHANGES.md",
"tox.ini",
"CHANGES.rst",
"uv.lock"
]
exclude = [
"docs/_build/",
]
[tool.uv]
default-groups = ["dev", "pre-commit", "tests", "typing"]
[tool.pytest.ini_options]
testpaths = ["tests"]
filterwarnings = [
@ -64,17 +96,24 @@ 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.8"
files = ["src/jinja2"]
python_version = "3.10"
files = ["src"]
show_error_codes = true
pretty = true
strict = true
[tool.pyright]
pythonVersion = "3.8"
include = ["src/jinja2"]
typeCheckingMode = "basic"
pythonVersion = "3.10"
include = ["src"]
typeCheckingMode = "standard"
[tool.ruff]
src = ["src"]
@ -91,6 +130,9 @@ select = [
"UP", # pyupgrade
"W", # pycodestyle warning
]
ignore = [
"UP038", # keep isinstance tuple
]
[tool.ruff.lint.isort]
force-single-line = true
@ -100,3 +142,70 @@ 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}]]

View File

@ -1 +0,0 @@
build

View File

@ -1,12 +0,0 @@
#
# 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

View File

@ -1,6 +0,0 @@
-r docs.in
-r tests.in
-r typing.in
pip-compile-multi
pre-commit
tox

View File

@ -1,151 +0,0 @@
#
# 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

View File

@ -1,4 +0,0 @@
Pallets-Sphinx-Themes
Sphinx
sphinx-issues
sphinxcontrib-log-cabinet

View File

@ -1,63 +0,0 @@
#
# 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

View File

@ -1,2 +0,0 @@
pytest
trio

View File

@ -1,28 +0,0 @@
#
# 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

View File

@ -1,43 +0,0 @@
#
# 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

View File

@ -1 +0,0 @@
mypy

View File

@ -1,12 +0,0 @@
#
# 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

View File

@ -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]):
b = list(g)
yield b[0][1], b[-1][1]
lb = list(g)
yield lb[0][1], lb[-1][1]
def build_pattern(ranges):
@ -54,17 +54,16 @@ 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")

View File

@ -3,6 +3,10 @@ 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
@ -35,4 +39,19 @@ from .utils import pass_environment as pass_environment
from .utils import pass_eval_context as pass_eval_context
from .utils import select_autoescape as select_autoescape
__version__ = "3.1.6"
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)

View File

@ -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
)

View File

@ -85,7 +85,7 @@ class _IteratorToAsyncIterator(t.Generic[V]):
def auto_aiter(
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
iterable: "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.Union[t.AsyncIterable[V], t.Iterable[V]]",
) -> t.List["V"]:
value: "t.AsyncIterable[V] | t.Iterable[V]",
) -> list["V"]:
return [x async for x in auto_aiter(value)]

View File

@ -27,9 +27,7 @@ if t.TYPE_CHECKING:
class _MemcachedClient(te.Protocol):
def get(self, key: str) -> bytes: ...
def set(
self, key: str, value: bytes, timeout: t.Optional[int] = None
) -> None: ...
def set(self, key: str, value: bytes, timeout: int | None = None) -> None: ...
bc_version = 5
@ -60,7 +58,7 @@ class Bucket:
def reset(self) -> None:
"""Resets the bucket (unloads the bytecode)."""
self.code: t.Optional[CodeType] = None
self.code: CodeType | None = None
def load_bytecode(self, f: t.BinaryIO) -> None:
"""Loads bytecode from a file or file like object."""
@ -149,9 +147,7 @@ class BytecodeCache:
by a particular environment.
"""
def get_cache_key(
self, name: str, filename: t.Optional[t.Union[str]] = None
) -> str:
def get_cache_key(self, name: str, filename: str | None = None) -> str:
"""Returns the unique hash key for this template name."""
hash = sha1(name.encode("utf-8"))
@ -168,7 +164,7 @@ class BytecodeCache:
self,
environment: "Environment",
name: str,
filename: t.Optional[str],
filename: str | None,
source: str,
) -> Bucket:
"""Return a cache bucket for the given template. All arguments are
@ -204,7 +200,7 @@ class FileSystemBytecodeCache(BytecodeCache):
"""
def __init__(
self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
self, directory: str | None = None, pattern: str = "__jinja2_%s.cache"
) -> None:
if directory is None:
directory = self._get_default_cache_dir()
@ -377,7 +373,7 @@ class MemcachedBytecodeCache(BytecodeCache):
self,
client: "_MemcachedClient",
prefix: str = "jinja2/bytecode/",
timeout: t.Optional[int] = None,
timeout: int | None = None,
ignore_memcache_errors: bool = True,
):
self.client = client

View File

@ -101,12 +101,12 @@ def _make_unop(
def generate(
node: nodes.Template,
environment: "Environment",
name: t.Optional[str],
filename: t.Optional[str],
stream: t.Optional[t.TextIO] = None,
name: str | None,
filename: str | None,
stream: t.TextIO | None = None,
defer_init: bool = False,
optimized: bool = True,
) -> t.Optional[str]:
) -> str | None:
"""Generate the python source for a node tree."""
if not isinstance(node, nodes.Template):
raise TypeError("Can't compile non template nodes")
@ -139,9 +139,7 @@ def has_safe_repr(value: t.Any) -> bool:
return False
def find_undeclared(
nodes: t.Iterable[nodes.Node], names: t.Iterable[str]
) -> t.Set[str]:
def find_undeclared(nodes: t.Iterable[nodes.Node], names: t.Iterable[str]) -> 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.
"""
@ -155,7 +153,7 @@ def find_undeclared(
class MacroRef:
def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None:
def __init__(self, node: nodes.Macro | nodes.CallBlock) -> None:
self.node = node
self.accesses_caller = False
self.accesses_kwargs = False
@ -169,7 +167,7 @@ class Frame:
self,
eval_ctx: EvalContext,
parent: t.Optional["Frame"] = None,
level: t.Optional[int] = None,
level: int | None = None,
) -> None:
self.eval_ctx = eval_ctx
@ -187,10 +185,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: t.Optional[str] = None
self.buffer: str | None = None
# the name of the block we're in, otherwise None.
self.block: t.Optional[str] = None
self.block: str | None = None
else:
self.symbols = Symbols(parent.symbols, level=level)
@ -253,8 +251,8 @@ class DependencyFinderVisitor(NodeVisitor):
"""A visitor that collects filter and test calls."""
def __init__(self) -> None:
self.filters: t.Set[str] = set()
self.tests: t.Set[str] = set()
self.filters: set[str] = set()
self.tests: set[str] = set()
def visit_Filter(self, node: nodes.Filter) -> None:
self.generic_visit(node)
@ -276,7 +274,7 @@ class UndeclaredNameVisitor(NodeVisitor):
def __init__(self, names: t.Iterable[str]) -> None:
self.names = set(names)
self.undeclared: t.Set[str] = set()
self.undeclared: set[str] = set()
def visit_Name(self, node: nodes.Name) -> None:
if node.ctx == "load" and node.name in self.names:
@ -301,9 +299,9 @@ class CodeGenerator(NodeVisitor):
def __init__(
self,
environment: "Environment",
name: t.Optional[str],
filename: t.Optional[str],
stream: t.Optional[t.TextIO] = None,
name: str | None,
filename: str | None,
stream: t.TextIO | None = None,
defer_init: bool = False,
optimized: bool = True,
) -> None:
@ -315,17 +313,17 @@ class CodeGenerator(NodeVisitor):
self.stream = stream
self.created_block_context = False
self.defer_init = defer_init
self.optimizer: t.Optional[Optimizer] = None
self.optimizer: Optimizer | None = None
if optimized:
self.optimizer = Optimizer(environment)
# aliases for imports
self.import_aliases: t.Dict[str, str] = {}
self.import_aliases: dict[str, str] = {}
# a registry for all blocks. Because blocks are moved out
# into the global python scope they are registered here
self.blocks: t.Dict[str, nodes.Block] = {}
self.blocks: dict[str, nodes.Block] = {}
# the number of extends statements so far
self.extends_so_far = 0
@ -339,12 +337,12 @@ class CodeGenerator(NodeVisitor):
self.code_lineno = 1
# registry of all filters and tests (global, not block local)
self.tests: t.Dict[str, str] = {}
self.filters: t.Dict[str, str] = {}
self.tests: dict[str, str] = {}
self.filters: dict[str, str] = {}
# the debug information
self.debug_info: t.List[t.Tuple[int, int]] = []
self._write_debug_info: t.Optional[int] = None
self.debug_info: list[tuple[int, int]] = []
self._write_debug_info: int | None = None
# the number of new lines before the next write()
self._new_lines = 0
@ -363,10 +361,10 @@ class CodeGenerator(NodeVisitor):
self._indentation = 0
# Tracks toplevel assignments
self._assign_stack: t.List[t.Set[str]] = []
self._assign_stack: list[set[str]] = []
# Tracks parameter definition blocks
self._param_def_block: t.List[t.Set[str]] = []
self._param_def_block: list[set[str]] = []
# Tracks the current context.
self._context_reference_stack = ["context"]
@ -419,7 +417,7 @@ class CodeGenerator(NodeVisitor):
"""Outdent by step."""
self._indentation -= step
def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None:
def start_write(self, frame: Frame, node: nodes.Node | None = None) -> None:
"""Yield or write into the frame buffer."""
if frame.buffer is None:
self.writeline("yield ", node)
@ -432,7 +430,7 @@ class CodeGenerator(NodeVisitor):
self.write(")")
def simple_write(
self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None
self, s: str, frame: Frame, node: nodes.Node | None = None
) -> None:
"""Simple shortcut for start_write + write + end_write."""
self.start_write(frame, node)
@ -464,14 +462,12 @@ class CodeGenerator(NodeVisitor):
self._new_lines = 0
self.stream.write(x)
def writeline(
self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0
) -> None:
def writeline(self, x: str, node: nodes.Node | None = None, extra: int = 0) -> None:
"""Combination of newline and write."""
self.newline(node, extra)
self.write(x)
def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None:
def newline(self, node: nodes.Node | None = 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:
@ -480,9 +476,9 @@ class CodeGenerator(NodeVisitor):
def signature(
self,
node: t.Union[nodes.Call, nodes.Filter, nodes.Test],
node: nodes.Call | nodes.Filter | nodes.Test,
frame: Frame,
extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
extra_kwargs: t.Mapping[str, t.Any] | None = None,
) -> None:
"""Writes a function call to the stream for the current node.
A leading comma is added automatically. The extra keyword
@ -612,8 +608,8 @@ class CodeGenerator(NodeVisitor):
return f"{self.choose_async()}def {name}"
def macro_body(
self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame
) -> t.Tuple[Frame, MacroRef]:
self, node: nodes.Macro | nodes.CallBlock, frame: Frame
) -> tuple[Frame, MacroRef]:
"""Dump the function def of a macro or call block."""
frame = frame.inner()
frame.symbols.analyze_node(node)
@ -826,9 +822,7 @@ class CodeGenerator(NodeVisitor):
# -- Statement Visitors
def visit_Template(
self, node: nodes.Template, frame: t.Optional[Frame] = None
) -> None:
def visit_Template(self, node: nodes.Template, frame: Frame | None = None) -> None:
assert frame is None, "no root frame allowed"
eval_ctx = EvalContext(self.environment, self.name)
@ -1098,7 +1092,7 @@ class CodeGenerator(NodeVisitor):
self.outdent()
def _import_common(
self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame
self, node: nodes.Import | nodes.FromImport, frame: Frame
) -> None:
self.write(f"{self.choose_async('await ')}environment.get_template(")
self.visit(node.template, frame)
@ -1371,7 +1365,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):
for target, expr in zip(node.targets, node.values, strict=False):
self.newline()
self.visit(target, with_frame)
self.write(" = ")
@ -1384,8 +1378,8 @@ class CodeGenerator(NodeVisitor):
self.visit(node.node, frame)
class _FinalizeInfo(t.NamedTuple):
const: t.Optional[t.Callable[..., str]]
src: t.Optional[str]
const: t.Callable[..., str] | None
src: str | None
@staticmethod
def _default_finalize(value: t.Any) -> t.Any:
@ -1395,7 +1389,7 @@ class CodeGenerator(NodeVisitor):
"""
return str(value)
_finalize: t.Optional[_FinalizeInfo] = None
_finalize: _FinalizeInfo | None = None
def _make_finalize(self) -> _FinalizeInfo:
"""Build the finalize function to be used on constants and at
@ -1413,7 +1407,7 @@ class CodeGenerator(NodeVisitor):
if self._finalize is not None:
return self._finalize
finalize: t.Optional[t.Callable[..., t.Any]]
finalize: t.Callable[..., t.Any] | None
finalize = default = self._default_finalize
src = None
@ -1511,7 +1505,7 @@ class CodeGenerator(NodeVisitor):
self.indent()
finalize = self._make_finalize()
body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = []
body: list[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
@ -1586,7 +1580,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: t.Set[str] = set()
seen_refs: set[str] = set()
for nsref in node.find_all(nodes.NSRef):
if nsref.name in seen_refs:
@ -1793,7 +1787,7 @@ class CodeGenerator(NodeVisitor):
@contextmanager
def _filter_test_common(
self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool
self, node: nodes.Filter | nodes.Test, frame: Frame, is_filter: bool
) -> t.Iterator[None]:
if self.environment.is_async:
self.write("(await auto_await(")

View File

@ -11,7 +11,7 @@ if t.TYPE_CHECKING:
from .runtime import Context
def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
def rewrite_traceback_stack(source: str | None = 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: t.Optional[str] = None) -> BaseException:
def fake_traceback( # type: ignore
exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
exc_value: BaseException, tb: TracebackType | None, 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,26 +118,7 @@ def fake_traceback( # type: ignore
elif function.startswith("block_"):
location = f"block {function[6:]!r}"
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,
)
code = code.replace(co_name=location)
# Execute the new code, which is guaranteed to raise, and return
# the new traceback without this frame.
@ -147,15 +128,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]) -> t.Dict[str, t.Any]:
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> dict[str, t.Any]:
"""Based on the runtime locals, get the context that would be
available at that point in the template.
"""
# Start with the current template context.
ctx: t.Optional[Context] = real_locals.get("context")
ctx: Context | None = real_locals.get("context")
if ctx is not None:
data: t.Dict[str, t.Any] = ctx.get_all().copy()
data: dict[str, t.Any] = ctx.get_all().copy()
else:
data = {}
@ -163,7 +144,7 @@ def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.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: t.Dict[str, t.Tuple[int, t.Any]] = {}
local_overrides: dict[str, tuple[int, t.Any]] = {}
for name, value in real_locals.items():
if not name.startswith("l_") or value is missing:

View File

@ -17,8 +17,8 @@ VARIABLE_START_STRING = "{{"
VARIABLE_END_STRING = "}}"
COMMENT_START_STRING = "{#"
COMMENT_END_STRING = "#}"
LINE_STATEMENT_PREFIX: t.Optional[str] = None
LINE_COMMENT_PREFIX: t.Optional[str] = None
LINE_STATEMENT_PREFIX: str | None = None
LINE_COMMENT_PREFIX: str | None = 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: t.Dict[str, t.Any] = {
DEFAULT_POLICIES: dict[str, t.Any] = {
"compiler.ascii_str": True,
"urlize.rel": "noopener",
"urlize.target": None,

View File

@ -7,6 +7,7 @@ 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
@ -66,7 +67,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: t.Type[_env_bound], *args: t.Any) -> _env_bound:
def get_spontaneous_environment(cls: 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.
@ -81,7 +82,7 @@ def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_b
def create_cache(
size: int,
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
) -> t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None:
"""Return the cache class for the given size."""
if size == 0:
return None
@ -93,8 +94,8 @@ def create_cache(
def copy_cache(
cache: t.Optional[t.MutableMapping[t.Any, t.Any]],
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
cache: t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None,
) -> t.MutableMapping[tuple["weakref.ref[BaseLoader]", str], "Template"] | None:
"""Create an empty copy of the given cache."""
if cache is None:
return None
@ -107,8 +108,8 @@ def copy_cache(
def load_extensions(
environment: "Environment",
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]],
) -> t.Dict[str, "Extension"]:
extensions: t.Sequence[str | type["Extension"]],
) -> dict[str, "Extension"]:
"""Load the extensions from the list and bind it to the environment.
Returns a dict of instantiated extensions.
"""
@ -116,7 +117,7 @@ def load_extensions(
for extension in extensions:
if isinstance(extension, str):
extension = t.cast(t.Type["Extension"], import_string(extension))
extension = t.cast(type["Extension"], import_string(extension))
result[extension.identifier] = extension(environment)
@ -125,9 +126,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
@ -281,15 +282,15 @@ class Environment:
#: the class that is used for code generation. See
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
code_generator_class: t.Type["CodeGenerator"] = CodeGenerator
code_generator_class: type["CodeGenerator"] = CodeGenerator
concat = "".join
#: the context class that is used for templates. See
#: :class:`~jinja2.runtime.Context` for more information.
context_class: t.Type[Context] = Context
context_class: type[Context] = Context
template_class: t.Type["Template"]
template_class: type["Template"]
def __init__(
self,
@ -299,17 +300,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: t.Optional[str] = LINE_STATEMENT_PREFIX,
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
line_statement_prefix: str | None = LINE_STATEMENT_PREFIX,
line_comment_prefix: str | None = LINE_COMMENT_PREFIX,
trim_blocks: bool = TRIM_BLOCKS,
lstrip_blocks: bool = LSTRIP_BLOCKS,
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
extensions: t.Sequence[str | type["Extension"]] = (),
optimized: bool = True,
undefined: t.Type[Undefined] = Undefined,
finalize: t.Optional[t.Callable[..., t.Any]] = None,
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
undefined: type[Undefined] = Undefined,
finalize: t.Callable[..., t.Any] | None = None,
autoescape: bool | t.Callable[[str | None], bool] = False,
loader: t.Optional["BaseLoader"] = None,
cache_size: int = 400,
auto_reload: bool = True,
@ -342,7 +343,7 @@ class Environment:
self.keep_trailing_newline = keep_trailing_newline
# runtime information
self.undefined: t.Type[Undefined] = undefined
self.undefined: type[Undefined] = undefined
self.optimized = optimized
self.finalize = finalize
self.autoescape = autoescape
@ -367,7 +368,7 @@ class Environment:
self.is_async = enable_async
_environment_config_check(self)
def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None:
def add_extension(self, extension: str | type["Extension"]) -> None:
"""Adds an extension after the environment was created.
.. versionadded:: 2.5
@ -391,17 +392,17 @@ class Environment:
variable_end_string: str = missing,
comment_start_string: str = missing,
comment_end_string: str = missing,
line_statement_prefix: t.Optional[str] = missing,
line_comment_prefix: t.Optional[str] = missing,
line_statement_prefix: str | None = missing,
line_comment_prefix: str | None = missing,
trim_blocks: bool = missing,
lstrip_blocks: bool = missing,
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
keep_trailing_newline: bool = missing,
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
extensions: t.Sequence[str | type["Extension"]] = missing,
optimized: bool = missing,
undefined: t.Type[Undefined] = missing,
finalize: t.Optional[t.Callable[..., t.Any]] = missing,
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing,
undefined: type[Undefined] = missing,
finalize: t.Callable[..., t.Any] | None = missing,
autoescape: bool | t.Callable[[str | None], bool] = missing,
loader: t.Optional["BaseLoader"] = missing,
cache_size: int = missing,
auto_reload: bool = missing,
@ -463,9 +464,7 @@ 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: t.Union[str, t.Any]
) -> t.Union[t.Any, Undefined]:
def getitem(self, obj: t.Any, argument: str | t.Any) -> t.Any | Undefined:
"""Get an item or attribute of an object but prefer the item."""
try:
return obj[argument]
@ -497,12 +496,12 @@ class Environment:
def _filter_test_common(
self,
name: t.Union[str, Undefined],
name: str | Undefined,
value: t.Any,
args: t.Optional[t.Sequence[t.Any]],
kwargs: t.Optional[t.Mapping[str, t.Any]],
context: t.Optional[Context],
eval_ctx: t.Optional[EvalContext],
args: t.Sequence[t.Any] | None,
kwargs: t.Mapping[str, t.Any] | None,
context: Context | None,
eval_ctx: EvalContext | None,
is_filter: bool,
) -> t.Any:
if is_filter:
@ -553,10 +552,10 @@ class Environment:
self,
name: str,
value: t.Any,
args: t.Optional[t.Sequence[t.Any]] = None,
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
context: t.Optional[Context] = None,
eval_ctx: t.Optional[EvalContext] = None,
args: t.Sequence[t.Any] | None = None,
kwargs: t.Mapping[str, t.Any] | None = None,
context: Context | None = None,
eval_ctx: EvalContext | None = None,
) -> t.Any:
"""Invoke a filter on a value the same way the compiler does.
@ -574,10 +573,10 @@ class Environment:
self,
name: str,
value: t.Any,
args: t.Optional[t.Sequence[t.Any]] = None,
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
context: t.Optional[Context] = None,
eval_ctx: t.Optional[EvalContext] = None,
args: t.Sequence[t.Any] | None = None,
kwargs: t.Mapping[str, t.Any] | None = None,
context: Context | None = None,
eval_ctx: EvalContext | None = None,
) -> t.Any:
"""Invoke a test on a value the same way the compiler does.
@ -599,8 +598,8 @@ class Environment:
def parse(
self,
source: str,
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
name: str | None = None,
filename: str | None = 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
@ -616,7 +615,7 @@ class Environment:
self.handle_exception(source=source)
def _parse(
self, source: str, name: t.Optional[str], filename: t.Optional[str]
self, source: str, name: str | None, filename: str | None
) -> nodes.Template:
"""Internal parsing function used by `parse` and `compile`."""
return Parser(self, source, name, filename).parse()
@ -624,9 +623,9 @@ class Environment:
def lex(
self,
source: str,
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
) -> t.Iterator[t.Tuple[int, str, str]]:
name: str | None = None,
filename: str | None = None,
) -> t.Iterator[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>`
@ -645,8 +644,8 @@ class Environment:
def preprocess(
self,
source: str,
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
name: str | None = None,
filename: str | None = None,
) -> str:
"""Preprocesses the source with all extensions. This is automatically
called for all parsing and compiling methods but *not* for :meth:`lex`
@ -661,9 +660,9 @@ class Environment:
def _tokenize(
self,
source: str,
name: t.Optional[str],
filename: t.Optional[str] = None,
state: t.Optional[str] = None,
name: str | None,
filename: str | None = None,
state: str | None = None,
) -> TokenStream:
"""Called by the parser to do the preprocessing and filtering
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
@ -682,8 +681,8 @@ class Environment:
def _generate(
self,
source: nodes.Template,
name: t.Optional[str],
filename: t.Optional[str],
name: str | None,
filename: str | None,
defer_init: bool = False,
) -> str:
"""Internal hook that can be overridden to hook a different generate
@ -711,9 +710,9 @@ class Environment:
@typing.overload
def compile(
self,
source: t.Union[str, nodes.Template],
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
source: str | nodes.Template,
name: str | None = None,
filename: str | None = None,
raw: "te.Literal[False]" = False,
defer_init: bool = False,
) -> CodeType: ...
@ -721,9 +720,9 @@ class Environment:
@typing.overload
def compile(
self,
source: t.Union[str, nodes.Template],
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
source: str | nodes.Template,
name: str | None = None,
filename: str | None = None,
raw: "te.Literal[True]" = ...,
defer_init: bool = False,
) -> str: ...
@ -731,12 +730,12 @@ class Environment:
@internalcode
def compile(
self,
source: t.Union[str, nodes.Template],
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
source: str | nodes.Template,
name: str | None = None,
filename: str | None = None,
raw: bool = False,
defer_init: bool = False,
) -> t.Union[str, CodeType]:
) -> 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.
@ -818,10 +817,10 @@ class Environment:
def compile_templates(
self,
target: t.Union[str, "os.PathLike[str]"],
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,
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,
ignore_errors: bool = True,
) -> None:
"""Finds all the templates the loader can find, compiles them
@ -898,9 +897,9 @@ class Environment:
def list_templates(
self,
extensions: t.Optional[t.Collection[str]] = None,
filter_func: t.Optional[t.Callable[[str], bool]] = None,
) -> t.List[str]:
extensions: t.Collection[str] | None = None,
filter_func: t.Callable[[str], bool] | None = None,
) -> list[str]:
"""Returns a list of templates for this environment. This requires
that the loader supports the loader's
:meth:`~BaseLoader.list_templates` method.
@ -933,7 +932,7 @@ class Environment:
return names
def handle_exception(self, source: t.Optional[str] = None) -> "te.NoReturn":
def handle_exception(self, source: str | None = None) -> "te.NoReturn":
"""Exception handling helper. This is used internally to either raise
rewritten exceptions or return a rendered traceback for the template.
"""
@ -955,7 +954,7 @@ class Environment:
@internalcode
def _load_template(
self, name: str, globals: t.Optional[t.MutableMapping[str, t.Any]]
self, name: str, globals: t.MutableMapping[str, t.Any] | None
) -> "Template":
if self.loader is None:
raise TypeError("no loader for this environment specified")
@ -982,8 +981,8 @@ class Environment:
def get_template(
self,
name: t.Union[str, "Template"],
parent: t.Optional[str] = None,
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
parent: str | None = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
"""Load a template by name with :attr:`loader` and return a
:class:`Template`. If the template does not exist a
@ -1019,8 +1018,8 @@ class Environment:
def select_template(
self,
names: t.Iterable[t.Union[str, "Template"]],
parent: t.Optional[str] = None,
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
parent: str | None = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
"""Like :meth:`get_template`, but tries loading multiple names.
If none of the names can be loaded a :exc:`TemplatesNotFound`
@ -1072,11 +1071,9 @@ class Environment:
@internalcode
def get_or_select_template(
self,
template_name_or_list: t.Union[
str, "Template", t.List[t.Union[str, "Template"]]
],
parent: t.Optional[str] = None,
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
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":
"""Use :meth:`select_template` if an iterable of template names
is given, or :meth:`get_template` if one name is given.
@ -1091,9 +1088,9 @@ class Environment:
def from_string(
self,
source: t.Union[str, nodes.Template],
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
template_class: t.Optional[t.Type["Template"]] = None,
source: str | nodes.Template,
globals: t.MutableMapping[str, t.Any] | None = None,
template_class: type["Template"] | None = None,
) -> "Template":
"""Load a template from a source string without using
:attr:`loader`.
@ -1111,7 +1108,7 @@ class Environment:
return cls.from_code(self, self.compile(source), gs, None)
def make_globals(
self, d: t.Optional[t.MutableMapping[str, t.Any]]
self, d: t.MutableMapping[str, t.Any] | None
) -> t.MutableMapping[str, t.Any]:
"""Make the globals map for a template. Any given template
globals overlay the environment :attr:`globals`.
@ -1152,38 +1149,38 @@ class Template:
#: Type of environment to create when creating a template directly
#: rather than through an existing environment.
environment_class: t.Type[Environment] = Environment
environment_class: type[Environment] = Environment
environment: Environment
globals: t.MutableMapping[str, t.Any]
name: t.Optional[str]
filename: t.Optional[str]
blocks: t.Dict[str, t.Callable[[Context], t.Iterator[str]]]
name: str | None
filename: str | None
blocks: dict[str, t.Callable[[Context], t.Iterator[str]]]
root_render_func: t.Callable[[Context], t.Iterator[str]]
_module: t.Optional["TemplateModule"]
_debug_info: str
_uptodate: t.Optional[t.Callable[[], bool]]
_uptodate: t.Callable[[], bool] | None
def __new__(
cls,
source: t.Union[str, nodes.Template],
source: str | nodes.Template,
block_start_string: str = BLOCK_START_STRING,
block_end_string: str = BLOCK_END_STRING,
variable_start_string: str = VARIABLE_START_STRING,
variable_end_string: str = VARIABLE_END_STRING,
comment_start_string: str = COMMENT_START_STRING,
comment_end_string: str = COMMENT_END_STRING,
line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
line_statement_prefix: str | None = LINE_STATEMENT_PREFIX,
line_comment_prefix: str | None = LINE_COMMENT_PREFIX,
trim_blocks: bool = TRIM_BLOCKS,
lstrip_blocks: bool = LSTRIP_BLOCKS,
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
extensions: t.Sequence[str | type["Extension"]] = (),
optimized: bool = True,
undefined: t.Type[Undefined] = Undefined,
finalize: t.Optional[t.Callable[..., t.Any]] = None,
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
undefined: type[Undefined] = Undefined,
finalize: t.Callable[..., t.Any] | None = None,
autoescape: bool | t.Callable[[str | None], bool] = False,
enable_async: bool = False,
) -> t.Any: # it returns a `Template`, but this breaks the sphinx build...
env = get_spontaneous_environment(
@ -1219,7 +1216,7 @@ class Template:
environment: Environment,
code: CodeType,
globals: t.MutableMapping[str, t.Any],
uptodate: t.Optional[t.Callable[[], bool]] = None,
uptodate: t.Callable[[], bool] | None = 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.
@ -1334,7 +1331,7 @@ class Template:
if self.environment.is_async:
import asyncio
async def to_list() -> t.List[str]:
async def to_list() -> list[str]:
return [x async for x in self.generate_async(*args, **kwargs)]
yield from asyncio.run(to_list())
@ -1361,22 +1358,19 @@ class Template:
ctx = self.new_context(dict(*args, **kwargs))
try:
agen = self.root_render_func(ctx)
try:
async for event in agen: # type: ignore
agen: t.AsyncGenerator[str, None] = self.root_render_func(ctx) # type: ignore[assignment]
async with aclosing(agen):
async for event in agen:
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: t.Optional[t.Dict[str, t.Any]] = None,
vars: dict[str, t.Any] | None = None,
shared: bool = False,
locals: t.Optional[t.Mapping[str, t.Any]] = None,
locals: t.Mapping[str, t.Any] | None = None,
) -> Context:
"""Create a new :class:`Context` for this template. The vars
provided will be passed to the template. Per default the globals
@ -1391,9 +1385,9 @@ class Template:
def make_module(
self,
vars: t.Optional[t.Dict[str, t.Any]] = None,
vars: dict[str, t.Any] | None = None,
shared: bool = False,
locals: t.Optional[t.Mapping[str, t.Any]] = None,
locals: t.Mapping[str, t.Any] | None = None,
) -> "TemplateModule":
"""This method works like the :attr:`module` attribute when called
without arguments but it will evaluate the template on every call
@ -1406,9 +1400,9 @@ class Template:
async def make_module_async(
self,
vars: t.Optional[t.Dict[str, t.Any]] = None,
vars: dict[str, t.Any] | None = None,
shared: bool = False,
locals: t.Optional[t.Mapping[str, t.Any]] = None,
locals: t.Mapping[str, t.Any] | None = None,
) -> "TemplateModule":
"""As template module creation can invoke template code for
asynchronous executions this method must be used instead of the
@ -1423,7 +1417,7 @@ class Template:
)
@internalcode
def _get_default_module(self, ctx: t.Optional[Context] = None) -> "TemplateModule":
def _get_default_module(self, ctx: Context | None = 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
@ -1450,7 +1444,7 @@ class Template:
return self._module
async def _get_default_module_async(
self, ctx: t.Optional[Context] = None
self, ctx: Context | None = None
) -> "TemplateModule":
if ctx is not None:
keys = ctx.globals_keys - self.globals.keys()
@ -1496,7 +1490,7 @@ class Template:
return self._uptodate()
@property
def debug_info(self) -> t.List[t.Tuple[int, int]]:
def debug_info(self) -> list[tuple[int, int]]:
"""The debug info mapping."""
if self._debug_info:
return [
@ -1524,7 +1518,7 @@ class TemplateModule:
self,
template: Template,
context: Context,
body_stream: t.Optional[t.Iterable[str]] = None,
body_stream: t.Iterable[str] | None = None,
) -> None:
if body_stream is None:
if context.environment.is_async:
@ -1564,7 +1558,7 @@ class TemplateExpression:
self._template = template
self._undefined_to_none = undefined_to_none
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any | None:
context = self._template.new_context(dict(*args, **kwargs))
consume(self._template.root_render_func(context))
rv = context.vars["result"]
@ -1590,9 +1584,9 @@ class TemplateStream:
def dump(
self,
fp: t.Union[str, t.IO[bytes]],
encoding: t.Optional[str] = None,
errors: t.Optional[str] = "strict",
fp: str | t.IO[bytes],
encoding: str | None = None,
errors: str | None = "strict",
) -> None:
"""Dump the complete stream into a file or file-like object.
Per default strings are written, if you want to encode
@ -1634,7 +1628,7 @@ class TemplateStream:
self.buffered = False
def _buffered_generator(self, size: int) -> t.Iterator[str]:
buf: t.List[str] = []
buf: list[str] = []
c_size = 0
push = buf.append

View File

@ -7,11 +7,11 @@ if t.TYPE_CHECKING:
class TemplateError(Exception):
"""Baseclass for all template errors."""
def __init__(self, message: t.Optional[str] = None) -> None:
def __init__(self, message: str | None = None) -> None:
super().__init__(message)
@property
def message(self) -> t.Optional[str]:
def message(self) -> str | None:
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: t.Optional[str] = None
message: str | None = None
def __init__(
self,
name: t.Optional[t.Union[str, "Undefined"]],
message: t.Optional[str] = None,
name: t.Union[str, "Undefined"] | None,
message: str | None = None,
) -> None:
IOError.__init__(self, name)
@ -65,7 +65,7 @@ class TemplatesNotFound(TemplateNotFound):
def __init__(
self,
names: t.Sequence[t.Union[str, "Undefined"]] = (),
message: t.Optional[str] = None,
message: str | None = None,
) -> None:
if message is None:
from .runtime import Undefined
@ -92,14 +92,14 @@ class TemplateSyntaxError(TemplateError):
self,
message: str,
lineno: int,
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
name: str | None = None,
filename: str | None = None,
) -> None:
super().__init__(message)
self.lineno = lineno
self.name = name
self.filename = filename
self.source: t.Optional[str] = None
self.source: str | None = None
# this is set to True if the debug.translate_syntax_error
# function translated the syntax error into a new traceback

View File

@ -37,12 +37,12 @@ if t.TYPE_CHECKING:
self, context: str, singular: str, plural: str, n: int
) -> str: ...
_SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
_SupportedTranslations = _TranslationsBasic | _TranslationsContext
# I18N functions available in Jinja templates. If the I18N library
# provides ugettext, it will be assigned to gettext.
GETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
GETTEXT_FUNCTIONS: 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: t.Set[str] = set()
tags: 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: t.Optional[str], filename: t.Optional[str] = None
self, source: str, name: str | None, filename: str | None = 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") -> t.Union[nodes.Node, t.List[nodes.Node]]:
def parse(self, parser: "Parser") -> nodes.Node | list[nodes.Node]:
"""If any of the :attr:`tags` matched this method is called with the
parser as first argument. The token the parser stream is pointing at
is the name token that matched. This method has to return one or a
@ -123,9 +123,7 @@ class Extension:
"""
raise NotImplementedError()
def attr(
self, name: str, lineno: t.Optional[int] = None
) -> nodes.ExtensionAttribute:
def attr(self, name: str, lineno: int | None = None) -> nodes.ExtensionAttribute:
"""Return an attribute node for the current extension. This is useful
to pass constants on extensions to generated template code.
@ -138,11 +136,11 @@ class Extension:
def call_method(
self,
name: str,
args: t.Optional[t.List[nodes.Expr]] = None,
kwargs: t.Optional[t.List[nodes.Keyword]] = None,
dyn_args: t.Optional[nodes.Expr] = None,
dyn_kwargs: t.Optional[nodes.Expr] = None,
lineno: t.Optional[int] = None,
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,
) -> nodes.Call:
"""Call a method of the extension. This is a shortcut for
:meth:`attr` + :class:`jinja2.nodes.Call`.
@ -164,7 +162,7 @@ class Extension:
@pass_context
def _gettext_alias(
__context: Context, *args: t.Any, **kwargs: t.Any
) -> t.Union[t.Any, Undefined]:
) -> t.Any | Undefined:
return __context.call(__context.resolve("gettext"), *args, **kwargs)
@ -268,7 +266,7 @@ class InternationalizationExtension(Extension):
)
def _install(
self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
self, translations: "_SupportedTranslations", newstyle: bool | None = None
) -> None:
# ugettext and ungettext are preferred in case the I18N library
# is providing compatibility with older Python versions.
@ -285,41 +283,25 @@ class InternationalizationExtension(Extension):
gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
)
def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
def _install_null(self, newstyle: bool | None = 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=pgettext,
npgettext=npgettext,
pgettext=translations.pgettext,
npgettext=translations.npgettext,
)
def _install_callables(
self,
gettext: t.Callable[[str], str],
ngettext: t.Callable[[str, str, int], str],
newstyle: t.Optional[bool] = None,
pgettext: t.Optional[t.Callable[[str, str], str]] = None,
npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
newstyle: bool | None = None,
pgettext: t.Callable[[str, str], str] | None = None,
npgettext: t.Callable[[str, str, str, int], str] | None = None,
) -> None:
if newstyle is not None:
self.environment.newstyle_gettext = newstyle # type: ignore
@ -343,16 +325,14 @@ class InternationalizationExtension(Extension):
def _extract(
self,
source: t.Union[str, nodes.Template],
source: str | nodes.Template,
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
) -> t.Iterator[
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
]:
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...]]]:
if isinstance(source, str):
source = self.environment.parse(source)
return extract_from_ast(source, gettext_functions)
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
def parse(self, parser: "Parser") -> nodes.Node | list[nodes.Node]:
"""Parse a translatable tag."""
lineno = next(parser.stream).lineno
@ -365,10 +345,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: t.Optional[nodes.Expr] = None
plural_expr_assignment: t.Optional[nodes.Assign] = None
plural_expr: nodes.Expr | None = None
plural_expr_assignment: nodes.Assign | None = None
num_called_num = False
variables: t.Dict[str, nodes.Expr] = {}
variables: dict[str, nodes.Expr] = {}
trimmed = None
while parser.stream.current.type != "block_end":
if variables:
@ -479,7 +459,7 @@ class InternationalizationExtension(Extension):
def _parse_block(
self, parser: "Parser", allow_pluralize: bool
) -> t.Tuple[t.List[str], str]:
) -> tuple[list[str], str]:
"""Parse until the next block tag with a given name."""
referenced = []
buf = []
@ -527,10 +507,10 @@ class InternationalizationExtension(Extension):
def _make_node(
self,
singular: str,
plural: t.Optional[str],
context: t.Optional[str],
variables: t.Dict[str, nodes.Expr],
plural_expr: t.Optional[nodes.Expr],
plural: str | None,
context: str | None,
variables: dict[str, nodes.Expr],
plural_expr: nodes.Expr | None,
vars_referenced: bool,
num_called_num: bool,
) -> nodes.Output:
@ -546,7 +526,7 @@ class InternationalizationExtension(Extension):
plural = plural.replace("%%", "%")
func_name = "gettext"
func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
func_args: list[nodes.Expr] = [nodes.Const(singular)]
if context is not None:
func_args.insert(0, nodes.Const(context))
@ -605,7 +585,7 @@ class LoopControlExtension(Extension):
tags = {"break", "continue"}
def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
def parse(self, parser: "Parser") -> nodes.Break | nodes.Continue:
token = next(parser.stream)
if token.value == "break":
return nodes.Break(lineno=token.lineno)
@ -656,9 +636,7 @@ def extract_from_ast(
ast: nodes.Template,
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
babel_style: bool = True,
) -> t.Iterator[
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
]:
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...]]]:
"""Extract localizable strings from the given template node. Per
default this function returns matches in babel style that means non string
parameters as well as keyword arguments are returned as `None`. This
@ -693,7 +671,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: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]
out: str | None | tuple[str | None, ...]
for node in ast.find_all(nodes.Call):
if (
@ -702,7 +680,7 @@ def extract_from_ast(
):
continue
strings: t.List[t.Optional[str]] = []
strings: list[str | None] = []
for arg in node.args:
if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
@ -739,14 +717,14 @@ class _CommentFinder:
"""
def __init__(
self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
self, tokens: t.Sequence[tuple[int, str, str]], comment_tags: t.Sequence[str]
) -> None:
self.tokens = tokens
self.comment_tags = comment_tags
self.offset = 0
self.last_lineno = 0
def find_backwards(self, offset: int) -> t.List[str]:
def find_backwards(self, offset: int) -> list[str]:
try:
for _, token_type, token_value in reversed(
self.tokens[self.offset : offset]
@ -762,7 +740,7 @@ class _CommentFinder:
finally:
self.offset = offset
def find_comments(self, lineno: int) -> t.List[str]:
def find_comments(self, lineno: int) -> list[str]:
if not self.comment_tags or self.last_lineno > lineno:
return []
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
@ -775,12 +753,8 @@ def babel_extract(
fileobj: t.BinaryIO,
keywords: t.Sequence[str],
comment_tags: t.Sequence[str],
options: t.Dict[str, t.Any],
) -> t.Iterator[
t.Tuple[
int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
]
]:
options: dict[str, t.Any],
) -> t.Iterator[tuple[int, str, str | None | tuple[str | None, ...], list[str]]]:
"""Babel extraction method for Jinja templates.
.. versionchanged:: 2.3
@ -808,7 +782,7 @@ def babel_extract(
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
(comments will be empty currently)
"""
extensions: t.Dict[t.Type[Extension], None] = {}
extensions: dict[type[Extension], None] = {}
for extension_name in options.get("extensions", "").split(","):
extension_name = extension_name.strip()

View File

@ -57,9 +57,9 @@ def ignore_case(value: V) -> V:
def make_attrgetter(
environment: "Environment",
attribute: t.Optional[t.Union[str, int]],
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
default: t.Optional[t.Any] = None,
attribute: str | int | None,
postprocess: t.Callable[[t.Any], t.Any] | None = None,
default: t.Any | None = 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: t.Optional[t.Union[str, int]],
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
) -> t.Callable[[t.Any], t.List[t.Any]]:
attribute: str | int | None,
postprocess: t.Callable[[t.Any], t.Any] | None = None,
) -> t.Callable[[t.Any], 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[t.Union[str, int, None]] = attribute.split(",")
split: t.Sequence[str | int | None] = attribute.split(",")
else:
split = [attribute]
parts = [_prepare_attribute_parts(item) for item in split]
def attrgetter(item: t.Any) -> t.List[t.Any]:
def attrgetter(item: t.Any) -> 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: t.Optional[t.Union[str, int]],
) -> t.List[t.Union[str, int]]:
attr: str | int | None,
) -> list[str | int]:
if attr is None:
return []
@ -136,7 +136,7 @@ def _prepare_attribute_parts(
return [attr]
def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup:
def do_forceescape(value: "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: "t.Union[str, HasHTML]") -> Markup:
def do_urlencode(
value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]],
value: str | t.Mapping[str, t.Any] | t.Iterable[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[t.Tuple[str, t.Any]] = value.items()
items: t.Iterable[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: t.Optional[int] = None
eval_ctx: "EvalContext", s: str, old: str, new: str, count: int | None = 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.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K, V]]:
def do_items(value: t.Mapping[K, V] | Undefined) -> t.Iterator[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,
) -> t.List[t.Tuple[K, V]]:
) -> list[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: t.Tuple[t.Any, t.Any]) -> t.Any:
def sort_func(item: 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: t.Optional[t.Union[str, int]] = None,
) -> "t.List[V]":
attribute: str | int | None = None,
) -> "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: t.Optional[t.Union[str, int]] = None,
attribute: str | int | None = 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.Union[t.AsyncIterable[V], t.Iterable[V]]",
value: "t.AsyncIterable[V] | t.Iterable[V]",
case_sensitive: bool = False,
attribute: t.Optional[t.Union[str, int]] = None,
attribute: str | int | None = 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: t.Optional[t.Union[str, int]],
) -> "t.Union[V, Undefined]":
attribute: str | int | None,
) -> "V | Undefined":
it = iter(value)
try:
@ -508,8 +508,8 @@ def do_min(
environment: "Environment",
value: "t.Iterable[V]",
case_sensitive: bool = False,
attribute: t.Optional[t.Union[str, int]] = None,
) -> "t.Union[V, Undefined]":
attribute: str | int | None = None,
) -> "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: t.Optional[t.Union[str, int]] = None,
) -> "t.Union[V, Undefined]":
attribute: str | int | None = None,
) -> "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: t.Optional[t.Union[str, int]] = None,
attribute: str | int | None = 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.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
d: str = "",
attribute: t.Optional[t.Union[str, int]] = None,
attribute: str | int | None = None,
) -> str:
return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute)
@ -650,9 +650,7 @@ def do_center(value: str, width: int = 80) -> str:
@pass_environment
def sync_do_first(
environment: "Environment", seq: "t.Iterable[V]"
) -> "t.Union[V, Undefined]":
def sync_do_first(environment: "Environment", seq: "t.Iterable[V]") -> "V | Undefined":
"""Return the first item of a sequence."""
try:
return next(iter(seq))
@ -662,8 +660,8 @@ def sync_do_first(
@async_variant(sync_do_first) # type: ignore
async def do_first(
environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]"
) -> "t.Union[V, Undefined]":
environment: "Environment", seq: "t.AsyncIterable[V] | t.Iterable[V]"
) -> "V | Undefined":
try:
return await auto_aiter(seq).__anext__()
except StopAsyncIteration:
@ -671,9 +669,7 @@ async def do_first(
@pass_environment
def do_last(
environment: "Environment", seq: "t.Reversible[V]"
) -> "t.Union[V, Undefined]":
def do_last(environment: "Environment", seq: "t.Reversible[V]") -> "V | Undefined":
"""Return the last item of a sequence.
Note: Does not work with generators. You may want to explicitly
@ -693,7 +689,7 @@ def do_last(
@pass_context
def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]":
def do_random(context: "Context", seq: "t.Sequence[V]") -> "V | Undefined":
"""Return a random item from the sequence."""
try:
return random.choice(seq)
@ -701,7 +697,7 @@ def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined
return context.environment.undefined("No random item, sequence was empty.")
def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str:
def do_filesizeformat(value: 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
@ -746,11 +742,11 @@ _uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$")
def do_urlize(
eval_ctx: "EvalContext",
value: str,
trim_url_limit: t.Optional[int] = None,
trim_url_limit: int | None = None,
nofollow: bool = False,
target: t.Optional[str] = None,
rel: t.Optional[str] = None,
extra_schemes: t.Optional[t.Iterable[str]] = None,
target: str | None = None,
rel: str | None = None,
extra_schemes: t.Iterable[str] | None = None,
) -> str:
"""Convert URLs in text into clickable links.
@ -823,7 +819,7 @@ def do_urlize(
def do_indent(
s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False
s: str, width: 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.
@ -877,7 +873,7 @@ def do_truncate(
length: int = 255,
killwords: bool = False,
end: str = "...",
leeway: t.Optional[int] = None,
leeway: int | None = None,
) -> str:
"""Return a truncated copy of the string. The length is specified
with the first parameter which defaults to ``255``. If the second
@ -924,7 +920,7 @@ def do_wordwrap(
s: str,
width: int = 79,
break_long_words: bool = True,
wrapstring: t.Optional[str] = None,
wrapstring: str | None = None,
break_on_hyphens: bool = True,
) -> str:
"""Wrap a string to the given width. Existing newlines are treated
@ -1043,12 +1039,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: t.Optional[str] = None) -> str:
def do_trim(value: str, chars: str | None = None) -> str:
"""Strip leading and trailing characters, by default whitespace."""
return soft_str(value).strip(chars)
def do_striptags(value: "t.Union[str, HasHTML]") -> str:
def do_striptags(value: "str | HasHTML") -> str:
"""Strip SGML/XML tags and replace adjacent whitespace by one space."""
if hasattr(value, "__html__"):
value = t.cast("HasHTML", value).__html__()
@ -1057,8 +1053,8 @@ def do_striptags(value: "t.Union[str, HasHTML]") -> str:
def sync_do_slice(
value: "t.Collection[V]", slices: int, fill_with: "t.Optional[V]" = None
) -> "t.Iterator[t.List[V]]":
value: "t.Collection[V]", slices: int, fill_with: "V | None" = None
) -> "t.Iterator[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:
@ -1101,16 +1097,16 @@ def sync_do_slice(
@async_variant(sync_do_slice) # type: ignore
async def do_slice(
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
value: "t.AsyncIterable[V] | t.Iterable[V]",
slices: int,
fill_with: t.Optional[t.Any] = None,
) -> "t.Iterator[t.List[V]]":
fill_with: t.Any | None = None,
) -> "t.Iterator[list[V]]":
return sync_do_slice(await auto_to_list(value), slices, fill_with)
def do_batch(
value: "t.Iterable[V]", linecount: int, fill_with: "t.Optional[V]" = None
) -> "t.Iterator[t.List[V]]":
value: "t.Iterable[V]", linecount: int, fill_with: "V | None" = None
) -> "t.Iterator[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
@ -1129,7 +1125,7 @@ def do_batch(
{%- endfor %}
</table>
"""
tmp: t.List[V] = []
tmp: list[V] = []
for item in value:
if len(tmp) == linecount:
@ -1187,7 +1183,7 @@ def do_round(
class _GroupTuple(t.NamedTuple):
grouper: t.Any
list: t.List[t.Any]
list: list[t.Any]
# Use the regular tuple repr to hide this subclass if users print
# out the value during debugging.
@ -1202,10 +1198,10 @@ class _GroupTuple(t.NamedTuple):
def sync_do_groupby(
environment: "Environment",
value: "t.Iterable[V]",
attribute: t.Union[str, int],
default: t.Optional[t.Any] = None,
attribute: str | int,
default: t.Any | None = None,
case_sensitive: bool = False,
) -> "t.List[_GroupTuple]":
) -> "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``,
@ -1285,11 +1281,11 @@ def sync_do_groupby(
@async_variant(sync_do_groupby) # type: ignore
async def do_groupby(
environment: "Environment",
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
attribute: t.Union[str, int],
default: t.Optional[t.Any] = None,
value: "t.AsyncIterable[V] | t.Iterable[V]",
attribute: str | int,
default: t.Any | None = None,
case_sensitive: bool = False,
) -> "t.List[_GroupTuple]":
) -> "list[_GroupTuple]":
expr = make_attrgetter(
environment,
attribute,
@ -1313,7 +1309,7 @@ async def do_groupby(
def sync_do_sum(
environment: "Environment",
iterable: "t.Iterable[V]",
attribute: t.Optional[t.Union[str, int]] = None,
attribute: str | int | None = None,
start: V = 0, # type: ignore
) -> V:
"""Returns the sum of a sequence of numbers plus the value of parameter
@ -1339,8 +1335,8 @@ def sync_do_sum(
@async_variant(sync_do_sum) # type: ignore
async def do_sum(
environment: "Environment",
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
attribute: t.Optional[t.Union[str, int]] = None,
iterable: "t.AsyncIterable[V] | t.Iterable[V]",
attribute: str | int | None = None,
start: V = 0, # type: ignore
) -> V:
rv = start
@ -1358,7 +1354,7 @@ async def do_sum(
return rv
def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]":
def sync_do_list(value: "t.Iterable[V]") -> "list[V]":
"""Convert the value into a list. If it was a string the returned list
will be a list of characters.
"""
@ -1366,7 +1362,7 @@ def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]":
@async_variant(sync_do_list) # type: ignore
async def do_list(value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]") -> "t.List[V]":
async def do_list(value: "t.AsyncIterable[V] | t.Iterable[V]") -> "list[V]":
return await auto_to_list(value)
@ -1390,7 +1386,7 @@ def do_reverse(value: str) -> str: ...
def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]": ...
def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]]:
def do_reverse(value: str | t.Iterable[V]) -> str | t.Iterable[V]:
"""Reverse the object or return an iterator that iterates over it the other
way round.
"""
@ -1409,9 +1405,7 @@ def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]
@pass_environment
def do_attr(
environment: "Environment", obj: t.Any, name: str
) -> t.Union[Undefined, t.Any]:
def do_attr(environment: "Environment", obj: t.Any, name: str) -> 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.
@ -1449,7 +1443,7 @@ def sync_do_map(
value: t.Iterable[t.Any],
*,
attribute: str = ...,
default: t.Optional[t.Any] = None,
default: t.Any | None = None,
) -> t.Iterable[t.Any]: ...
@ -1506,7 +1500,7 @@ def sync_do_map(
@typing.overload
def do_map(
context: "Context",
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
name: str,
*args: t.Any,
**kwargs: t.Any,
@ -1516,17 +1510,17 @@ def do_map(
@typing.overload
def do_map(
context: "Context",
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
*,
attribute: str = ...,
default: t.Optional[t.Any] = None,
default: t.Any | None = None,
) -> t.Iterable[t.Any]: ...
@async_variant(sync_do_map) # type: ignore
async def do_map(
context: "Context",
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
value: t.AsyncIterable[t.Any] | t.Iterable[t.Any],
*args: t.Any,
**kwargs: t.Any,
) -> t.AsyncIterable[t.Any]:
@ -1571,7 +1565,7 @@ def sync_do_select(
@async_variant(sync_do_select) # type: ignore
async def do_select(
context: "Context",
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
@ -1607,7 +1601,7 @@ def sync_do_reject(
@async_variant(sync_do_reject) # type: ignore
async def do_reject(
context: "Context",
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
@ -1647,7 +1641,7 @@ def sync_do_selectattr(
@async_variant(sync_do_selectattr) # type: ignore
async def do_selectattr(
context: "Context",
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
@ -1685,7 +1679,7 @@ def sync_do_rejectattr(
@async_variant(sync_do_rejectattr) # type: ignore
async def do_rejectattr(
context: "Context",
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
value: "t.AsyncIterable[V] | t.Iterable[V]",
*args: t.Any,
**kwargs: t.Any,
) -> "t.AsyncIterator[V]":
@ -1694,7 +1688,7 @@ async def do_rejectattr(
@pass_eval_context
def do_tojson(
eval_ctx: "EvalContext", value: t.Any, indent: t.Optional[int] = None
eval_ctx: "EvalContext", value: t.Any, indent: int | None = 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.
@ -1722,7 +1716,7 @@ def do_tojson(
def prepare_map(
context: "Context", args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any]
context: "Context", args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
) -> t.Callable[[t.Any], t.Any]:
if not args and "attribute" in kwargs:
attribute = kwargs.pop("attribute")
@ -1751,8 +1745,8 @@ def prepare_map(
def prepare_select_or_reject(
context: "Context",
args: t.Tuple[t.Any, ...],
kwargs: t.Dict[str, t.Any],
args: tuple[t.Any, ...],
kwargs: dict[str, t.Any],
modfunc: t.Callable[[t.Any], t.Any],
lookup_attr: bool,
) -> t.Callable[[t.Any], t.Any]:
@ -1786,8 +1780,8 @@ def prepare_select_or_reject(
def select_or_reject(
context: "Context",
value: "t.Iterable[V]",
args: t.Tuple[t.Any, ...],
kwargs: t.Dict[str, t.Any],
args: tuple[t.Any, ...],
kwargs: dict[str, t.Any],
modfunc: t.Callable[[t.Any], t.Any],
lookup_attr: bool,
) -> "t.Iterator[V]":
@ -1801,9 +1795,9 @@ def select_or_reject(
async def async_select_or_reject(
context: "Context",
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
args: t.Tuple[t.Any, ...],
kwargs: t.Dict[str, t.Any],
value: "t.AsyncIterable[V] | t.Iterable[V]",
args: tuple[t.Any, ...],
kwargs: dict[str, t.Any],
modfunc: t.Callable[[t.Any], t.Any],
lookup_attr: bool,
) -> "t.AsyncIterator[V]":

View File

@ -32,7 +32,7 @@ def symbols_for_node(
class Symbols:
def __init__(
self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
self, parent: t.Optional["Symbols"] = None, level: int | None = None
) -> None:
if level is None:
if parent is None:
@ -42,24 +42,22 @@ class Symbols:
self.level: int = level
self.parent = parent
self.refs: t.Dict[str, str] = {}
self.loads: t.Dict[str, t.Any] = {}
self.stores: t.Set[str] = set()
self.refs: dict[str, str] = {}
self.loads: dict[str, t.Any] = {}
self.stores: 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: t.Optional[t.Tuple[str, t.Optional[str]]] = None
) -> str:
def _define_ref(self, name: str, load: tuple[str, str | None] | None = None) -> str:
ident = f"l_{self.level}_{name}"
self.refs[name] = ident
if load is not None:
self.loads[ident] = load
return ident
def find_load(self, target: str) -> t.Optional[t.Any]:
def find_load(self, target: str) -> t.Any | None:
if target in self.loads:
return self.loads[target]
@ -68,7 +66,7 @@ class Symbols:
return None
def find_ref(self, name: str) -> t.Optional[str]:
def find_ref(self, name: str) -> str | None:
if name in self.refs:
return self.refs[name]
@ -121,7 +119,7 @@ class Symbols:
self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
stores: t.Set[str] = set()
stores: set[str] = set()
for branch in branch_symbols:
stores.update(branch.stores)
@ -144,9 +142,9 @@ class Symbols:
continue
self.loads[target] = (VAR_LOAD_RESOLVE, name)
def dump_stores(self) -> t.Dict[str, str]:
rv: t.Dict[str, str] = {}
node: t.Optional[Symbols] = self
def dump_stores(self) -> dict[str, str]:
rv: dict[str, str] = {}
node: Symbols | None = self
while node is not None:
for name in sorted(node.stores):
@ -157,9 +155,9 @@ class Symbols:
return rv
def dump_param_targets(self) -> t.Set[str]:
def dump_param_targets(self) -> set[str]:
rv = set()
node: t.Optional[Symbols] = self
node: Symbols | None = self
while node is not None:
for target, (instr, _) in self.loads.items():

View File

@ -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[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore
_lexer_cache: t.MutableMapping[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") -> t.List[t.Tuple[str, str]]:
def compile_rules(environment: "Environment") -> list[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: t.Type[TemplateSyntaxError] = TemplateSyntaxError
self, message: str, cls: type[TemplateSyntaxError] = TemplateSyntaxError
) -> None:
self.message = message
self.error_class = cls
def __call__(self, lineno: int, filename: t.Optional[str]) -> "te.NoReturn":
def __call__(self, lineno: int, filename: str | None) -> "te.NoReturn":
raise self.error_class(self.message, lineno, filename)
@ -325,11 +325,11 @@ class TokenStream:
def __init__(
self,
generator: t.Iterable[Token],
name: t.Optional[str],
filename: t.Optional[str],
name: str | None,
filename: str | None,
):
self._iter = iter(generator)
self._pushed: te.Deque[Token] = deque()
self._pushed: 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) -> t.Optional[Token]:
def next_if(self, expr: str) -> Token | None:
"""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: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]]
command: t.Optional[str]
tokens: str | tuple[str, ...] | tuple[Failure]
command: str | None
class Lexer:
@ -484,7 +484,7 @@ class Lexer:
return re.compile(x, re.M | re.S)
# lexing rules for tags
tag_rules: t.List[_Rule] = [
tag_rules: 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: t.Dict[str, t.List[_Rule]] = {
self.rules: dict[str, list[_Rule]] = {
"root": [
# directives
_Rule(
@ -604,9 +604,9 @@ class Lexer:
def tokenize(
self,
source: str,
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
state: t.Optional[str] = None,
name: str | None = None,
filename: str | None = None,
state: str | None = 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[t.Tuple[int, str, str]],
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
stream: t.Iterable[tuple[int, str, str]],
name: str | None = None,
filename: str | None = 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: t.Optional[str],
filename: t.Optional[str] = None,
state: t.Optional[str] = None,
) -> t.Iterator[t.Tuple[int, str, str]]:
name: str | None,
filename: str | None = None,
state: str | None = None,
) -> t.Iterator[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: t.List[str] = []
balancing_stack: list[str] = []
newlines_stripped = 0
line_starting = True

View File

@ -22,14 +22,14 @@ if t.TYPE_CHECKING:
from .environment import Template
def split_template_path(template: str) -> t.List[str]:
def split_template_path(template: str) -> list[str]:
"""Split a path into segments and perform a sanity check. If it detects
'..' in the path it will raise a `TemplateNotFound` error.
"""
pieces = []
for piece in template.split("/"):
if (
os.path.sep in piece
os.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
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
"""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) -> t.List[str]:
def list_templates(self) -> 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.Optional[t.MutableMapping[str, t.Any]] = None,
globals: t.MutableMapping[str, t.Any] | None = 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
) -> t.Tuple[str, str, t.Callable[[], bool]]:
) -> 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) -> t.List[str]:
def list_templates(self) -> 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.path.sep)
.replace(os.path.sep, "/")
.strip(os.sep)
.replace(os.sep, "/")
)
if template[:2] == "./":
template = template[2:]
@ -245,24 +245,23 @@ class FileSystemLoader(BaseLoader):
if sys.version_info >= (3, 13):
def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
def _get_zipimporter_files(z: t.Any) -> 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) -> t.Dict[str, object]:
def _get_zipimporter_files(z: t.Any) -> 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]
@ -306,12 +305,12 @@ class PackageLoader(BaseLoader):
package_path: "str" = "templates",
encoding: str = "utf-8",
) -> None:
package_path = os.path.normpath(package_path).rstrip(os.path.sep)
package_path = os.path.normpath(package_path).rstrip(os.sep)
# normpath preserves ".", which isn't valid in zip paths.
if package_path == os.path.curdir:
package_path = ""
elif package_path[:2] == os.path.curdir + os.path.sep:
elif package_path[:2] == os.path.curdir + os.sep:
package_path = package_path[2:]
self.package_path = package_path
@ -331,9 +330,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.path.sep)
template_root = os.path.join(pkgdir, package_path).rstrip(os.sep)
else:
roots: t.List[str] = []
roots: list[str] = []
# One element for regular packages, multiple for namespace
# packages, or None for single module file.
@ -365,14 +364,14 @@ class PackageLoader(BaseLoader):
def get_source(
self, environment: "Environment", template: str
) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
) -> tuple[str, str, t.Callable[[], bool] | None]:
# Use posixpath even on Windows to avoid "drive:" or UNC
# segments breaking out of the search directory. Use normpath to
# convert Windows altsep to sep.
p = os.path.normpath(
posixpath.join(self._template_root, *split_template_path(template))
)
up_to_date: t.Optional[t.Callable[[], bool]]
up_to_date: t.Callable[[], bool] | None
if self._archive is None:
# Package is a directory.
@ -401,33 +400,30 @@ class PackageLoader(BaseLoader):
return source.decode(self.encoding), p, up_to_date
def list_templates(self) -> t.List[str]:
results: t.List[str] = []
def list_templates(self) -> list[str]:
results: 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.path.sep)
dirpath = dirpath[offset:].lstrip(os.sep)
results.extend(
os.path.join(dirpath, name).replace(os.path.sep, "/")
os.path.join(dirpath, name).replace(os.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.path.sep)
+ os.path.sep
)
prefix = self._template_root[len(self._archive) :].lstrip(os.sep) + os.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.path.sep:
results.append(name[offset:].replace(os.path.sep, "/"))
if name.startswith(prefix) and name[-1] != os.sep:
results.append(name[offset:].replace(os.sep, "/"))
results.sort()
return results
@ -447,13 +443,13 @@ class DictLoader(BaseLoader):
def get_source(
self, environment: "Environment", template: str
) -> t.Tuple[str, None, t.Callable[[], bool]]:
) -> tuple[str, None, t.Callable[[], bool]]:
if template in self.mapping:
source = self.mapping[template]
return source, None, lambda: source == self.mapping.get(template)
raise TemplateNotFound(template)
def list_templates(self) -> t.List[str]:
def list_templates(self) -> list[str]:
return sorted(self.mapping)
@ -479,18 +475,14 @@ class FunctionLoader(BaseLoader):
self,
load_func: t.Callable[
[str],
t.Optional[
t.Union[
str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
]
],
str | tuple[str, str | None, t.Callable[[], bool] | None] | None,
],
) -> None:
self.load_func = load_func
def get_source(
self, environment: "Environment", template: str
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
rv = self.load_func(template)
if rv is None:
@ -523,7 +515,7 @@ class PrefixLoader(BaseLoader):
self.mapping = mapping
self.delimiter = delimiter
def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
def get_loader(self, template: str) -> tuple[BaseLoader, str]:
try:
prefix, name = template.split(self.delimiter, 1)
loader = self.mapping[prefix]
@ -533,7 +525,7 @@ class PrefixLoader(BaseLoader):
def get_source(
self, environment: "Environment", template: str
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
loader, name = self.get_loader(template)
try:
return loader.get_source(environment, name)
@ -547,7 +539,7 @@ class PrefixLoader(BaseLoader):
self,
environment: "Environment",
name: str,
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
loader, local_name = self.get_loader(name)
try:
@ -557,7 +549,7 @@ class PrefixLoader(BaseLoader):
# (the one that includes the prefix)
raise TemplateNotFound(name) from e
def list_templates(self) -> t.List[str]:
def list_templates(self) -> list[str]:
result = []
for prefix, loader in self.mapping.items():
for template in loader.list_templates():
@ -584,7 +576,7 @@ class ChoiceLoader(BaseLoader):
def get_source(
self, environment: "Environment", template: str
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
for loader in self.loaders:
try:
return loader.get_source(environment, template)
@ -597,7 +589,7 @@ class ChoiceLoader(BaseLoader):
self,
environment: "Environment",
name: str,
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
for loader in self.loaders:
try:
@ -606,7 +598,7 @@ class ChoiceLoader(BaseLoader):
pass
raise TemplateNotFound(name)
def list_templates(self) -> t.List[str]:
def list_templates(self) -> list[str]:
found = set()
for loader in self.loaders:
found.update(loader.list_templates())
@ -669,7 +661,7 @@ class ModuleLoader(BaseLoader):
self,
environment: "Environment",
name: str,
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
globals: t.MutableMapping[str, t.Any] | None = None,
) -> "Template":
key = self.get_template_key(name)
module = f"{self.package_name}.{key}"

View File

@ -17,7 +17,7 @@ class TrackingCodeGenerator(CodeGenerator):
def __init__(self, environment: "Environment") -> None:
super().__init__(environment, "<introspection>", "<introspection>")
self.undeclared_identifiers: t.Set[str] = set()
self.undeclared_identifiers: 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) -> t.Set[str]:
def find_undeclared_variables(ast: nodes.Template) -> 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) -> t.Set[str]:
_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]
_RefType = nodes.Extends | nodes.FromImport | nodes.Import | nodes.Include
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[str | None]:
"""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

View File

@ -13,7 +13,7 @@ from .environment import Environment
from .environment import Template
def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
def native_concat(values: t.Iterable[t.Any]) -> t.Any | None:
"""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

View File

@ -19,7 +19,7 @@ if t.TYPE_CHECKING:
_NodeBound = t.TypeVar("_NodeBound", bound="Node")
_binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
_binop_to_func: dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
"*": operator.mul,
"/": operator.truediv,
"//": operator.floordiv,
@ -29,13 +29,13 @@ _binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
"-": operator.sub,
}
_uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
_uaop_to_func: dict[str, t.Callable[[t.Any], t.Any]] = {
"not": operator.not_,
"+": operator.pos,
"-": operator.neg,
}
_cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
_cmpop_to_func: 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: t.List[t.Tuple[str, ...]] = []
storage: list[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: t.Optional[str] = None
self, environment: "Environment", template_name: str | None = 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: t.Optional[EvalContext]) -> EvalContext:
def get_eval_context(node: "Node", ctx: EvalContext | None) -> EvalContext:
if ctx is None:
if node.environment is None:
raise RuntimeError(
@ -119,8 +119,8 @@ class Node(metaclass=NodeType):
all nodes automatically.
"""
fields: t.Tuple[str, ...] = ()
attributes: t.Tuple[str, ...] = ("lineno", "environment")
fields: tuple[str, ...] = ()
attributes: 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):
for name, arg in zip(self.fields, fields, strict=False):
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.Optional[t.Container[str]] = None,
only: t.Optional[t.Container[str]] = None,
) -> t.Iterator[t.Tuple[str, t.Any]]:
exclude: t.Container[str] | None = None,
only: t.Container[str] | None = None,
) -> t.Iterator[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.Optional[t.Container[str]] = None,
only: t.Optional[t.Container[str]] = None,
exclude: t.Container[str] | None = None,
only: t.Container[str] | None = 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: t.Type[_NodeBound]) -> t.Optional[_NodeBound]:
def find(self, node_type: type[_NodeBound]) -> _NodeBound | None:
"""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: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]]
self, node_type: type[_NodeBound] | tuple[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: t.Union[Node, t.Any]) -> None:
def _dump(node: 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: t.List[str] = []
buf: list[str] = []
_dump(self)
return "".join(buf)
@ -297,7 +297,7 @@ class Template(Node):
"""
fields = ("body",)
body: t.List[Node]
body: list[Node]
class Output(Stmt):
@ -306,7 +306,7 @@ class Output(Stmt):
"""
fields = ("nodes",)
nodes: t.List["Expr"]
nodes: list["Expr"]
class Extends(Stmt):
@ -328,9 +328,9 @@ class For(Stmt):
fields = ("target", "iter", "body", "else_", "test", "recursive")
target: Node
iter: Node
body: t.List[Node]
else_: t.List[Node]
test: t.Optional[Node]
body: list[Node]
else_: list[Node]
test: Node | None
recursive: bool
@ -339,9 +339,9 @@ class If(Stmt):
fields = ("test", "body", "elif_", "else_")
test: Node
body: t.List[Node]
elif_: t.List["If"]
else_: t.List[Node]
body: list[Node]
elif_: list["If"]
else_: list[Node]
class Macro(Stmt):
@ -352,9 +352,9 @@ class Macro(Stmt):
fields = ("name", "args", "defaults", "body")
name: str
args: t.List["Name"]
defaults: t.List["Expr"]
body: t.List[Node]
args: list["Name"]
defaults: list["Expr"]
body: list[Node]
class CallBlock(Stmt):
@ -364,16 +364,16 @@ class CallBlock(Stmt):
fields = ("call", "args", "defaults", "body")
call: "Call"
args: t.List["Name"]
defaults: t.List["Expr"]
body: t.List[Node]
args: list["Name"]
defaults: list["Expr"]
body: list[Node]
class FilterBlock(Stmt):
"""Node for filter sections."""
fields = ("body", "filter")
body: t.List[Node]
body: list[Node]
filter: "Filter"
@ -385,9 +385,9 @@ class With(Stmt):
"""
fields = ("targets", "values", "body")
targets: t.List["Expr"]
values: t.List["Expr"]
body: t.List[Node]
targets: list["Expr"]
values: list["Expr"]
body: list[Node]
class Block(Stmt):
@ -399,7 +399,7 @@ class Block(Stmt):
fields = ("name", "body", "scoped", "required")
name: str
body: t.List[Node]
body: list[Node]
scoped: bool
required: bool
@ -436,7 +436,7 @@ class FromImport(Stmt):
fields = ("template", "names", "with_context")
template: "Expr"
names: t.List[t.Union[str, t.Tuple[str, str]]]
names: list[str | tuple[str, str]]
with_context: bool
@ -461,7 +461,7 @@ class AssignBlock(Stmt):
fields = ("target", "filter", "body")
target: "Expr"
filter: t.Optional["Filter"]
body: t.List[Node]
body: list[Node]
class Expr(Node):
@ -469,7 +469,7 @@ class Expr(Node):
abstract = True
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = 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: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = 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: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = 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: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
return self.value
@classmethod
def from_untrusted(
cls,
value: t.Any,
lineno: t.Optional[int] = None,
environment: "t.Optional[Environment]" = None,
lineno: int | None = None,
environment: "Environment | None" = 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: t.Optional[EvalContext] = None) -> str:
def as_const(self, eval_ctx: EvalContext | None = 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: t.List[Expr]
items: list[Expr]
ctx: str
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]:
def as_const(self, eval_ctx: EvalContext | None = None) -> 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: t.List[Expr]
items: list[Expr]
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]:
def as_const(self, eval_ctx: EvalContext | None = None) -> list[t.Any]:
eval_ctx = get_eval_context(self, eval_ctx)
return [x.as_const(eval_ctx) for x in self.items]
@ -658,11 +658,9 @@ class Dict(Literal):
"""
fields = ("items",)
items: t.List["Pair"]
items: list["Pair"]
def as_const(
self, eval_ctx: t.Optional[EvalContext] = None
) -> t.Dict[t.Any, t.Any]:
def as_const(self, eval_ctx: EvalContext | None = None) -> 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)
@ -674,9 +672,7 @@ class Pair(Helper):
key: Expr
value: Expr
def as_const(
self, eval_ctx: t.Optional[EvalContext] = None
) -> t.Tuple[t.Any, t.Any]:
def as_const(self, eval_ctx: EvalContext | None = None) -> 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)
@ -688,7 +684,7 @@ class Keyword(Helper):
key: str
value: Expr
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]:
def as_const(self, eval_ctx: EvalContext | None = None) -> tuple[str, t.Any]:
eval_ctx = get_eval_context(self, eval_ctx)
return self.key, self.value.as_const(eval_ctx)
@ -701,9 +697,9 @@ class CondExpr(Expr):
fields = ("test", "expr1", "expr2")
test: Expr
expr1: Expr
expr2: t.Optional[Expr]
expr2: Expr | None
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = 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)
@ -716,8 +712,8 @@ class CondExpr(Expr):
def args_as_const(
node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext]
) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]:
node: t.Union["_FilterTestCommon", "Call"], eval_ctx: EvalContext | None
) -> tuple[list[t.Any], 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)
@ -740,14 +736,14 @@ class _FilterTestCommon(Expr):
fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs")
node: Expr
name: str
args: t.List[Expr]
kwargs: t.List[Pair]
dyn_args: t.Optional[Expr]
dyn_kwargs: t.Optional[Expr]
args: list[Expr]
kwargs: list[Pair]
dyn_args: Expr | None
dyn_kwargs: Expr | None
abstract = True
_is_filter = True
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
if eval_ctx.volatile:
@ -792,9 +788,9 @@ class Filter(_FilterTestCommon):
and is applied to the content of the block.
"""
node: t.Optional[Expr] # type: ignore
node: Expr | None # type: ignore
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
if self.node is None:
raise Impossible()
@ -824,10 +820,10 @@ class Call(Expr):
fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs")
node: Expr
args: t.List[Expr]
kwargs: t.List[Keyword]
dyn_args: t.Optional[Expr]
dyn_kwargs: t.Optional[Expr]
args: list[Expr]
kwargs: list[Keyword]
dyn_args: Expr | None
dyn_kwargs: Expr | None
class Getitem(Expr):
@ -838,7 +834,7 @@ class Getitem(Expr):
arg: Expr
ctx: str
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
if self.ctx != "load":
raise Impossible()
@ -862,7 +858,7 @@ class Getattr(Expr):
attr: str
ctx: str
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
if self.ctx != "load":
raise Impossible()
@ -880,14 +876,14 @@ class Slice(Expr):
"""
fields = ("start", "stop", "step")
start: t.Optional[Expr]
stop: t.Optional[Expr]
step: t.Optional[Expr]
start: Expr | None
stop: Expr | None
step: Expr | None
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice:
def as_const(self, eval_ctx: EvalContext | None = None) -> slice:
eval_ctx = get_eval_context(self, eval_ctx)
def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]:
def const(obj: Expr | None) -> t.Any | None:
if obj is None:
return None
return obj.as_const(eval_ctx)
@ -901,9 +897,9 @@ class Concat(Expr):
"""
fields = ("nodes",)
nodes: t.List[Expr]
nodes: list[Expr]
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
def as_const(self, eval_ctx: EvalContext | None = None) -> str:
eval_ctx = get_eval_context(self, eval_ctx)
return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)
@ -915,9 +911,9 @@ class Compare(Expr):
fields = ("expr", "ops")
expr: Expr
ops: t.List["Operand"]
ops: list["Operand"]
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
result = value = self.expr.as_const(eval_ctx)
@ -993,7 +989,7 @@ class And(BinExpr):
operator = "and"
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = 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)
@ -1003,7 +999,7 @@ class Or(BinExpr):
operator = "or"
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
def as_const(self, eval_ctx: EvalContext | None = 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)
@ -1086,7 +1082,7 @@ class MarkSafe(Expr):
fields = ("expr",)
expr: Expr
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> Markup:
def as_const(self, eval_ctx: EvalContext | None = None) -> Markup:
eval_ctx = get_eval_context(self, eval_ctx)
return Markup(self.expr.as_const(eval_ctx))
@ -1101,9 +1097,7 @@ class MarkSafeIfAutoescape(Expr):
fields = ("expr",)
expr: Expr
def as_const(
self, eval_ctx: t.Optional[EvalContext] = None
) -> t.Union[Markup, t.Any]:
def as_const(self, eval_ctx: EvalContext | None = None) -> Markup | t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
if eval_ctx.volatile:
raise Impossible()
@ -1152,7 +1146,7 @@ class Scope(Stmt):
"""An artificial scope."""
fields = ("body",)
body: t.List[Node]
body: list[Node]
class OverlayScope(Stmt):
@ -1171,7 +1165,7 @@ class OverlayScope(Stmt):
fields = ("context", "body")
context: Expr
body: t.List[Node]
body: list[Node]
class EvalContextModifier(Stmt):
@ -1184,7 +1178,7 @@ class EvalContextModifier(Stmt):
"""
fields = ("options",)
options: t.List[Keyword]
options: list[Keyword]
class ScopedEvalContextModifier(EvalContextModifier):
@ -1194,7 +1188,7 @@ class ScopedEvalContextModifier(EvalContextModifier):
"""
fields = ("body",)
body: t.List[Node]
body: list[Node]
# make sure nobody creates custom nodes

View File

@ -25,7 +25,7 @@ def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node:
class Optimizer(NodeTransformer):
def __init__(self, environment: "t.Optional[Environment]") -> None:
def __init__(self, environment: "Environment | None") -> None:
self.environment = environment
def generic_visit(

View File

@ -35,7 +35,7 @@ _statement_keywords = frozenset(
)
_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])
_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = {
_math_nodes: dict[str, type[nodes.Expr]] = {
"add": nodes.Add,
"sub": nodes.Sub,
"mul": nodes.Mul,
@ -54,30 +54,30 @@ class Parser:
self,
environment: "Environment",
source: str,
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
state: t.Optional[str] = None,
name: str | None = None,
filename: str | None = None,
state: str | None = None,
) -> None:
self.environment = environment
self.stream = environment._tokenize(source, name, filename, state)
self.name = name
self.filename = filename
self.closed = False
self.extensions: t.Dict[
str, t.Callable[[Parser], t.Union[nodes.Node, t.List[nodes.Node]]]
self.extensions: dict[
str, t.Callable[[Parser], nodes.Node | list[nodes.Node]]
] = {}
for extension in environment.iter_extensions():
for tag in extension.tags:
self.extensions[tag] = extension.parse
self._last_identifier = 0
self._tag_stack: t.List[str] = []
self._end_token_stack: t.List[t.Tuple[str, ...]] = []
self._tag_stack: list[str] = []
self._end_token_stack: list[tuple[str, ...]] = []
def fail(
self,
msg: str,
lineno: t.Optional[int] = None,
exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError,
lineno: int | None = None,
exc: 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: t.Optional[str],
end_token_stack: t.List[t.Tuple[str, ...]],
lineno: t.Optional[int],
name: str | None,
end_token_stack: list[tuple[str, ...]],
lineno: int | None,
) -> "te.NoReturn":
expected: t.Set[str] = set()
expected: set[str] = set()
for exprs in end_token_stack:
expected.update(map(describe_token_expr, exprs))
if end_token_stack:
currently_looking: t.Optional[str] = " or ".join(
currently_looking: str | None = " or ".join(
map(repr, map(describe_token_expr, end_token_stack[-1]))
)
else:
@ -127,9 +127,7 @@ class Parser:
self.fail(" ".join(message), lineno)
def fail_unknown_tag(
self, name: str, lineno: t.Optional[int] = None
) -> "te.NoReturn":
def fail_unknown_tag(self, name: str, lineno: int | None = 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.
@ -138,8 +136,8 @@ class Parser:
def fail_eof(
self,
end_tokens: t.Optional[t.Tuple[str, ...]] = None,
lineno: t.Optional[int] = None,
end_tokens: tuple[str, ...] | None = None,
lineno: int | None = None,
) -> "te.NoReturn":
"""Like fail_unknown_tag but for end of template situations."""
stack = list(self._end_token_stack)
@ -147,9 +145,7 @@ class Parser:
stack.append(end_tokens)
self._fail_ut_eof(None, stack, lineno)
def is_tuple_end(
self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None
) -> bool:
def is_tuple_end(self, extra_end_rules: tuple[str, ...] | None = None) -> bool:
"""Are we at the end of a tuple?"""
if self.stream.current.type in ("variable_end", "block_end", "rparen"):
return True
@ -157,14 +153,14 @@ class Parser:
return self.stream.current.test_any(extra_end_rules) # type: ignore
return False
def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName:
def free_identifier(self, lineno: int | None = 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) -> t.Union[nodes.Node, t.List[nodes.Node]]:
def parse_statement(self) -> nodes.Node | list[nodes.Node]:
"""Parse a single statement."""
token = self.stream.current
if token.type != "name":
@ -194,8 +190,8 @@ class Parser:
self._tag_stack.pop()
def parse_statements(
self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False
) -> t.List[nodes.Node]:
self, end_tokens: tuple[str, ...], drop_needle: bool = False
) -> 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
@ -222,7 +218,7 @@ class Parser:
next(self.stream)
return result
def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]:
def parse_set(self) -> nodes.Assign | nodes.AssignBlock:
"""Parse an assign statement."""
lineno = next(self.stream).lineno
target = self.parse_assign_target(with_namespace=True)
@ -272,8 +268,8 @@ class Parser:
def parse_with(self) -> nodes.With:
node = nodes.With(lineno=next(self.stream).lineno)
targets: t.List[nodes.Expr] = []
values: t.List[nodes.Expr] = []
targets: list[nodes.Expr] = []
values: list[nodes.Expr] = []
while self.stream.current.type != "block_end":
if targets:
self.stream.expect("comma")
@ -466,17 +462,17 @@ class Parser:
self,
with_tuple: bool = True,
name_only: bool = False,
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
extra_end_rules: tuple[str, ...] | None = None,
with_namespace: bool = False,
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: ...
) -> nodes.NSRef | nodes.Name | nodes.Tuple: ...
def parse_assign_target(
self,
with_tuple: bool = True,
name_only: bool = False,
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
extra_end_rules: tuple[str, ...] | None = None,
with_namespace: bool = False,
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
) -> 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
@ -521,7 +517,7 @@ class Parser:
def parse_condexpr(self) -> nodes.Expr:
lineno = self.stream.current.lineno
expr1 = self.parse_or()
expr3: t.Optional[nodes.Expr]
expr3: nodes.Expr | None
while self.stream.skip_if("name:if"):
expr2 = self.parse_or()
@ -686,10 +682,10 @@ class Parser:
self,
simplified: bool = False,
with_condexpr: bool = True,
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
extra_end_rules: tuple[str, ...] | None = None,
explicit_parentheses: bool = False,
with_namespace: bool = False,
) -> t.Union[nodes.Tuple, nodes.Expr]:
) -> 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
@ -720,7 +716,7 @@ class Parser:
def parse() -> nodes.Expr:
return self.parse_expression(with_condexpr=with_condexpr)
args: t.List[nodes.Expr] = []
args: list[nodes.Expr] = []
is_tuple = False
while True:
@ -753,7 +749,7 @@ class Parser:
def parse_list(self) -> nodes.List:
token = self.stream.expect("lbracket")
items: t.List[nodes.Expr] = []
items: list[nodes.Expr] = []
while self.stream.current.type != "rbracket":
if items:
self.stream.expect("comma")
@ -765,7 +761,7 @@ class Parser:
def parse_dict(self) -> nodes.Dict:
token = self.stream.expect("lbrace")
items: t.List[nodes.Pair] = []
items: list[nodes.Pair] = []
while self.stream.current.type != "rbrace":
if items:
self.stream.expect("comma")
@ -806,9 +802,7 @@ class Parser:
break
return node
def parse_subscript(
self, node: nodes.Expr
) -> t.Union[nodes.Getattr, nodes.Getitem]:
def parse_subscript(self, node: nodes.Expr) -> nodes.Getattr | nodes.Getitem:
token = next(self.stream)
arg: nodes.Expr
@ -824,7 +818,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: t.List[nodes.Expr] = []
args: list[nodes.Expr] = []
while self.stream.current.type != "rbracket":
if args:
self.stream.expect("comma")
@ -839,7 +833,7 @@ class Parser:
def parse_subscribed(self) -> nodes.Expr:
lineno = self.stream.current.lineno
args: t.List[t.Optional[nodes.Expr]]
args: list[nodes.Expr | None]
if self.stream.current.type == "colon":
next(self.stream)
@ -871,11 +865,11 @@ class Parser:
def parse_call_args(
self,
) -> t.Tuple[
t.List[nodes.Expr],
t.List[nodes.Keyword],
t.Optional[nodes.Expr],
t.Optional[nodes.Expr],
) -> tuple[
list[nodes.Expr],
list[nodes.Keyword],
nodes.Expr | None,
nodes.Expr | None,
]:
token = self.stream.expect("lparen")
args = []
@ -933,8 +927,8 @@ class Parser:
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)
def parse_filter(
self, node: t.Optional[nodes.Expr], start_inline: bool = False
) -> t.Optional[nodes.Expr]:
self, node: nodes.Expr | None, start_inline: bool = False
) -> nodes.Expr | None:
while self.stream.current.type == "pipe" or start_inline:
if not start_inline:
next(self.stream)
@ -967,7 +961,7 @@ class Parser:
next(self.stream)
name += "." + self.stream.expect("name").value
dyn_args = dyn_kwargs = None
kwargs: t.List[nodes.Keyword] = []
kwargs: 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 {
@ -993,11 +987,9 @@ class Parser:
node = nodes.Not(node, lineno=token.lineno)
return node
def subparse(
self, end_tokens: t.Optional[t.Tuple[str, ...]] = None
) -> t.List[nodes.Node]:
body: t.List[nodes.Node] = []
data_buffer: t.List[nodes.Node] = []
def subparse(self, end_tokens: tuple[str, ...] | None = None) -> list[nodes.Node]:
body: list[nodes.Node] = []
data_buffer: list[nodes.Node] = []
add_data = data_buffer.append
if end_tokens is not None:

View File

@ -92,12 +92,12 @@ def str_join(seq: t.Iterable[t.Any]) -> str:
def new_context(
environment: "Environment",
template_name: t.Optional[str],
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
vars: t.Optional[t.Dict[str, t.Any]] = None,
template_name: str | None,
blocks: dict[str, t.Callable[["Context"], t.Iterator[str]]],
vars: dict[str, t.Any] | None = None,
shared: bool = False,
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
locals: t.Optional[t.Mapping[str, t.Any]] = None,
globals: t.MutableMapping[str, t.Any] | None = None,
locals: t.Mapping[str, t.Any] | None = None,
) -> "Context":
"""Internal helper for context creation."""
if vars is None:
@ -165,16 +165,16 @@ class Context:
def __init__(
self,
environment: "Environment",
parent: t.Dict[str, t.Any],
name: t.Optional[str],
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
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,
):
self.parent = parent
self.vars: t.Dict[str, t.Any] = {}
self.vars: dict[str, t.Any] = {}
self.environment: Environment = environment
self.eval_ctx = EvalContext(self.environment, name)
self.exported_vars: t.Set[str] = set()
self.exported_vars: 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) -> t.Dict[str, t.Any]:
def get_exported(self) -> 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) -> t.Dict[str, t.Any]:
def get_all(self) -> 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,
__self, # noqa: B902
__obj: t.Callable[..., t.Any],
*args: t.Any,
**kwargs: t.Any, # noqa: B902
**kwargs: t.Any,
) -> 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: t.Optional[t.Dict[str, t.Any]] = None) -> "Context":
def derived(self, locals: dict[str, t.Any] | None = 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: t.List[t.Callable[["Context"], t.Iterator[str]]],
stack: 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: t.Optional[int] = None
_length: int | None = 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: t.Type["Undefined"],
undefined: 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) -> t.Tuple[t.Any, "LoopContext"]:
def __next__(self) -> 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.Union[t.Iterable[V], t.AsyncIterable[V]],
iterable: 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) -> t.Tuple[t.Any, "AsyncLoopContext"]:
async def __anext__(self) -> 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: t.List[str],
arguments: list[str],
catch_kwargs: bool,
catch_varargs: bool,
caller: bool,
default_autoescape: t.Optional[bool] = None,
default_autoescape: bool | None = None,
):
self._environment = environment
self._func = func
@ -769,7 +769,7 @@ class Macro:
return self._invoke(arguments, autoescape)
async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:
async def _async_invoke(self, arguments: 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: t.List[t.Any], autoescape: bool) -> str:
def _invoke(self, arguments: 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: t.Optional[str] = None,
hint: str | None = None,
obj: t.Any = missing,
name: t.Optional[str] = None,
exc: t.Type[TemplateRuntimeError] = UndefinedError,
name: str | None = None,
exc: 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: t.Type[Undefined] = Undefined
) -> t.Type[Undefined]:
logger: t.Optional["logging.Logger"] = None, base: type[Undefined] = Undefined
) -> 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.

View File

@ -25,10 +25,10 @@ F = t.TypeVar("F", bound=t.Callable[..., t.Any])
MAX_RANGE = 100000
#: Unsafe function attributes.
UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
UNSAFE_FUNCTION_ATTRIBUTES: set[str] = set()
#: Unsafe method attributes. Function attributes are unsafe for methods too.
UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
UNSAFE_METHOD_ATTRIBUTES: 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: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = (
_mutable_spec: tuple[tuple[type[t.Any], 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: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
default_binop_table: 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: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
default_unop_table: 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: t.FrozenSet[str] = frozenset()
intercepted_binops: 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: t.FrozenSet[str] = frozenset()
intercepted_unops: frozenset[str] = frozenset()
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
super().__init__(*args, **kwargs)
@ -285,9 +285,7 @@ class SandboxedEnvironment(Environment):
"""
return self.unop_table[operator](arg)
def getitem(
self, obj: t.Any, argument: t.Union[str, t.Any]
) -> t.Union[t.Any, Undefined]:
def getitem(self, obj: t.Any, argument: str | t.Any) -> t.Any | Undefined:
"""Subscribe an object from sandboxed code."""
try:
return obj[argument]
@ -311,7 +309,7 @@ class SandboxedEnvironment(Environment):
return self.unsafe_undefined(obj, argument)
return self.undefined(obj=obj, name=argument)
def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
def getattr(self, obj: t.Any, attribute: str) -> t.Any | Undefined:
"""Subscribe an object from sandboxed code and prefer the
attribute. The attribute passed *must* be a bytestring.
"""
@ -341,7 +339,7 @@ class SandboxedEnvironment(Environment):
exc=SecurityError,
)
def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]:
def wrap_str_format(self, value: t.Any) -> t.Callable[..., str] | None:
"""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
@ -357,7 +355,7 @@ class SandboxedEnvironment(Environment):
if not isinstance(f_self, str):
return None
str_type: t.Type[str] = type(f_self)
str_type: type[str] = type(f_self)
is_format_map = value.__name__ == "format_map"
formatter: SandboxedFormatter
@ -421,7 +419,7 @@ class SandboxedFormatter(Formatter):
def get_field(
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
) -> t.Tuple[t.Any, str]:
) -> 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:

View File

@ -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.Optional[t.IO[t.Any]]:
def open_if_exists(filename: str, mode: str = "rb") -> t.IO[t.Any] | None:
"""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: t.Optional[int] = None,
rel: t.Optional[str] = None,
target: t.Optional[str] = None,
extra_schemes: t.Optional[t.Iterable[str]] = None,
trim_url_limit: int | None = None,
rel: str | None = None,
target: str | None = None,
extra_schemes: t.Iterable[str] | None = 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: t.Dict[t.Any, t.Any] = {}
self._queue: te.Deque[t.Any] = deque()
self._mapping: dict[t.Any, t.Any] = {}
self._queue: 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) -> t.Tuple[t.Any, ...]:
def __getnewargs__(self) -> 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[t.Tuple[t.Any, t.Any]]:
def items(self) -> t.Iterable[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[[t.Optional[str]], bool]:
) -> t.Callable[[str | None], 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: t.Optional[str]) -> bool:
def autoescape(template_name: str | None) -> 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.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
obj: t.Any, dumps: t.Callable[..., str] | None = 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

View File

@ -25,7 +25,7 @@ class NodeVisitor:
(return value `None`) the `generic_visit` visitor is used instead.
"""
def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]":
def get_visitor(self, node: Node) -> "VisitCallable | None":
"""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) -> t.List[Node]:
def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> list[Node]:
"""As transformers may return lists in some places this method
can be used to enforce a list as return value.
"""

View File

@ -1,11 +1,22 @@
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."""

View File

@ -1,7 +1,4 @@
import asyncio
import pytest
import trio
from jinja2 import ChainableUndefined
from jinja2 import DictLoader
@ -14,15 +11,6 @@ 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
@ -461,9 +449,7 @@ 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"

View File

@ -1,9 +1,7 @@
import asyncio
import contextlib
from collections import namedtuple
import pytest
import trio
from markupsafe import Markup
from jinja2 import Environment
@ -29,15 +27,6 @@ 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:

View File

@ -191,9 +191,7 @@ 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"

View File

@ -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():

View File

@ -554,8 +554,7 @@ 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: &lt;test&gt;</strong>"
assert t.render(ae=False) == "<strong>Wert: <test></strong>"

View File

@ -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() == (

View File

@ -43,8 +43,7 @@ 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 %}"

View File

@ -13,6 +13,11 @@ 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()
@ -122,6 +127,18 @@ 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
View File

@ -1,63 +0,0 @@
[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}

1244
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff