update project files (#1968)
This commit is contained in:
commit
da3a9f0b80
17
.devcontainer/devcontainer.json
Normal file
17
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "pallets/jinja",
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/.venv",
|
||||
"python.terminal.activateEnvInCurrentTerminal": true,
|
||||
"python.terminal.launchArgs": [
|
||||
"-X",
|
||||
"dev"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"onCreateCommand": ".devcontainer/on-create-command.sh"
|
||||
}
|
||||
7
.devcontainer/on-create-command.sh
Executable file
7
.devcontainer/on-create-command.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
python3 -m venv --upgrade-deps .venv
|
||||
. .venv/bin/activate
|
||||
pip install -r requirements/dev.txt
|
||||
pip install -e .
|
||||
pre-commit install --install-hooks
|
||||
@ -9,5 +9,5 @@ end_of_line = lf
|
||||
charset = utf-8
|
||||
max_line_length = 88
|
||||
|
||||
[*.{yml,yaml,json,js,css,html}]
|
||||
[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
28
.flake8
28
.flake8
@ -1,28 +0,0 @@
|
||||
[flake8]
|
||||
extend-select =
|
||||
# bugbear
|
||||
B
|
||||
# bugbear opinions
|
||||
B9
|
||||
# implicit str concat
|
||||
ISC
|
||||
extend-ignore =
|
||||
# slice notation whitespace, invalid
|
||||
E203
|
||||
# line length, handled by bugbear B950
|
||||
E501
|
||||
# bare except, handled by bugbear B001
|
||||
E722
|
||||
# zip with strict=, requires python >= 3.10
|
||||
B905
|
||||
# string formatting opinion, B028 renamed to B907
|
||||
B028
|
||||
B907
|
||||
# up to 88 allowed by bugbear B950
|
||||
max-line-length = 80
|
||||
per-file-ignores =
|
||||
# __init__ exports names
|
||||
src/jinja2/__init__.py: F401
|
||||
# not relevant to jinja's compiler
|
||||
src/jinja2/compiler.py: B906
|
||||
src/jinja2/idtracking.py: B906
|
||||
4
.github/ISSUE_TEMPLATE/bug-report.md
vendored
4
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -4,8 +4,8 @@ about: Report a bug in Jinja (not other projects which depend on Jinja)
|
||||
---
|
||||
|
||||
<!--
|
||||
This issue tracker is a tool to address bugs in Jinja itself. Please
|
||||
use Pallets Discord or Stack Overflow for questions about your own code.
|
||||
This issue tracker is a tool to address bugs in Jinja itself. Please use
|
||||
GitHub Discussions or the Pallets Discord for questions about your own code.
|
||||
|
||||
Replace this comment with a clear outline of what the bug is.
|
||||
-->
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/config.yml
vendored
13
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security issue
|
||||
url: security@palletsprojects.com
|
||||
about: Do not report security issues publicly. Email our security contact.
|
||||
- name: Questions
|
||||
url: https://stackoverflow.com/questions/tagged/Jinja?tab=Frequent
|
||||
about: Search for and ask questions about your code on Stack Overflow.
|
||||
- name: Questions and discussions
|
||||
- name: Questions on Discussions
|
||||
url: https://github.com/pallets/jinja/discussions/
|
||||
about: Ask questions about your own code on the Discussions tab.
|
||||
- name: Questions on Chat
|
||||
url: https://discord.gg/pallets
|
||||
about: Discuss questions about your code on our Discord chat.
|
||||
about: Ask questions about your own code on our Discord chat.
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/feature-request.md
vendored
6
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -5,11 +5,11 @@ about: Suggest a new feature for Jinja
|
||||
|
||||
<!--
|
||||
Replace this comment with a description of what the feature should do.
|
||||
Include details such as links relevant specs or previous discussions.
|
||||
Include details such as links to relevant specs or previous discussions.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Replace this comment with an example of the problem which this feature
|
||||
would resolve. Is this problem solvable without changes to Jinja,
|
||||
such as by subclassing or using an extension?
|
||||
would resolve. Is this problem solvable without changes to Jinja, such
|
||||
as by subclassing or using an extension?
|
||||
-->
|
||||
|
||||
29
.github/dependabot.yml
vendored
29
.github/dependabot.yml
vendored
@ -1,9 +1,24 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
day: "monday"
|
||||
time: "16:00"
|
||||
timezone: "UTC"
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
ignore:
|
||||
# slsa depends on upload/download v3
|
||||
- dependency-name: actions/upload-artifact
|
||||
versions: '>= 4'
|
||||
- dependency-name: actions/download-artifact
|
||||
versions: '>= 4'
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- '*'
|
||||
- package-ecosystem: pip
|
||||
directory: /requirements/
|
||||
schedule:
|
||||
interval: monthly
|
||||
groups:
|
||||
python-requirements:
|
||||
patterns:
|
||||
- '*'
|
||||
|
||||
31
.github/pull_request_template.md
vendored
31
.github/pull_request_template.md
vendored
@ -1,26 +1,25 @@
|
||||
<!--
|
||||
Before opening a PR, open a ticket describing the issue or feature the PR will address. Follow the steps in CONTRIBUTING.rst.
|
||||
Before opening a PR, open a ticket describing the issue or feature the
|
||||
PR will address. An issue is not required for fixing typos in
|
||||
documentation, or other simple non-code changes.
|
||||
|
||||
Replace this comment with a description of the change. Describe how it addresses the linked ticket.
|
||||
Replace this comment with a description of the change. Describe how it
|
||||
addresses the linked ticket.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Link to relevant issues or previous PRs, one per line. Use "fixes" to automatically close an issue.
|
||||
-->
|
||||
Link to relevant issues or previous PRs, one per line. Use "fixes" to
|
||||
automatically close an issue.
|
||||
|
||||
- fixes #<issue number>
|
||||
fixes #<issue number>
|
||||
-->
|
||||
|
||||
<!--
|
||||
Ensure each step in CONTRIBUTING.rst is complete by adding an "x" to each box below.
|
||||
Ensure each step in CONTRIBUTING.rst is complete, especially the following:
|
||||
|
||||
If only docs were changed, these aren't relevant and can be removed.
|
||||
- Add tests that demonstrate the correct behavior of the change. Tests
|
||||
should fail without the change.
|
||||
- Add or update relevant docs, in the docs folder and in code.
|
||||
- Add an entry in CHANGES.rst summarizing the change and linking to the issue.
|
||||
- Add `.. versionchanged::` entries in any relevant code docs.
|
||||
-->
|
||||
|
||||
Checklist:
|
||||
|
||||
- [ ] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
|
||||
- [ ] Add or update relevant docs, in the docs folder and in code.
|
||||
- [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
|
||||
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
|
||||
- [ ] Run `pre-commit` hooks and fix any issues.
|
||||
- [ ] Run `pytest` and `tox`, no tests failed.
|
||||
|
||||
16
.github/workflows/lock.yaml
vendored
16
.github/workflows/lock.yaml
vendored
@ -1,25 +1,23 @@
|
||||
name: 'Lock threads'
|
||||
# Lock closed issues that have not received any further activity for
|
||||
# two weeks. This does not close open issues, only humans may do that.
|
||||
# We find that it is easier to respond to new issues with fresh examples
|
||||
# rather than continuing discussions on old issues.
|
||||
name: Lock inactive closed issues
|
||||
# Lock closed issues that have not received any further activity for two weeks.
|
||||
# This does not close open issues, only humans may do that. It is easier to
|
||||
# respond to new issues with fresh examples rather than continuing discussions
|
||||
# on old issues.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@c1b35aecc5cdb1a34539d14196df55838bb2f836
|
||||
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||
with:
|
||||
issue-inactive-days: 14
|
||||
pr-inactive-days: 14
|
||||
discussion-inactive-days: 14
|
||||
|
||||
32
.github/workflows/publish.yaml
vendored
32
.github/workflows/publish.yaml
vendored
@ -9,12 +9,12 @@ jobs:
|
||||
outputs:
|
||||
hash: ${{ steps.hash.outputs.hash }}
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
|
||||
- uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
|
||||
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'requirements/*.txt'
|
||||
cache: pip
|
||||
cache-dependency-path: requirements*/*.txt
|
||||
- run: pip install -r requirements/build.txt
|
||||
# Use the commit date instead of the current date during the build.
|
||||
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
|
||||
@ -23,28 +23,28 @@ jobs:
|
||||
- name: generate hash
|
||||
id: hash
|
||||
run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
path: ./dist
|
||||
provenance:
|
||||
needs: ['build']
|
||||
needs: [build]
|
||||
permissions:
|
||||
actions: read
|
||||
id-token: write
|
||||
contents: write
|
||||
# Can't pin with hash due to how this workflow works.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.4.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
|
||||
with:
|
||||
base64-subjects: ${{ needs.build.outputs.hash }}
|
||||
create-release:
|
||||
# Upload the sdist, wheels, and provenance to a GitHub release. They remain
|
||||
# available as build artifacts for a while as well.
|
||||
needs: ['provenance']
|
||||
needs: [provenance]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
- name: create release
|
||||
run: >
|
||||
gh release create --draft --repo ${{ github.repository }}
|
||||
@ -53,15 +53,21 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
publish-pypi:
|
||||
needs: ['provenance']
|
||||
needs: [provenance]
|
||||
# Wait for approval before attempting to upload to PyPI. This allows reviewing the
|
||||
# files in the draft release.
|
||||
environment: 'publish'
|
||||
environment:
|
||||
name: publish
|
||||
url: https://pypi.org/project/Jinja2/${{ github.ref_name }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a
|
||||
- uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
- uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
packages-dir: artifact/
|
||||
- uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
|
||||
with:
|
||||
packages-dir: artifact/
|
||||
|
||||
58
.github/workflows/tests.yaml
vendored
58
.github/workflows/tests.yaml
vendored
@ -9,48 +9,50 @@ on:
|
||||
- '*.md'
|
||||
- '*.rst'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- '*.x'
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
- '*.rst'
|
||||
jobs:
|
||||
tests:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.name || matrix.python }}
|
||||
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {name: Linux, python: '3.11', os: ubuntu-latest, tox: py311}
|
||||
- {name: Windows, python: '3.11', os: windows-latest, tox: py311}
|
||||
- {name: Mac, python: '3.11', os: macos-latest, tox: py311}
|
||||
- {name: '3.12-dev', python: '3.12-dev', os: ubuntu-latest, tox: py312}
|
||||
- {name: '3.10', python: '3.10', os: ubuntu-latest, tox: py310}
|
||||
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
|
||||
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
|
||||
- {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37}
|
||||
- {name: 'PyPy', python: 'pypy-3.10', os: ubuntu-latest, tox: pypy310}
|
||||
- {name: Typing, python: '3.11', os: ubuntu-latest, tox: typing}
|
||||
- {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}
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
|
||||
- uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
|
||||
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'requirements/*.txt'
|
||||
- name: update pip
|
||||
run: |
|
||||
pip install -U wheel
|
||||
pip install -U setuptools
|
||||
python -m pip install -U pip
|
||||
allow-prereleases: true
|
||||
cache: pip
|
||||
cache-dependency-path: requirements*/*.txt
|
||||
- run: pip install tox
|
||||
- run: tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
|
||||
typing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
|
||||
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
cache: pip
|
||||
cache-dependency-path: requirements*/*.txt
|
||||
- name: cache mypy
|
||||
uses: actions/cache@58c146cc91c5b9e778e71775dfe9bf1442ad9a12
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: ./.mypy_cache
|
||||
key: mypy|${{ matrix.python }}|${{ hashFiles('setup.cfg') }}
|
||||
if: matrix.tox == 'typing'
|
||||
key: mypy|${{ hashFiles('pyproject.toml') }}
|
||||
- run: pip install tox
|
||||
- run: tox run -e ${{ matrix.tox }}
|
||||
- run: tox run -e typing
|
||||
|
||||
29
.gitignore
vendored
29
.gitignore
vendored
@ -1,21 +1,10 @@
|
||||
*.so
|
||||
docs/_build/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.egg-info/
|
||||
*.egg
|
||||
build/
|
||||
dist/
|
||||
.DS_Store
|
||||
.tox/
|
||||
.cache/
|
||||
.idea/
|
||||
env/
|
||||
venv/
|
||||
venv-*/
|
||||
.coverage
|
||||
.coverage.*
|
||||
htmlcov
|
||||
.pytest_cache/
|
||||
/.vscode/
|
||||
.mypy_cache
|
||||
.vscode/
|
||||
.venv*/
|
||||
venv*/
|
||||
__pycache__/
|
||||
dist/
|
||||
.coverage*
|
||||
htmlcov/
|
||||
.tox/
|
||||
docs/_build/
|
||||
|
||||
@ -1,33 +1,16 @@
|
||||
ci:
|
||||
autoupdate_branch: "3.1.x"
|
||||
autoupdate_schedule: monthly
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.0
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ["--py37-plus"]
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.12.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ["--application-directories", "src"]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.12.1
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [flake8-bugbear]
|
||||
- repo: https://github.com/peterdemin/pip-compile-multi
|
||||
rev: v2.6.3
|
||||
hooks:
|
||||
- id: pip-compile-multi-verify
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: debug-statements
|
||||
- id: fix-byte-order-marker
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
version: 2
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
python: '3.12'
|
||||
python:
|
||||
install:
|
||||
- requirements: requirements/docs.txt
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at report@palletsprojects.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
10
MANIFEST.in
10
MANIFEST.in
@ -1,10 +0,0 @@
|
||||
include CHANGES.rst
|
||||
include tox.ini
|
||||
include requirements/*.txt
|
||||
graft artwork
|
||||
graft docs
|
||||
prune docs/_build
|
||||
graft examples
|
||||
graft tests
|
||||
include src/jinja2/py.typed
|
||||
global-exclude *.pyc
|
||||
@ -1,5 +1,4 @@
|
||||
Jinja
|
||||
=====
|
||||
# Jinja
|
||||
|
||||
Jinja is a fast, expressive, extensible templating engine. Special
|
||||
placeholders in the template allow writing code similar to Python
|
||||
@ -26,20 +25,7 @@ possible, it shouldn't make the template designer's job difficult by
|
||||
restricting functionality too much.
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Install and update using `pip`_:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install -U Jinja2
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/getting-started/
|
||||
|
||||
|
||||
In A Nutshell
|
||||
-------------
|
||||
## In A Nutshell
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
@ -54,23 +40,11 @@ In A Nutshell
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Donate
|
||||
------
|
||||
## Donate
|
||||
|
||||
The Pallets organization develops and supports Jinja and other popular
|
||||
packages. In order to grow the community of contributors and users, and
|
||||
allow the maintainers to devote more time to the projects, `please
|
||||
donate today`_.
|
||||
allow the maintainers to devote more time to the projects, [please
|
||||
donate today][].
|
||||
|
||||
.. _please donate today: https://palletsprojects.com/donate
|
||||
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
- Documentation: https://jinja.palletsprojects.com/
|
||||
- Changes: https://jinja.palletsprojects.com/changes/
|
||||
- PyPI Releases: https://pypi.org/project/Jinja2/
|
||||
- Source Code: https://github.com/pallets/jinja/
|
||||
- Issue Tracker: https://github.com/pallets/jinja/issues/
|
||||
- Chat: https://discord.gg/pallets
|
||||
[please donate today]: https://palletsprojects.com/donate
|
||||
21
docs/conf.py
21
docs/conf.py
@ -10,17 +10,24 @@ release, version = get_version("Jinja2")
|
||||
|
||||
# General --------------------------------------------------------------
|
||||
|
||||
master_doc = "index"
|
||||
default_role = "code"
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.extlinks",
|
||||
"sphinx.ext.intersphinx",
|
||||
"pallets_sphinx_themes",
|
||||
"sphinxcontrib.log_cabinet",
|
||||
"sphinx_issues",
|
||||
"pallets_sphinx_themes",
|
||||
]
|
||||
autodoc_member_order = "bysource"
|
||||
autodoc_typehints = "description"
|
||||
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
|
||||
issues_github_path = "pallets/jinja"
|
||||
autodoc_preserve_defaults = True
|
||||
extlinks = {
|
||||
"issue": ("https://github.com/pallets/jinja/issues/%s", "#%s"),
|
||||
"pr": ("https://github.com/pallets/jinja/pull/%s", "#%s"),
|
||||
}
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3/", None),
|
||||
}
|
||||
|
||||
# HTML -----------------------------------------------------------------
|
||||
|
||||
@ -45,7 +52,3 @@ html_favicon = "_static/jinja-logo-sidebar.png"
|
||||
html_logo = "_static/jinja-logo-sidebar.png"
|
||||
html_title = f"Jinja Documentation ({version})"
|
||||
html_show_sourcelink = False
|
||||
|
||||
# LaTeX ----------------------------------------------------------------
|
||||
|
||||
latex_documents = [(master_doc, f"Jinja-{version}.tex", html_title, author, "manual")]
|
||||
|
||||
@ -5,7 +5,6 @@ from jinja2.ext import Extension
|
||||
from jinja2.lexer import count_newlines
|
||||
from jinja2.lexer import Token
|
||||
|
||||
|
||||
_outside_re = re.compile(r"\\?(gettext|_)\(")
|
||||
_inside_re = re.compile(r"\\?[()]")
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
BSD-3-Clause License
|
||||
====================
|
||||
|
||||
.. include:: ../LICENSE.rst
|
||||
.. literalinclude:: ../LICENSE.txt
|
||||
:language: text
|
||||
|
||||
98
pyproject.toml
Normal file
98
pyproject.toml
Normal file
@ -0,0 +1,98 @@
|
||||
[project]
|
||||
name = "Jinja2"
|
||||
description = "A very fast and expressive template engine."
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE.txt"}
|
||||
maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Web Environment",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||
"Topic :: Text Processing :: Markup :: HTML",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
requires-python = ">=3.7"
|
||||
dependencies = ["MarkupSafe>=2.0"]
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.urls]
|
||||
Donate = "https://palletsprojects.com/donate"
|
||||
Documentation = "https://jinja.palletsprojects.com/"
|
||||
Changes = "https://jinja.palletsprojects.com/changes/"
|
||||
Source = "https://github.com/pallets/jinja/"
|
||||
Chat = "https://discord.gg/pallets"
|
||||
|
||||
[project.optional-dependencies]
|
||||
i18n = ["Babel>=2.7"]
|
||||
|
||||
[project.entry-points."babel.extractors"]
|
||||
jinja2 = "jinja2.ext:babel_extract[i18n]"
|
||||
|
||||
[build-system]
|
||||
requires = ["flit_core<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
[tool.flit.module]
|
||||
name = "jinja2"
|
||||
|
||||
[tool.flit.sdist]
|
||||
include = [
|
||||
"docs/",
|
||||
"requirements/",
|
||||
"tests/",
|
||||
"CHANGES.md",
|
||||
"tox.ini",
|
||||
]
|
||||
exclude = [
|
||||
"docs/_build/",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
filterwarnings = [
|
||||
"error",
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
source = ["jinja2", "tests"]
|
||||
|
||||
[tool.coverage.paths]
|
||||
source = ["src", "*/site-packages"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
files = ["src/jinja2"]
|
||||
show_error_codes = true
|
||||
pretty = true
|
||||
strict = true
|
||||
|
||||
[tool.pyright]
|
||||
pythonVersion = "3.8"
|
||||
include = ["src/jinja2"]
|
||||
typeCheckingMode = "basic"
|
||||
|
||||
[tool.ruff]
|
||||
src = ["src"]
|
||||
fix = true
|
||||
show-fixes = true
|
||||
output-format = "full"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"B", # flake8-bugbear
|
||||
"E", # pycodestyle error
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"UP", # pyupgrade
|
||||
"W", # pycodestyle warning
|
||||
]
|
||||
ignore-init-module-imports = true
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
order-by-type = false
|
||||
81
setup.cfg
81
setup.cfg
@ -1,81 +0,0 @@
|
||||
[metadata]
|
||||
name = Jinja2
|
||||
version = attr: jinja2.__version__
|
||||
url = https://palletsprojects.com/p/jinja/
|
||||
project_urls =
|
||||
Donate = https://palletsprojects.com/donate
|
||||
Documentation = https://jinja.palletsprojects.com/
|
||||
Changes = https://jinja.palletsprojects.com/changes/
|
||||
Source Code = https://github.com/pallets/jinja/
|
||||
Issue Tracker = https://github.com/pallets/jinja/issues/
|
||||
Chat = https://discord.gg/pallets
|
||||
license = BSD-3-Clause
|
||||
license_files = LICENSE.rst
|
||||
maintainer = Pallets
|
||||
maintainer_email = contact@palletsprojects.com
|
||||
description = A very fast and expressive template engine.
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
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
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
package_dir = = src
|
||||
include_package_data = True
|
||||
python_requires = >= 3.7
|
||||
# Dependencies are in setup.py for GitHub's dependency graph.
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
||||
[options.entry_points]
|
||||
babel.extractors =
|
||||
jinja2 = jinja2.ext:babel_extract[i18n]
|
||||
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
filterwarnings =
|
||||
error
|
||||
|
||||
[coverage:run]
|
||||
branch = True
|
||||
source =
|
||||
jinja2
|
||||
tests
|
||||
|
||||
[coverage:paths]
|
||||
source =
|
||||
src
|
||||
*/site-packages
|
||||
|
||||
[mypy]
|
||||
files = src/jinja2
|
||||
python_version = 3.7
|
||||
show_error_codes = True
|
||||
disallow_subclassing_any = True
|
||||
disallow_untyped_calls = True
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
no_implicit_optional = True
|
||||
local_partial_types = True
|
||||
no_implicit_reexport = True
|
||||
strict_equality = True
|
||||
warn_redundant_casts = True
|
||||
warn_unused_configs = True
|
||||
warn_unused_ignores = True
|
||||
warn_return_any = True
|
||||
warn_unreachable = True
|
||||
|
||||
[mypy-jinja2.defaults]
|
||||
no_implicit_reexport = False
|
||||
|
||||
[mypy-markupsafe]
|
||||
no_implicit_reexport = False
|
||||
8
setup.py
8
setup.py
@ -1,8 +0,0 @@
|
||||
from setuptools import setup
|
||||
|
||||
# Metadata goes in setup.cfg. These are here for GitHub's dependency graph.
|
||||
setup(
|
||||
name="Jinja2",
|
||||
install_requires=["MarkupSafe>=2.0"],
|
||||
extras_require={"i18n": ["Babel>=2.7"]},
|
||||
)
|
||||
@ -2,6 +2,7 @@
|
||||
non-XML syntax that supports inline expressions and an optional
|
||||
sandboxed environment.
|
||||
"""
|
||||
|
||||
from .bccache import BytecodeCache as BytecodeCache
|
||||
from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
|
||||
from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
|
||||
|
||||
@ -47,7 +47,7 @@ def async_variant(normal_func): # type: ignore
|
||||
if need_eval_context:
|
||||
wrapper = pass_eval_context(wrapper)
|
||||
|
||||
wrapper.jinja_async_variant = True
|
||||
wrapper.jinja_async_variant = True # type: ignore[attr-defined]
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
@ -5,6 +5,7 @@ slows down your application too much.
|
||||
Situations where this is useful are often forking web applications that
|
||||
are initialized on the first request.
|
||||
"""
|
||||
|
||||
import errno
|
||||
import fnmatch
|
||||
import marshal
|
||||
@ -20,14 +21,15 @@ from types import CodeType
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
class _MemcachedClient(te.Protocol):
|
||||
def get(self, key: str) -> bytes:
|
||||
...
|
||||
def 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: t.Optional[int] = None
|
||||
) -> None: ...
|
||||
|
||||
|
||||
bc_version = 5
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"""Compiles nodes from the parser into Python code."""
|
||||
|
||||
import typing as t
|
||||
from contextlib import contextmanager
|
||||
from functools import update_wrapper
|
||||
@ -24,6 +25,7 @@ from .visitor import NodeVisitor
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
@ -60,8 +62,7 @@ def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"]
|
||||
@optimizeconst
|
||||
def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None:
|
||||
if (
|
||||
self.environment.sandboxed
|
||||
and op in self.environment.intercepted_binops # type: ignore
|
||||
self.environment.sandboxed and op in self.environment.intercepted_binops # type: ignore
|
||||
):
|
||||
self.write(f"environment.call_binop(context, {op!r}, ")
|
||||
self.visit(node.left, frame)
|
||||
@ -84,8 +85,7 @@ def _make_unop(
|
||||
@optimizeconst
|
||||
def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None:
|
||||
if (
|
||||
self.environment.sandboxed
|
||||
and op in self.environment.intercepted_unops # type: ignore
|
||||
self.environment.sandboxed and op in self.environment.intercepted_unops # type: ignore
|
||||
):
|
||||
self.write(f"environment.call_unop(context, {op!r}, ")
|
||||
self.visit(node.node, frame)
|
||||
@ -133,7 +133,7 @@ def has_safe_repr(value: t.Any) -> bool:
|
||||
if type(value) in {tuple, list, set, frozenset}:
|
||||
return all(has_safe_repr(v) for v in value)
|
||||
|
||||
if type(value) is dict:
|
||||
if type(value) is dict: # noqa E721
|
||||
return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())
|
||||
|
||||
return False
|
||||
@ -551,10 +551,13 @@ class CodeGenerator(NodeVisitor):
|
||||
for node in nodes:
|
||||
visitor.visit(node)
|
||||
|
||||
for id_map, names, dependency in (self.filters, visitor.filters, "filters"), (
|
||||
self.tests,
|
||||
visitor.tests,
|
||||
"tests",
|
||||
for id_map, names, dependency in (
|
||||
(self.filters, visitor.filters, "filters"),
|
||||
(
|
||||
self.tests,
|
||||
visitor.tests,
|
||||
"tests",
|
||||
),
|
||||
):
|
||||
for name in sorted(names):
|
||||
if name not in id_map:
|
||||
@ -829,7 +832,8 @@ class CodeGenerator(NodeVisitor):
|
||||
assert frame is None, "no root frame allowed"
|
||||
eval_ctx = EvalContext(self.environment, self.name)
|
||||
|
||||
from .runtime import exported, async_exported
|
||||
from .runtime import async_exported
|
||||
from .runtime import exported
|
||||
|
||||
if self.environment.is_async:
|
||||
exported_names = sorted(exported + async_exported)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""Classes for managing templates and their runtime and compile time
|
||||
options.
|
||||
"""
|
||||
|
||||
import os
|
||||
import typing
|
||||
import typing as t
|
||||
@ -20,10 +21,10 @@ from .defaults import BLOCK_END_STRING
|
||||
from .defaults import BLOCK_START_STRING
|
||||
from .defaults import COMMENT_END_STRING
|
||||
from .defaults import COMMENT_START_STRING
|
||||
from .defaults import DEFAULT_FILTERS
|
||||
from .defaults import DEFAULT_FILTERS # type: ignore[attr-defined]
|
||||
from .defaults import DEFAULT_NAMESPACE
|
||||
from .defaults import DEFAULT_POLICIES
|
||||
from .defaults import DEFAULT_TESTS
|
||||
from .defaults import DEFAULT_TESTS # type: ignore[attr-defined]
|
||||
from .defaults import KEEP_TRAILING_NEWLINE
|
||||
from .defaults import LINE_COMMENT_PREFIX
|
||||
from .defaults import LINE_STATEMENT_PREFIX
|
||||
@ -55,6 +56,7 @@ from .utils import missing
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .bccache import BytecodeCache
|
||||
from .ext import Extension
|
||||
from .loaders import BaseLoader
|
||||
@ -79,7 +81,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, str], "Template"]]:
|
||||
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
|
||||
"""Return the cache class for the given size."""
|
||||
if size == 0:
|
||||
return None
|
||||
@ -91,13 +93,13 @@ def create_cache(
|
||||
|
||||
|
||||
def copy_cache(
|
||||
cache: t.Optional[t.MutableMapping],
|
||||
) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]:
|
||||
cache: t.Optional[t.MutableMapping[t.Any, t.Any]],
|
||||
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
|
||||
"""Create an empty copy of the given cache."""
|
||||
if cache is None:
|
||||
return None
|
||||
|
||||
if type(cache) is dict:
|
||||
if type(cache) is dict: # noqa E721
|
||||
return {}
|
||||
|
||||
return LRUCache(cache.capacity) # type: ignore
|
||||
@ -670,7 +672,7 @@ class Environment:
|
||||
stream = ext.filter_stream(stream) # type: ignore
|
||||
|
||||
if not isinstance(stream, TokenStream):
|
||||
stream = TokenStream(stream, name, filename) # type: ignore
|
||||
stream = TokenStream(stream, name, filename)
|
||||
|
||||
return stream
|
||||
|
||||
@ -711,8 +713,7 @@ class Environment:
|
||||
filename: t.Optional[str] = None,
|
||||
raw: "te.Literal[False]" = False,
|
||||
defer_init: bool = False,
|
||||
) -> CodeType:
|
||||
...
|
||||
) -> CodeType: ...
|
||||
|
||||
@typing.overload
|
||||
def compile(
|
||||
@ -722,8 +723,7 @@ class Environment:
|
||||
filename: t.Optional[str] = None,
|
||||
raw: "te.Literal[True]" = ...,
|
||||
defer_init: bool = False,
|
||||
) -> str:
|
||||
...
|
||||
) -> str: ...
|
||||
|
||||
@internalcode
|
||||
def compile(
|
||||
@ -814,7 +814,7 @@ class Environment:
|
||||
|
||||
def compile_templates(
|
||||
self,
|
||||
target: t.Union[str, os.PathLike],
|
||||
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",
|
||||
@ -858,7 +858,10 @@ class Environment:
|
||||
f.write(data.encode("utf8"))
|
||||
|
||||
if zip is not None:
|
||||
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
|
||||
from zipfile import ZIP_DEFLATED
|
||||
from zipfile import ZIP_STORED
|
||||
from zipfile import ZipFile
|
||||
from zipfile import ZipInfo
|
||||
|
||||
zip_file = ZipFile(
|
||||
target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
|
||||
@ -1417,7 +1420,9 @@ class Template:
|
||||
"""
|
||||
ctx = self.new_context(vars, shared, locals)
|
||||
return TemplateModule(
|
||||
self, ctx, [x async for x in self.root_render_func(ctx)] # type: ignore
|
||||
self,
|
||||
ctx,
|
||||
[x async for x in self.root_render_func(ctx)], # type: ignore
|
||||
)
|
||||
|
||||
@internalcode
|
||||
@ -1588,7 +1593,7 @@ class TemplateStream:
|
||||
|
||||
def dump(
|
||||
self,
|
||||
fp: t.Union[str, t.IO],
|
||||
fp: t.Union[str, t.IO[bytes]],
|
||||
encoding: t.Optional[str] = None,
|
||||
errors: t.Optional[str] = "strict",
|
||||
) -> None:
|
||||
@ -1606,22 +1611,25 @@ class TemplateStream:
|
||||
if encoding is None:
|
||||
encoding = "utf-8"
|
||||
|
||||
fp = open(fp, "wb")
|
||||
real_fp: t.IO[bytes] = open(fp, "wb")
|
||||
close = True
|
||||
else:
|
||||
real_fp = fp
|
||||
|
||||
try:
|
||||
if encoding is not None:
|
||||
iterable = (x.encode(encoding, errors) for x in self) # type: ignore
|
||||
else:
|
||||
iterable = self # type: ignore
|
||||
|
||||
if hasattr(fp, "writelines"):
|
||||
fp.writelines(iterable)
|
||||
if hasattr(real_fp, "writelines"):
|
||||
real_fp.writelines(iterable)
|
||||
else:
|
||||
for item in iterable:
|
||||
fp.write(item)
|
||||
real_fp.write(item)
|
||||
finally:
|
||||
if close:
|
||||
fp.close()
|
||||
real_fp.close()
|
||||
|
||||
def disable_buffering(self) -> None:
|
||||
"""Disable the output buffering."""
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"""Extension API for adding custom tags and behavior."""
|
||||
|
||||
import pprint
|
||||
import re
|
||||
import typing as t
|
||||
@ -18,23 +19,23 @@ from .utils import pass_context
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .lexer import Token
|
||||
from .lexer import TokenStream
|
||||
from .parser import Parser
|
||||
|
||||
class _TranslationsBasic(te.Protocol):
|
||||
def gettext(self, message: str) -> str:
|
||||
...
|
||||
def gettext(self, message: str) -> str: ...
|
||||
|
||||
def ngettext(self, singular: str, plural: str, n: int) -> str:
|
||||
pass
|
||||
|
||||
class _TranslationsContext(_TranslationsBasic):
|
||||
def pgettext(self, context: str, message: str) -> str:
|
||||
...
|
||||
def pgettext(self, context: str, message: str) -> str: ...
|
||||
|
||||
def npgettext(self, context: str, singular: str, plural: str, n: int) -> str:
|
||||
...
|
||||
def npgettext(
|
||||
self, context: str, singular: str, plural: str, n: int
|
||||
) -> str: ...
|
||||
|
||||
_SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
|
||||
|
||||
@ -218,7 +219,7 @@ def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str
|
||||
|
||||
|
||||
def _make_new_npgettext(
|
||||
func: t.Callable[[str, str, str, int], str]
|
||||
func: t.Callable[[str, str, str, int], str],
|
||||
) -> t.Callable[..., str]:
|
||||
@pass_context
|
||||
def npgettext(
|
||||
@ -294,14 +295,14 @@ class InternationalizationExtension(Extension):
|
||||
pgettext = translations.pgettext
|
||||
else:
|
||||
|
||||
def pgettext(c: str, s: str) -> str:
|
||||
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:
|
||||
def npgettext(c: str, s: str, p: str, n: int) -> str: # type: ignore[misc]
|
||||
return s if n == 1 else p
|
||||
|
||||
self._install_callables(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"""Built-in template filters used with the ``|`` operator."""
|
||||
|
||||
import math
|
||||
import random
|
||||
import re
|
||||
@ -28,6 +29,7 @@ from .utils import urlize
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
from .nodes import EvalContext
|
||||
from .runtime import Context
|
||||
@ -122,7 +124,7 @@ def make_multi_attrgetter(
|
||||
|
||||
|
||||
def _prepare_attribute_parts(
|
||||
attr: t.Optional[t.Union[str, int]]
|
||||
attr: t.Optional[t.Union[str, int]],
|
||||
) -> t.List[t.Union[str, int]]:
|
||||
if attr is None:
|
||||
return []
|
||||
@ -142,7 +144,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: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]],
|
||||
) -> str:
|
||||
"""Quote data for use in a URL path or query using UTF-8.
|
||||
|
||||
@ -552,7 +554,7 @@ def do_default(
|
||||
@pass_eval_context
|
||||
def sync_do_join(
|
||||
eval_ctx: "EvalContext",
|
||||
value: t.Iterable,
|
||||
value: t.Iterable[t.Any],
|
||||
d: str = "",
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> str:
|
||||
@ -610,7 +612,7 @@ def sync_do_join(
|
||||
@async_variant(sync_do_join) # type: ignore
|
||||
async def do_join(
|
||||
eval_ctx: "EvalContext",
|
||||
value: t.Union[t.AsyncIterable, t.Iterable],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
d: str = "",
|
||||
attribute: t.Optional[t.Union[str, int]] = None,
|
||||
) -> str:
|
||||
@ -1160,7 +1162,7 @@ def do_round(
|
||||
|
||||
class _GroupTuple(t.NamedTuple):
|
||||
grouper: t.Any
|
||||
list: t.List
|
||||
list: t.List[t.Any]
|
||||
|
||||
# Use the regular tuple repr to hide this subclass if users print
|
||||
# out the value during debugging.
|
||||
@ -1356,13 +1358,11 @@ def do_mark_unsafe(value: str) -> str:
|
||||
|
||||
|
||||
@typing.overload
|
||||
def do_reverse(value: str) -> str:
|
||||
...
|
||||
def do_reverse(value: str) -> str: ...
|
||||
|
||||
|
||||
@typing.overload
|
||||
def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]":
|
||||
...
|
||||
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]]:
|
||||
@ -1416,26 +1416,28 @@ def do_attr(
|
||||
|
||||
@typing.overload
|
||||
def sync_do_map(
|
||||
context: "Context", value: t.Iterable, name: str, *args: t.Any, **kwargs: t.Any
|
||||
) -> t.Iterable:
|
||||
...
|
||||
context: "Context",
|
||||
value: t.Iterable[t.Any],
|
||||
name: str,
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Iterable[t.Any]: ...
|
||||
|
||||
|
||||
@typing.overload
|
||||
def sync_do_map(
|
||||
context: "Context",
|
||||
value: t.Iterable,
|
||||
value: t.Iterable[t.Any],
|
||||
*,
|
||||
attribute: str = ...,
|
||||
default: t.Optional[t.Any] = None,
|
||||
) -> t.Iterable:
|
||||
...
|
||||
) -> t.Iterable[t.Any]: ...
|
||||
|
||||
|
||||
@pass_context
|
||||
def sync_do_map(
|
||||
context: "Context", value: t.Iterable, *args: t.Any, **kwargs: t.Any
|
||||
) -> t.Iterable:
|
||||
context: "Context", value: t.Iterable[t.Any], *args: t.Any, **kwargs: t.Any
|
||||
) -> t.Iterable[t.Any]:
|
||||
"""Applies a filter on a sequence of objects or looks up an attribute.
|
||||
This is useful when dealing with lists of objects but you are really
|
||||
only interested in a certain value of it.
|
||||
@ -1485,32 +1487,30 @@ def sync_do_map(
|
||||
@typing.overload
|
||||
def do_map(
|
||||
context: "Context",
|
||||
value: t.Union[t.AsyncIterable, t.Iterable],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
name: str,
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Iterable:
|
||||
...
|
||||
) -> t.Iterable[t.Any]: ...
|
||||
|
||||
|
||||
@typing.overload
|
||||
def do_map(
|
||||
context: "Context",
|
||||
value: t.Union[t.AsyncIterable, t.Iterable],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
*,
|
||||
attribute: str = ...,
|
||||
default: t.Optional[t.Any] = None,
|
||||
) -> t.Iterable:
|
||||
...
|
||||
) -> t.Iterable[t.Any]: ...
|
||||
|
||||
|
||||
@async_variant(sync_do_map) # type: ignore
|
||||
async def do_map(
|
||||
context: "Context",
|
||||
value: t.Union[t.AsyncIterable, t.Iterable],
|
||||
value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> t.AsyncIterable:
|
||||
) -> t.AsyncIterable[t.Any]:
|
||||
if value:
|
||||
func = prepare_map(context, args, kwargs)
|
||||
|
||||
@ -1703,7 +1703,7 @@ def do_tojson(
|
||||
|
||||
|
||||
def prepare_map(
|
||||
context: "Context", args: t.Tuple, kwargs: t.Dict[str, t.Any]
|
||||
context: "Context", args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any]
|
||||
) -> t.Callable[[t.Any], t.Any]:
|
||||
if not args and "attribute" in kwargs:
|
||||
attribute = kwargs.pop("attribute")
|
||||
@ -1732,7 +1732,7 @@ def prepare_map(
|
||||
|
||||
def prepare_select_or_reject(
|
||||
context: "Context",
|
||||
args: t.Tuple,
|
||||
args: t.Tuple[t.Any, ...],
|
||||
kwargs: t.Dict[str, t.Any],
|
||||
modfunc: t.Callable[[t.Any], t.Any],
|
||||
lookup_attr: bool,
|
||||
@ -1767,7 +1767,7 @@ def prepare_select_or_reject(
|
||||
def select_or_reject(
|
||||
context: "Context",
|
||||
value: "t.Iterable[V]",
|
||||
args: t.Tuple,
|
||||
args: t.Tuple[t.Any, ...],
|
||||
kwargs: t.Dict[str, t.Any],
|
||||
modfunc: t.Callable[[t.Any], t.Any],
|
||||
lookup_attr: bool,
|
||||
@ -1783,7 +1783,7 @@ def select_or_reject(
|
||||
async def async_select_or_reject(
|
||||
context: "Context",
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
args: t.Tuple,
|
||||
args: t.Tuple[t.Any, ...],
|
||||
kwargs: t.Dict[str, t.Any],
|
||||
modfunc: t.Callable[[t.Any], t.Any],
|
||||
lookup_attr: bool,
|
||||
|
||||
@ -3,6 +3,7 @@ is used to do some preprocessing. It filters out invalid operators like
|
||||
the bitshift operators we don't allow in templates. It separates
|
||||
template code and python code in expressions.
|
||||
"""
|
||||
|
||||
import re
|
||||
import typing as t
|
||||
from ast import literal_eval
|
||||
@ -15,6 +16,7 @@ from .utils import LRUCache
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
# cache for the lexers. Exists in order to be able to have multiple
|
||||
@ -447,7 +449,7 @@ def get_lexer(environment: "Environment") -> "Lexer":
|
||||
return lexer
|
||||
|
||||
|
||||
class OptionalLStrip(tuple):
|
||||
class OptionalLStrip(tuple): # type: ignore[type-arg]
|
||||
"""A special tuple for marking a point in the state that can have
|
||||
lstrip applied.
|
||||
"""
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""API and implementations for loading templates from different data
|
||||
sources.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import posixpath
|
||||
@ -177,7 +178,9 @@ class FileSystemLoader(BaseLoader):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]],
|
||||
searchpath: t.Union[
|
||||
str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
|
||||
],
|
||||
encoding: str = "utf-8",
|
||||
followlinks: bool = False,
|
||||
) -> None:
|
||||
@ -601,7 +604,10 @@ class ModuleLoader(BaseLoader):
|
||||
has_source_access = False
|
||||
|
||||
def __init__(
|
||||
self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]]
|
||||
self,
|
||||
path: t.Union[
|
||||
str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
|
||||
],
|
||||
) -> None:
|
||||
package_name = f"_jinja2_module_templates_{id(self):x}"
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""Functions that expose information about templates that might be
|
||||
interesting for introspection.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from . import nodes
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
some node tree helper functions used by the parser and compiler in order
|
||||
to normalize nodes.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import operator
|
||||
import typing as t
|
||||
@ -13,6 +14,7 @@ from .utils import _PassArg
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
_NodeBound = t.TypeVar("_NodeBound", bound="Node")
|
||||
@ -56,7 +58,7 @@ class NodeType(type):
|
||||
|
||||
def __new__(mcs, name, bases, d): # type: ignore
|
||||
for attr in "fields", "attributes":
|
||||
storage = []
|
||||
storage: t.List[t.Tuple[str, ...]] = []
|
||||
storage.extend(getattr(bases[0] if bases else object, attr, ()))
|
||||
storage.extend(d.get(attr, ()))
|
||||
assert len(bases) <= 1, "multiple inheritance not allowed"
|
||||
|
||||
@ -7,6 +7,7 @@ want. For example, loop unrolling doesn't work because unrolled loops
|
||||
would have a different scope. The solution would be a second syntax tree
|
||||
that stored the scoping rules.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from . import nodes
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"""Parse tokens from the lexer into nodes for the compiler."""
|
||||
|
||||
import typing
|
||||
import typing as t
|
||||
|
||||
@ -10,6 +11,7 @@ from .lexer import describe_token_expr
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
_ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include)
|
||||
@ -457,8 +459,7 @@ class Parser:
|
||||
@typing.overload
|
||||
def parse_assign_target(
|
||||
self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ...
|
||||
) -> nodes.Name:
|
||||
...
|
||||
) -> nodes.Name: ...
|
||||
|
||||
@typing.overload
|
||||
def parse_assign_target(
|
||||
@ -467,8 +468,7 @@ class Parser:
|
||||
name_only: bool = False,
|
||||
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
||||
with_namespace: bool = False,
|
||||
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
|
||||
...
|
||||
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: ...
|
||||
|
||||
def parse_assign_target(
|
||||
self,
|
||||
@ -861,7 +861,14 @@ class Parser:
|
||||
|
||||
return nodes.Slice(lineno=lineno, *args) # noqa: B026
|
||||
|
||||
def parse_call_args(self) -> t.Tuple:
|
||||
def parse_call_args(
|
||||
self,
|
||||
) -> t.Tuple[
|
||||
t.List[nodes.Expr],
|
||||
t.List[nodes.Keyword],
|
||||
t.Optional[nodes.Expr],
|
||||
t.Optional[nodes.Expr],
|
||||
]:
|
||||
token = self.stream.expect("lparen")
|
||||
args = []
|
||||
kwargs = []
|
||||
@ -952,7 +959,7 @@ class Parser:
|
||||
next(self.stream)
|
||||
name += "." + self.stream.expect("name").value
|
||||
dyn_args = dyn_kwargs = None
|
||||
kwargs = []
|
||||
kwargs: t.List[nodes.Keyword] = []
|
||||
if self.stream.current.type == "lparen":
|
||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
||||
elif self.stream.current.type in {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"""The runtime functions and state used by compiled templates."""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import typing as t
|
||||
@ -28,7 +29,9 @@ F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
class LoopRenderFunc(te.Protocol):
|
||||
@ -37,8 +40,7 @@ if t.TYPE_CHECKING:
|
||||
reciter: t.Iterable[V],
|
||||
loop_render_func: "LoopRenderFunc",
|
||||
depth: int = 0,
|
||||
) -> str:
|
||||
...
|
||||
) -> str: ...
|
||||
|
||||
|
||||
# these variables are exported to the template runtime
|
||||
@ -259,7 +261,10 @@ class Context:
|
||||
|
||||
@internalcode
|
||||
def call(
|
||||
__self, __obj: t.Callable, *args: t.Any, **kwargs: t.Any # noqa: B902
|
||||
__self,
|
||||
__obj: t.Callable[..., t.Any],
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any, # noqa: B902
|
||||
) -> t.Union[t.Any, "Undefined"]:
|
||||
"""Call the callable with the arguments and keyword arguments
|
||||
provided but inject the active context or environment as first
|
||||
@ -586,7 +591,7 @@ class AsyncLoopContext(LoopContext):
|
||||
|
||||
@staticmethod
|
||||
def _to_iterator( # type: ignore
|
||||
iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]]
|
||||
iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]],
|
||||
) -> t.AsyncIterator[V]:
|
||||
return auto_aiter(iterable)
|
||||
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
"""A sandbox layer that ensures unsafe operations cannot be performed.
|
||||
Useful when the template itself comes from an untrusted source.
|
||||
"""
|
||||
|
||||
import operator
|
||||
import types
|
||||
import typing as t
|
||||
from _string import formatter_field_name_split # type: ignore
|
||||
from collections import abc
|
||||
from collections import deque
|
||||
from string import Formatter
|
||||
|
||||
from _string import formatter_field_name_split # type: ignore
|
||||
from markupsafe import EscapeFormatter
|
||||
from markupsafe import Markup
|
||||
|
||||
@ -37,7 +38,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.FrozenSet[str]], ...] = (
|
||||
_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = (
|
||||
(
|
||||
abc.MutableSet,
|
||||
frozenset(
|
||||
@ -80,7 +81,7 @@ _mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = (
|
||||
)
|
||||
|
||||
|
||||
def inspect_format_method(callable: t.Callable) -> t.Optional[str]:
|
||||
def inspect_format_method(callable: t.Callable[..., t.Any]) -> t.Optional[str]:
|
||||
if not isinstance(
|
||||
callable, (types.MethodType, types.BuiltinMethodType)
|
||||
) or callable.__name__ not in ("format", "format_map"):
|
||||
@ -350,7 +351,7 @@ class SandboxedEnvironment(Environment):
|
||||
s: str,
|
||||
args: t.Tuple[t.Any, ...],
|
||||
kwargs: t.Dict[str, t.Any],
|
||||
format_func: t.Optional[t.Callable] = None,
|
||||
format_func: t.Optional[t.Callable[..., t.Any]] = None,
|
||||
) -> str:
|
||||
"""If a format call is detected, then this is routed through this
|
||||
method so that our safety sandbox can be used for it.
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"""Built-in template tests used with the ``is`` operator."""
|
||||
|
||||
import operator
|
||||
import typing as t
|
||||
from collections import abc
|
||||
@ -169,7 +170,7 @@ def test_sequence(value: t.Any) -> bool:
|
||||
"""
|
||||
try:
|
||||
len(value)
|
||||
value.__getitem__
|
||||
value.__getitem__ # noqa B018
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@ -204,7 +205,7 @@ def test_escaped(value: t.Any) -> bool:
|
||||
return hasattr(value, "__html__")
|
||||
|
||||
|
||||
def test_in(value: t.Any, seq: t.Container) -> bool:
|
||||
def test_in(value: t.Any, seq: t.Container[t.Any]) -> bool:
|
||||
"""Check if value is in seq.
|
||||
|
||||
.. versionadded:: 2.10
|
||||
|
||||
@ -152,7 +152,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]:
|
||||
def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]:
|
||||
"""Returns a file descriptor for the filename if that file exists,
|
||||
otherwise ``None``.
|
||||
"""
|
||||
@ -450,7 +450,7 @@ class LRUCache:
|
||||
self.__dict__.update(d)
|
||||
self._postinit()
|
||||
|
||||
def __getnewargs__(self) -> t.Tuple:
|
||||
def __getnewargs__(self) -> t.Tuple[t.Any, ...]:
|
||||
return (self.capacity,)
|
||||
|
||||
def copy(self) -> "LRUCache":
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""API for traversing the AST nodes. Implemented by the compiler and
|
||||
meta introspection.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from .nodes import Node
|
||||
@ -9,8 +10,7 @@ if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
class VisitCallable(te.Protocol):
|
||||
def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
...
|
||||
def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: ...
|
||||
|
||||
|
||||
class NodeVisitor:
|
||||
|
||||
@ -150,7 +150,8 @@ class TestExtendedAPI:
|
||||
assert t.render(foo="<foo>") == "<foo>"
|
||||
|
||||
def test_sandbox_max_range(self, env):
|
||||
from jinja2.sandbox import SandboxedEnvironment, MAX_RANGE
|
||||
from jinja2.sandbox import MAX_RANGE
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
env = SandboxedEnvironment()
|
||||
t = env.from_string("{% for item in range(total) %}{{ item }}{% endfor %}")
|
||||
@ -264,7 +265,7 @@ class TestUndefined:
|
||||
|
||||
def test_undefined_and_special_attributes(self):
|
||||
with pytest.raises(AttributeError):
|
||||
Undefined("Foo").__dict__
|
||||
Undefined("Foo").__dict__ # noqa B018
|
||||
|
||||
def test_undefined_attribute_error(self):
|
||||
# Django's LazyObject turns the __class__ attribute into a
|
||||
|
||||
@ -365,11 +365,10 @@ class TestInheritance:
|
||||
|
||||
class TestBugFix:
|
||||
def test_fixed_macro_scoping_bug(self, env):
|
||||
assert (
|
||||
Environment(
|
||||
loader=DictLoader(
|
||||
{
|
||||
"test.html": """\
|
||||
assert Environment(
|
||||
loader=DictLoader(
|
||||
{
|
||||
"test.html": """\
|
||||
{% extends 'details.html' %}
|
||||
|
||||
{% macro my_macro() %}
|
||||
@ -380,7 +379,7 @@ class TestBugFix:
|
||||
{{ my_macro() }}
|
||||
{% endblock %}
|
||||
""",
|
||||
"details.html": """\
|
||||
"details.html": """\
|
||||
{% extends 'standard.html' %}
|
||||
|
||||
{% macro my_macro() %}
|
||||
@ -396,17 +395,12 @@ class TestBugFix:
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
""",
|
||||
"standard.html": """
|
||||
"standard.html": """
|
||||
{% block content %} {% endblock %}
|
||||
""",
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
.get_template("test.html")
|
||||
.render()
|
||||
.split()
|
||||
== ["outer_box", "my_macro"]
|
||||
)
|
||||
).get_template("test.html").render().split() == ["outer_box", "my_macro"]
|
||||
|
||||
def test_double_extends(self, env):
|
||||
"""Ensures that a template with more than 1 {% extends ... %} usage
|
||||
|
||||
@ -599,6 +599,7 @@ class TestBug:
|
||||
|
||||
def test_markup_and_chainable_undefined(self):
|
||||
from markupsafe import Markup
|
||||
|
||||
from jinja2.runtime import ChainableUndefined
|
||||
|
||||
assert str(Markup(ChainableUndefined())) == ""
|
||||
|
||||
18
tox.ini
18
tox.ini
@ -10,6 +10,8 @@ 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}
|
||||
|
||||
@ -24,4 +26,18 @@ commands = mypy
|
||||
|
||||
[testenv:docs]
|
||||
deps = -r requirements/docs.txt
|
||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
|
||||
commands = sphinx-build -E -W -b dirhtml docs docs/_build/dirhtml
|
||||
|
||||
[testenv:update-requirements]
|
||||
deps =
|
||||
pip-tools
|
||||
pre-commit
|
||||
skip_install = true
|
||||
change_dir = requirements
|
||||
commands =
|
||||
pre-commit autoupdate -j4
|
||||
pip-compile -U build.in
|
||||
pip-compile -U docs.in
|
||||
pip-compile -U tests.in
|
||||
pip-compile -U typing.in
|
||||
pip-compile -U dev.in
|
||||
|
||||
Loading…
Reference in New Issue
Block a user