update project files (#1968)

This commit is contained in:
David Lord 2024-04-23 10:20:59 -07:00 committed by GitHub
commit da3a9f0b80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 432 additions and 493 deletions

View 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"
}

View 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

View File

@ -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
View File

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

View File

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

View File

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

View File

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

View File

@ -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:
- '*'

View File

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

View File

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

View File

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

View File

@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"\\?[()]")

View File

@ -1,4 +1,5 @@
BSD-3-Clause License
====================
.. include:: ../LICENSE.rst
.. literalinclude:: ../LICENSE.txt
:language: text

98
pyproject.toml Normal file
View 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

View File

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

View File

@ -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"]},
)

View File

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

View File

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

View File

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

View File

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

View File

@ -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."""

View File

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

View File

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

View File

@ -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.
"""

View File

@ -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}"

View File

@ -1,6 +1,7 @@
"""Functions that expose information about templates that might be
interesting for introspection.
"""
import typing as t
from . import nodes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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":

View File

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

View File

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

View File

@ -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 %}&nbsp;{% 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

View File

@ -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
View File

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