mirror of https://github.com/pallets/flask.git
Compare commits
6 Commits
b2da253dc7
...
79f0cdf0c7
| Author | SHA1 | Date |
|---|---|---|
|
|
79f0cdf0c7 | |
|
|
bc143499cf | |
|
|
941efd4a36 | |
|
|
0109e496f6 | |
|
|
11c45eeba3 | |
|
|
028160e49f |
|
|
@ -10,6 +10,7 @@ on:
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
discussions: write
|
||||||
concurrency:
|
concurrency:
|
||||||
group: lock
|
group: lock
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,19 @@ jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
enable-cache: true
|
||||||
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
prune-cache: false
|
||||||
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
if: ${{ !cancelled() }}
|
id: setup-python
|
||||||
|
with:
|
||||||
|
python-version-file: pyproject.toml
|
||||||
|
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pre-commit
|
||||||
|
key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }}
|
||||||
|
- run: uv run --locked --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files
|
||||||
|
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
name: Publish
|
name: Publish
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags: ['*']
|
||||||
- '*'
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -10,16 +9,15 @@ jobs:
|
||||||
hash: ${{ steps.hash.outputs.hash }}
|
hash: ${{ steps.hash.outputs.hash }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
enable-cache: true
|
||||||
cache: pip
|
prune-cache: false
|
||||||
cache-dependency-path: requirements*/*.txt
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
- run: pip install -r requirements/build.txt
|
with:
|
||||||
# Use the commit date instead of the current date during the build.
|
python-version-file: pyproject.toml
|
||||||
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
|
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
|
||||||
- run: python -m build
|
- run: uv build
|
||||||
# Generate hashes used for provenance.
|
|
||||||
- name: generate hash
|
- name: generate hash
|
||||||
id: hash
|
id: hash
|
||||||
run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT
|
run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT
|
||||||
|
|
@ -37,14 +35,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
base64-subjects: ${{ needs.build.outputs.hash }}
|
base64-subjects: ${{ needs.build.outputs.hash }}
|
||||||
create-release:
|
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
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
- name: create release
|
- name: create release
|
||||||
run: >
|
run: >
|
||||||
gh release create --draft --repo ${{ github.repository }}
|
gh release create --draft --repo ${{ github.repository }}
|
||||||
|
|
@ -54,8 +50,6 @@ jobs:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
publish-pypi:
|
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:
|
environment:
|
||||||
name: publish
|
name: publish
|
||||||
url: https://pypi.org/project/Flask/${{ github.ref_name }}
|
url: https://pypi.org/project/Flask/${{ github.ref_name }}
|
||||||
|
|
@ -63,7 +57,7 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
- uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
|
- uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
|
||||||
with:
|
with:
|
||||||
packages-dir: artifact/
|
packages-dir: artifact/
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
name: Tests
|
name: Tests
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths-ignore: ['docs/**', 'README.md']
|
||||||
push:
|
push:
|
||||||
branches: [main, stable]
|
branches: [main, stable]
|
||||||
paths-ignore: ['docs/**', '*.md', '*.rst']
|
paths-ignore: ['docs/**', 'README.md']
|
||||||
pull_request:
|
|
||||||
paths-ignore: [ 'docs/**', '*.md', '*.rst' ]
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: ${{ matrix.name || matrix.python }}
|
name: ${{ matrix.name || matrix.python }}
|
||||||
|
|
@ -14,38 +14,39 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- {python: '3.13'}
|
- {python: '3.13'}
|
||||||
|
- {name: Windows, python: '3.13', os: windows-latest}
|
||||||
|
- {name: Mac, python: '3.13', os: macos-latest}
|
||||||
- {python: '3.12'}
|
- {python: '3.12'}
|
||||||
- {name: Windows, python: '3.12', os: windows-latest}
|
|
||||||
- {name: Mac, python: '3.12', os: macos-latest}
|
|
||||||
- {python: '3.11'}
|
- {python: '3.11'}
|
||||||
- {python: '3.10'}
|
- {python: '3.10'}
|
||||||
- {python: '3.9'}
|
- {python: '3.9'}
|
||||||
- {name: PyPy, python: 'pypy-3.10', tox: pypy310}
|
- {name: PyPy, python: 'pypy-3.11', tox: pypy3.11}
|
||||||
- {name: Minimum Versions, python: '3.12', tox: py-min}
|
- {name: Minimum Versions, python: '3.13', tox: tests-min}
|
||||||
- {name: Development Versions, python: '3.9', tox: py-dev}
|
- {name: Development Versions, python: '3.10', tox: tests-dev}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
prune-cache: false
|
||||||
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python }}
|
python-version: ${{ matrix.python }}
|
||||||
allow-prereleases: true
|
- run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
|
||||||
cache: pip
|
|
||||||
cache-dependency-path: requirements*/*.txt
|
|
||||||
- run: pip install tox
|
|
||||||
- run: tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
|
|
||||||
typing:
|
typing:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
enable-cache: true
|
||||||
cache: pip
|
prune-cache: false
|
||||||
cache-dependency-path: requirements*/*.txt
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
|
with:
|
||||||
|
python-version-file: pyproject.toml
|
||||||
- name: cache mypy
|
- name: cache mypy
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ./.mypy_cache
|
path: ./.mypy_cache
|
||||||
key: mypy|${{ hashFiles('pyproject.toml') }}
|
key: mypy|${{ hashFiles('pyproject.toml') }}
|
||||||
- run: pip install tox
|
- run: uv run --locked tox run -e typing
|
||||||
- run: tox run -e typing
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
.venv*/
|
|
||||||
venv*/
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
dist/
|
dist/
|
||||||
.coverage*
|
.coverage*
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.2
|
rev: 24e02b24b8ab2b7c76225602d13fa60e12d114e6 # frozen: v0.11.9
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
|
rev: 14ac15b122e538e407d036ff45e3895b7cf4a2bf # frozen: 0.7.3
|
||||||
|
hooks:
|
||||||
|
- id: uv-lock
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
version: 2
|
version: 2
|
||||||
build:
|
build:
|
||||||
os: ubuntu-22.04
|
os: ubuntu-24.04
|
||||||
tools:
|
tools:
|
||||||
python: '3.12'
|
python: '3.13'
|
||||||
python:
|
commands:
|
||||||
install:
|
- asdf plugin add uv
|
||||||
- requirements: requirements/docs.txt
|
- asdf install uv latest
|
||||||
- method: pip
|
- asdf global uv latest
|
||||||
path: .
|
- uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html
|
||||||
sphinx:
|
|
||||||
configuration: docs/conf.py
|
|
||||||
builder: dirhtml
|
|
||||||
fail_on_warning: true
|
|
||||||
|
|
|
||||||
190
pyproject.toml
190
pyproject.toml
|
|
@ -3,14 +3,14 @@ name = "Flask"
|
||||||
version = "3.2.0.dev"
|
version = "3.2.0.dev"
|
||||||
description = "A simple framework for building complex web applications."
|
description = "A simple framework for building complex web applications."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {file = "LICENSE.txt"}
|
license = "BSD-3-Clause"
|
||||||
|
license-files = ["LICENSE.txt"]
|
||||||
maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}]
|
maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Environment :: Web Environment",
|
"Environment :: Web Environment",
|
||||||
"Framework :: Flask",
|
"Framework :: Flask",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: BSD License",
|
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
|
|
@ -21,25 +21,65 @@ classifiers = [
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Werkzeug>=3.1",
|
"blinker>=1.9.0",
|
||||||
"Jinja2>=3.1.2",
|
|
||||||
"itsdangerous>=2.2",
|
|
||||||
"click>=8.1.3",
|
"click>=8.1.3",
|
||||||
"blinker>=1.9",
|
"importlib-metadata>=3.6.0; python_version < '3.10'",
|
||||||
"importlib-metadata>=3.6; python_version < '3.10'",
|
"itsdangerous>=2.2.0",
|
||||||
|
"jinja2>=3.1.2",
|
||||||
|
"markupsafe>=2.1.1",
|
||||||
|
"werkzeug>=3.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
async = ["asgiref>=3.2"]
|
||||||
|
dotenv = ["python-dotenv"]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"ruff",
|
||||||
|
"tox",
|
||||||
|
"tox-uv",
|
||||||
|
]
|
||||||
|
docs = [
|
||||||
|
"pallets-sphinx-themes",
|
||||||
|
"sphinx",
|
||||||
|
"sphinx-tabs",
|
||||||
|
"sphinxcontrib-log-cabinet",
|
||||||
|
]
|
||||||
|
docs-auto = [
|
||||||
|
"sphinx-autobuild",
|
||||||
|
]
|
||||||
|
gha-update = [
|
||||||
|
"gha-update ; python_full_version >= '3.12'",
|
||||||
|
]
|
||||||
|
pre-commit = [
|
||||||
|
"pre-commit",
|
||||||
|
"pre-commit-uv",
|
||||||
|
]
|
||||||
|
tests = [
|
||||||
|
"asgiref",
|
||||||
|
"greenlet ; python_version < '3.11'",
|
||||||
|
"pytest",
|
||||||
|
"python-dotenv",
|
||||||
|
]
|
||||||
|
typing = [
|
||||||
|
"asgiref",
|
||||||
|
"cryptography",
|
||||||
|
"mypy",
|
||||||
|
"pyright",
|
||||||
|
"pytest",
|
||||||
|
"python-dotenv",
|
||||||
|
"types-contextvars",
|
||||||
|
"types-dataclasses",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Donate = "https://palletsprojects.com/donate"
|
Donate = "https://palletsprojects.com/donate"
|
||||||
Documentation = "https://flask.palletsprojects.com/"
|
Documentation = "https://flask.palletsprojects.com/"
|
||||||
Changes = "https://flask.palletsprojects.com/changes/"
|
Changes = "https://flask.palletsprojects.com/page/changes/"
|
||||||
Source = "https://github.com/pallets/flask/"
|
Source = "https://github.com/pallets/flask/"
|
||||||
Chat = "https://discord.gg/pallets"
|
Chat = "https://discord.gg/pallets"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
|
||||||
async = ["asgiref>=3.2"]
|
|
||||||
dotenv = ["python-dotenv"]
|
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
flask = "flask.cli:main"
|
flask = "flask.cli:main"
|
||||||
|
|
||||||
|
|
@ -54,16 +94,17 @@ name = "flask"
|
||||||
include = [
|
include = [
|
||||||
"docs/",
|
"docs/",
|
||||||
"examples/",
|
"examples/",
|
||||||
"requirements/",
|
|
||||||
"tests/",
|
"tests/",
|
||||||
"CHANGES.rst",
|
"CHANGES.rst",
|
||||||
"CONTRIBUTING.rst",
|
"uv.lock"
|
||||||
"tox.ini",
|
|
||||||
]
|
]
|
||||||
exclude = [
|
exclude = [
|
||||||
"docs/_build/",
|
"docs/_build/",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
default-groups = ["dev", "pre-commit", "tests", "typing"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
filterwarnings = [
|
filterwarnings = [
|
||||||
|
|
@ -77,9 +118,16 @@ source = ["flask", "tests"]
|
||||||
[tool.coverage.paths]
|
[tool.coverage.paths]
|
||||||
source = ["src", "*/site-packages"]
|
source = ["src", "*/site-packages"]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_also = [
|
||||||
|
"if t.TYPE_CHECKING",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
": \\.{3}",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.9"
|
python_version = "3.9"
|
||||||
files = ["src/flask", "tests/type_check"]
|
files = ["src", "tests/type_check"]
|
||||||
show_error_codes = true
|
show_error_codes = true
|
||||||
pretty = true
|
pretty = true
|
||||||
strict = true
|
strict = true
|
||||||
|
|
@ -95,7 +143,7 @@ ignore_missing_imports = true
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
pythonVersion = "3.9"
|
pythonVersion = "3.9"
|
||||||
include = ["src/flask", "tests/type_check"]
|
include = ["src", "tests/type_check"]
|
||||||
typeCheckingMode = "basic"
|
typeCheckingMode = "basic"
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
|
@ -122,3 +170,111 @@ order-by-type = false
|
||||||
tag-only = [
|
tag-only = [
|
||||||
"slsa-framework/slsa-github-generator",
|
"slsa-framework/slsa-github-generator",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.tox]
|
||||||
|
env_list = [
|
||||||
|
"py3.13", "py3.12", "py3.11", "py3.10", "py3.9",
|
||||||
|
"pypy3.11",
|
||||||
|
"tests-min", "tests-dev",
|
||||||
|
"style",
|
||||||
|
"typing",
|
||||||
|
"docs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.tox.env_run_base]
|
||||||
|
description = "pytest on latest dependency versions"
|
||||||
|
runner = "uv-venv-lock-runner"
|
||||||
|
package = "wheel"
|
||||||
|
wheel_build_env = ".pkg"
|
||||||
|
constrain_package_deps = true
|
||||||
|
use_frozen_constraints = true
|
||||||
|
dependency_groups = ["tests"]
|
||||||
|
env_tmp_dir = "{toxworkdir}/tmp/{envname}"
|
||||||
|
commands = [[
|
||||||
|
"pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}",
|
||||||
|
{replace = "posargs", default = [], extend = true},
|
||||||
|
]]
|
||||||
|
|
||||||
|
[tool.tox.env.tests-min]
|
||||||
|
description = "pytest on minimum dependency versions"
|
||||||
|
base_python = ["3.13"]
|
||||||
|
commands = [
|
||||||
|
[
|
||||||
|
"uv", "pip", "install",
|
||||||
|
"blinker==1.9.0",
|
||||||
|
"click==8.1.3",
|
||||||
|
"itsdangerous==2.2.0",
|
||||||
|
"jinja2==3.1.2",
|
||||||
|
"markupsafe==2.1.1",
|
||||||
|
"werkzeug==3.1.0",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}",
|
||||||
|
{replace = "posargs", default = [], extend = true},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.tox.env.tests-dev]
|
||||||
|
description = "pytest on development dependency versions (git main branch)"
|
||||||
|
base_python = ["3.10"]
|
||||||
|
commands = [
|
||||||
|
[
|
||||||
|
"uv", "pip", "install",
|
||||||
|
"https://github.com/pallets-eco/blinker/archive/refs/heads/main.tar.gz",
|
||||||
|
"https://github.com/pallets/click/archive/refs/heads/main.tar.gz",
|
||||||
|
"https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz",
|
||||||
|
"https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz",
|
||||||
|
"https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz",
|
||||||
|
"https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}",
|
||||||
|
{replace = "posargs", default = [], extend = true},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.tox.env.style]
|
||||||
|
description = "run all pre-commit hooks on all files"
|
||||||
|
dependency_groups = ["pre-commit"]
|
||||||
|
skip_install = true
|
||||||
|
commands = [["pre-commit", "run", "--all-files"]]
|
||||||
|
|
||||||
|
[tool.tox.env.typing]
|
||||||
|
description = "run static type checkers"
|
||||||
|
dependency_groups = ["typing"]
|
||||||
|
commands = [
|
||||||
|
["mypy"],
|
||||||
|
["pyright"],
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.tox.env.docs]
|
||||||
|
description = "build docs"
|
||||||
|
dependency_groups = ["docs"]
|
||||||
|
commands = [["sphinx-build", "-E", "-W", "-b", "dirhtml", "docs", "docs/_build/dirhtml"]]
|
||||||
|
|
||||||
|
[tool.tox.env.docs-auto]
|
||||||
|
description = "continuously rebuild docs and start a local server"
|
||||||
|
dependency_groups = ["docs", "docs-auto"]
|
||||||
|
commands = [["sphinx-autobuild", "-W", "-b", "dirhtml", "--watch", "src", "docs", "docs/_build/dirhtml"]]
|
||||||
|
|
||||||
|
[tool.tox.env.update-actions]
|
||||||
|
description = "update GitHub Actions pins"
|
||||||
|
labels = ["update"]
|
||||||
|
dependency_groups = ["gha-update"]
|
||||||
|
skip_install = true
|
||||||
|
commands = [["gha-update"]]
|
||||||
|
|
||||||
|
[tool.tox.env.update-pre_commit]
|
||||||
|
description = "update pre-commit pins"
|
||||||
|
labels = ["update"]
|
||||||
|
dependency_groups = ["pre-commit"]
|
||||||
|
skip_install = true
|
||||||
|
commands = [["pre-commit", "autoupdate", "--freeze", "-j4"]]
|
||||||
|
|
||||||
|
[tool.tox.env.update-requirements]
|
||||||
|
description = "update uv lock"
|
||||||
|
labels = ["update"]
|
||||||
|
dependency_groups = []
|
||||||
|
no_default_groups = true
|
||||||
|
skip_install = true
|
||||||
|
commands = [["uv", "lock", {replace = "posargs", default = ["-U"], extend = true}]]
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
build
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
#
|
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile build.in
|
|
||||||
#
|
|
||||||
build==1.2.2.post1
|
|
||||||
# via -r build.in
|
|
||||||
packaging==24.2
|
|
||||||
# via build
|
|
||||||
pyproject-hooks==1.2.0
|
|
||||||
# via build
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
-r docs.txt
|
|
||||||
-r tests.txt
|
|
||||||
-r typing.txt
|
|
||||||
pre-commit
|
|
||||||
tox
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
#
|
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile dev.in
|
|
||||||
#
|
|
||||||
alabaster==1.0.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
asgiref==3.8.1
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/tests.txt
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
babel==2.17.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
cachetools==5.5.2
|
|
||||||
# via tox
|
|
||||||
certifi==2025.1.31
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# requests
|
|
||||||
cffi==1.17.1
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# cryptography
|
|
||||||
cfgv==3.4.0
|
|
||||||
# via pre-commit
|
|
||||||
chardet==5.2.0
|
|
||||||
# via tox
|
|
||||||
charset-normalizer==3.4.1
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# requests
|
|
||||||
colorama==0.4.6
|
|
||||||
# via tox
|
|
||||||
cryptography==44.0.2
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
distlib==0.3.9
|
|
||||||
# via virtualenv
|
|
||||||
docutils==0.21.2
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
# sphinx-tabs
|
|
||||||
filelock==3.18.0
|
|
||||||
# via
|
|
||||||
# tox
|
|
||||||
# virtualenv
|
|
||||||
identify==2.6.9
|
|
||||||
# via pre-commit
|
|
||||||
idna==3.10
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# requests
|
|
||||||
imagesize==1.4.1
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
iniconfig==2.1.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/tests.txt
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# pytest
|
|
||||||
jinja2==3.1.6
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
markupsafe==3.0.2
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# jinja2
|
|
||||||
mypy==1.15.0
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
mypy-extensions==1.0.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# mypy
|
|
||||||
nodeenv==1.9.1
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# pre-commit
|
|
||||||
# pyright
|
|
||||||
packaging==24.2
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# -r /Users/david/Projects/flask/requirements/tests.txt
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# pallets-sphinx-themes
|
|
||||||
# pyproject-api
|
|
||||||
# pytest
|
|
||||||
# sphinx
|
|
||||||
# tox
|
|
||||||
pallets-sphinx-themes==2.3.0
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
platformdirs==4.3.7
|
|
||||||
# via
|
|
||||||
# tox
|
|
||||||
# virtualenv
|
|
||||||
pluggy==1.5.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/tests.txt
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# pytest
|
|
||||||
# tox
|
|
||||||
pre-commit==4.2.0
|
|
||||||
# via -r dev.in
|
|
||||||
pycparser==2.22
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# cffi
|
|
||||||
pygments==2.19.1
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
# sphinx-tabs
|
|
||||||
pyproject-api==1.9.0
|
|
||||||
# via tox
|
|
||||||
pyright==1.1.398
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
pytest==8.3.5
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/tests.txt
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
python-dotenv==1.1.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/tests.txt
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
pyyaml==6.0.2
|
|
||||||
# via pre-commit
|
|
||||||
requests==2.32.3
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
roman-numerals-py==3.1.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
snowballstemmer==2.2.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
sphinx==8.2.3
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# pallets-sphinx-themes
|
|
||||||
# sphinx-notfound-page
|
|
||||||
# sphinx-tabs
|
|
||||||
# sphinxcontrib-log-cabinet
|
|
||||||
sphinx-notfound-page==1.1.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# pallets-sphinx-themes
|
|
||||||
sphinx-tabs==3.4.7
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
sphinxcontrib-applehelp==2.0.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
sphinxcontrib-devhelp==2.0.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
sphinxcontrib-htmlhelp==2.1.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
sphinxcontrib-jsmath==1.0.1
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
sphinxcontrib-log-cabinet==1.0.1
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
sphinxcontrib-qthelp==2.0.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
sphinxcontrib-serializinghtml==2.0.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# sphinx
|
|
||||||
tox==4.25.0
|
|
||||||
# via -r dev.in
|
|
||||||
types-contextvars==2.4.7.3
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
types-dataclasses==0.6.6
|
|
||||||
# via -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
typing-extensions==4.13.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/typing.txt
|
|
||||||
# mypy
|
|
||||||
# pyright
|
|
||||||
urllib3==2.3.0
|
|
||||||
# via
|
|
||||||
# -r /Users/david/Projects/flask/requirements/docs.txt
|
|
||||||
# requests
|
|
||||||
virtualenv==20.29.3
|
|
||||||
# via
|
|
||||||
# pre-commit
|
|
||||||
# tox
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
pallets-sphinx-themes
|
|
||||||
sphinx
|
|
||||||
sphinxcontrib-log-cabinet
|
|
||||||
sphinx-tabs
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#
|
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile docs.in
|
|
||||||
#
|
|
||||||
alabaster==1.0.0
|
|
||||||
# via sphinx
|
|
||||||
babel==2.17.0
|
|
||||||
# via sphinx
|
|
||||||
certifi==2025.1.31
|
|
||||||
# via requests
|
|
||||||
charset-normalizer==3.4.1
|
|
||||||
# via requests
|
|
||||||
docutils==0.21.2
|
|
||||||
# via
|
|
||||||
# sphinx
|
|
||||||
# sphinx-tabs
|
|
||||||
idna==3.10
|
|
||||||
# via requests
|
|
||||||
imagesize==1.4.1
|
|
||||||
# via sphinx
|
|
||||||
jinja2==3.1.6
|
|
||||||
# via sphinx
|
|
||||||
markupsafe==3.0.2
|
|
||||||
# via jinja2
|
|
||||||
packaging==24.2
|
|
||||||
# via
|
|
||||||
# pallets-sphinx-themes
|
|
||||||
# sphinx
|
|
||||||
pallets-sphinx-themes==2.3.0
|
|
||||||
# via -r docs.in
|
|
||||||
pygments==2.19.1
|
|
||||||
# via
|
|
||||||
# sphinx
|
|
||||||
# sphinx-tabs
|
|
||||||
requests==2.32.3
|
|
||||||
# via sphinx
|
|
||||||
roman-numerals-py==3.1.0
|
|
||||||
# via sphinx
|
|
||||||
snowballstemmer==2.2.0
|
|
||||||
# via sphinx
|
|
||||||
sphinx==8.2.3
|
|
||||||
# via
|
|
||||||
# -r docs.in
|
|
||||||
# pallets-sphinx-themes
|
|
||||||
# sphinx-notfound-page
|
|
||||||
# sphinx-tabs
|
|
||||||
# sphinxcontrib-log-cabinet
|
|
||||||
sphinx-notfound-page==1.1.0
|
|
||||||
# via pallets-sphinx-themes
|
|
||||||
sphinx-tabs==3.4.7
|
|
||||||
# via -r docs.in
|
|
||||||
sphinxcontrib-applehelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-devhelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-htmlhelp==2.1.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-jsmath==1.0.1
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-log-cabinet==1.0.1
|
|
||||||
# via -r docs.in
|
|
||||||
sphinxcontrib-qthelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-serializinghtml==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
urllib3==2.3.0
|
|
||||||
# via requests
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz
|
|
||||||
https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz
|
|
||||||
https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz
|
|
||||||
https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz
|
|
||||||
https://github.com/pallets/click/archive/refs/heads/main.tar.gz
|
|
||||||
https://github.com/pallets-eco/blinker/archive/refs/heads/main.tar.gz
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
werkzeug==3.1.0
|
|
||||||
jinja2==3.1.2
|
|
||||||
markupsafe==2.1.1
|
|
||||||
itsdangerous==2.2.0
|
|
||||||
click==8.1.3
|
|
||||||
blinker==1.9.0
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#
|
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile tests-min.in
|
|
||||||
#
|
|
||||||
blinker==1.9.0
|
|
||||||
# via -r tests-min.in
|
|
||||||
click==8.1.3
|
|
||||||
# via -r tests-min.in
|
|
||||||
itsdangerous==2.2.0
|
|
||||||
# via -r tests-min.in
|
|
||||||
jinja2==3.1.2
|
|
||||||
# via -r tests-min.in
|
|
||||||
markupsafe==2.1.1
|
|
||||||
# via
|
|
||||||
# -r tests-min.in
|
|
||||||
# jinja2
|
|
||||||
# werkzeug
|
|
||||||
werkzeug==3.1.0
|
|
||||||
# via -r tests-min.in
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
pytest
|
|
||||||
asgiref
|
|
||||||
greenlet ; python_version < "3.11"
|
|
||||||
python-dotenv
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#
|
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile tests.in
|
|
||||||
#
|
|
||||||
asgiref==3.8.1
|
|
||||||
# via -r tests.in
|
|
||||||
iniconfig==2.1.0
|
|
||||||
# via pytest
|
|
||||||
packaging==24.2
|
|
||||||
# via pytest
|
|
||||||
pluggy==1.5.0
|
|
||||||
# via pytest
|
|
||||||
pytest==8.3.5
|
|
||||||
# via -r tests.in
|
|
||||||
python-dotenv==1.1.0
|
|
||||||
# via -r tests.in
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
mypy
|
|
||||||
pyright
|
|
||||||
pytest
|
|
||||||
types-contextvars
|
|
||||||
types-dataclasses
|
|
||||||
asgiref
|
|
||||||
cryptography
|
|
||||||
python-dotenv
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#
|
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
|
||||||
# by the following command:
|
|
||||||
#
|
|
||||||
# pip-compile typing.in
|
|
||||||
#
|
|
||||||
asgiref==3.8.1
|
|
||||||
# via -r typing.in
|
|
||||||
cffi==1.17.1
|
|
||||||
# via cryptography
|
|
||||||
cryptography==44.0.2
|
|
||||||
# via -r typing.in
|
|
||||||
iniconfig==2.1.0
|
|
||||||
# via pytest
|
|
||||||
mypy==1.15.0
|
|
||||||
# via -r typing.in
|
|
||||||
mypy-extensions==1.0.0
|
|
||||||
# via mypy
|
|
||||||
nodeenv==1.9.1
|
|
||||||
# via pyright
|
|
||||||
packaging==24.2
|
|
||||||
# via pytest
|
|
||||||
pluggy==1.5.0
|
|
||||||
# via pytest
|
|
||||||
pycparser==2.22
|
|
||||||
# via cffi
|
|
||||||
pyright==1.1.398
|
|
||||||
# via -r typing.in
|
|
||||||
pytest==8.3.5
|
|
||||||
# via -r typing.in
|
|
||||||
python-dotenv==1.1.0
|
|
||||||
# via -r typing.in
|
|
||||||
types-contextvars==2.4.7.3
|
|
||||||
# via -r typing.in
|
|
||||||
types-dataclasses==0.6.6
|
|
||||||
# via -r typing.in
|
|
||||||
typing-extensions==4.13.0
|
|
||||||
# via
|
|
||||||
# mypy
|
|
||||||
# pyright
|
|
||||||
|
|
@ -1222,7 +1222,7 @@ class Flask(App):
|
||||||
# waiting to do it manually, so that the class can handle any
|
# waiting to do it manually, so that the class can handle any
|
||||||
# special logic
|
# special logic
|
||||||
rv = self.response_class(
|
rv = self.response_class(
|
||||||
rv,
|
rv, # pyright: ignore
|
||||||
status=status,
|
status=status,
|
||||||
headers=headers, # type: ignore[arg-type]
|
headers=headers, # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections.abc as c
|
||||||
|
import typing as t
|
||||||
|
from abc import ABCMeta
|
||||||
|
from collections.abc import MutableMapping
|
||||||
|
from datetime import datetime
|
||||||
|
from datetime import timezone
|
||||||
|
|
||||||
|
from werkzeug.datastructures import CallbackDict
|
||||||
|
|
||||||
|
from .app import App
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
|
||||||
|
class SessionMixin(MutableMapping[str, t.Any]):
|
||||||
|
"""Expands a basic dictionary with session attributes."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def permanent(self) -> bool:
|
||||||
|
"""This reflects the ``'_permanent'`` key in the dict."""
|
||||||
|
return self.get("_permanent", False)
|
||||||
|
|
||||||
|
@permanent.setter
|
||||||
|
def permanent(self, value: bool) -> None:
|
||||||
|
self["_permanent"] = bool(value)
|
||||||
|
|
||||||
|
#: Some implementations can detect whether a session is newly
|
||||||
|
#: created, but that is not guaranteed. Use with caution. The mixin
|
||||||
|
# default is hard-coded ``False``.
|
||||||
|
new = False
|
||||||
|
|
||||||
|
#: Some implementations can detect changes to the session and set
|
||||||
|
#: this when that happens. The mixin default is hard coded to
|
||||||
|
#: ``True``.
|
||||||
|
modified = True
|
||||||
|
|
||||||
|
#: Some implementations can detect when session data is read or
|
||||||
|
#: written and set this when that happens. The mixin default is hard
|
||||||
|
#: coded to ``True``.
|
||||||
|
accessed = True
|
||||||
|
|
||||||
|
|
||||||
|
class SecureCookieSession(CallbackDict[str, t.Any], SessionMixin):
|
||||||
|
"""Base class for sessions based on signed cookies.
|
||||||
|
|
||||||
|
This session backend will set the :attr:`modified` and
|
||||||
|
:attr:`accessed` attributes. It cannot reliably track whether a
|
||||||
|
session is new (vs. empty), so :attr:`new` remains hard coded to
|
||||||
|
``False``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: When data is changed, this is set to ``True``. Only the session
|
||||||
|
#: dictionary itself is tracked; if the session contains mutable
|
||||||
|
#: data (for example a nested dict) then this must be set to
|
||||||
|
#: ``True`` manually when modifying that data. The session cookie
|
||||||
|
#: will only be written to the response if this is ``True``.
|
||||||
|
modified = False
|
||||||
|
|
||||||
|
#: When data is read or written, this is set to ``True``. Used by
|
||||||
|
# :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
|
||||||
|
#: header, which allows caching proxies to cache different pages for
|
||||||
|
#: different users.
|
||||||
|
accessed = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
initial: c.Mapping[str, t.Any] | c.Iterable[tuple[str, t.Any]] | None = None,
|
||||||
|
) -> None:
|
||||||
|
def on_update(self: te.Self) -> None:
|
||||||
|
self.modified = True
|
||||||
|
self.accessed = True
|
||||||
|
|
||||||
|
super().__init__(initial, on_update)
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> t.Any:
|
||||||
|
self.accessed = True
|
||||||
|
return super().__getitem__(key)
|
||||||
|
|
||||||
|
def get(self, key: str, default: t.Any = None) -> t.Any:
|
||||||
|
self.accessed = True
|
||||||
|
return super().get(key, default)
|
||||||
|
|
||||||
|
def setdefault(self, key: str, default: t.Any = None) -> t.Any:
|
||||||
|
self.accessed = True
|
||||||
|
return super().setdefault(key, default)
|
||||||
|
|
||||||
|
|
||||||
|
class NullSession(SecureCookieSession):
|
||||||
|
"""Class used to generate nicer error messages if sessions are not
|
||||||
|
available. Will still allow read-only access to the empty session
|
||||||
|
but fail on setting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
|
||||||
|
raise RuntimeError(
|
||||||
|
"The session is unavailable because no secret "
|
||||||
|
"key was set. Set the secret_key on the "
|
||||||
|
"application to something unique and secret."
|
||||||
|
)
|
||||||
|
|
||||||
|
__setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950
|
||||||
|
del _fail
|
||||||
|
|
||||||
|
|
||||||
|
class SessionInterface(metaclass=ABCMeta): # noqa: B024
|
||||||
|
"""This is a SansIO abstract base class used by Flask and Quart to
|
||||||
|
then define thebasic interface to implement in order to replace
|
||||||
|
the default session interface of the frameworks.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: :meth:`make_null_session` will look here for the class that should
|
||||||
|
#: be created when a null session is requested. Likewise the
|
||||||
|
#: :meth:`is_null_session` method will perform a typecheck against
|
||||||
|
#: this type.
|
||||||
|
null_session_class = NullSession
|
||||||
|
|
||||||
|
#: A flag that indicates if the session interface is pickle based.
|
||||||
|
#: This can be used by Flask extensions to make a decision in regards
|
||||||
|
#: to how to deal with the session object.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.10
|
||||||
|
pickle_based = False
|
||||||
|
|
||||||
|
def is_null_session(self, obj: object) -> bool:
|
||||||
|
"""Checks if a given object is a null session. Null sessions are
|
||||||
|
not asked to be saved.
|
||||||
|
|
||||||
|
This checks if the object is an instance of :attr:`null_session_class`
|
||||||
|
by default.
|
||||||
|
"""
|
||||||
|
return isinstance(obj, self.null_session_class)
|
||||||
|
|
||||||
|
def get_cookie_name(self, app: App) -> str:
|
||||||
|
"""The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``."""
|
||||||
|
return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def get_cookie_domain(self, app: App) -> str | None:
|
||||||
|
"""The value of the ``Domain`` parameter on the session cookie. If not set,
|
||||||
|
browsers will only send the cookie to the exact domain it was set from.
|
||||||
|
Otherwise, they will send it to any subdomain of the given value as well.
|
||||||
|
|
||||||
|
Uses the :data:`SESSION_COOKIE_DOMAIN` config.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.3
|
||||||
|
Not set by default, does not fall back to ``SERVER_NAME``.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def get_cookie_path(self, app: App) -> str:
|
||||||
|
"""Returns the path for which the cookie should be valid. The
|
||||||
|
default implementation uses the value from the ``SESSION_COOKIE_PATH``
|
||||||
|
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
||||||
|
uses ``/`` if it's ``None``.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def get_cookie_httponly(self, app: App) -> bool:
|
||||||
|
"""Returns True if the session cookie should be httponly. This
|
||||||
|
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
|
||||||
|
config var.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_HTTPONLY"] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def get_cookie_secure(self, app: App) -> bool:
|
||||||
|
"""Returns True if the cookie should be secure. This currently
|
||||||
|
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_SECURE"] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def get_cookie_samesite(self, app: App) -> str | None:
|
||||||
|
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
|
||||||
|
``SameSite`` attribute. This currently just returns the value of
|
||||||
|
the :data:`SESSION_COOKIE_SAMESITE` setting.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_SAMESITE"] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def get_cookie_partitioned(self, app: App) -> bool:
|
||||||
|
"""Returns True if the cookie should be partitioned. By default, uses
|
||||||
|
the value of :data:`SESSION_COOKIE_PARTITIONED`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.1
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_PARTITIONED"] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def get_expiration_time(self, app: App, session: SessionMixin) -> datetime | None:
|
||||||
|
"""A helper method that returns an expiration date for the session
|
||||||
|
or ``None`` if the session is linked to the browser session. The
|
||||||
|
default implementation returns now + the permanent session
|
||||||
|
lifetime configured on the application.
|
||||||
|
"""
|
||||||
|
if session.permanent:
|
||||||
|
return datetime.now(timezone.utc) + app.permanent_session_lifetime
|
||||||
|
return None
|
||||||
|
|
||||||
|
def should_set_cookie(self, app: App, session: SessionMixin) -> bool:
|
||||||
|
"""Used by session backends to determine if a ``Set-Cookie`` header
|
||||||
|
should be set for this session cookie for this response. If the session
|
||||||
|
has been modified, the cookie is set. If the session is permanent and
|
||||||
|
the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
|
||||||
|
always set.
|
||||||
|
|
||||||
|
This check is usually skipped if the session was deleted.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
|
||||||
|
return session.modified or (
|
||||||
|
session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"]
|
||||||
|
)
|
||||||
|
|
@ -1,122 +1,28 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import collections.abc as c
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import typing as t
|
import typing as t
|
||||||
from collections.abc import MutableMapping
|
|
||||||
from datetime import datetime
|
|
||||||
from datetime import timezone
|
|
||||||
|
|
||||||
from itsdangerous import BadSignature
|
from itsdangerous import BadSignature
|
||||||
from itsdangerous import URLSafeTimedSerializer
|
from itsdangerous import URLSafeTimedSerializer
|
||||||
from werkzeug.datastructures import CallbackDict
|
|
||||||
|
|
||||||
from .json.tag import TaggedJSONSerializer
|
from .json.tag import TaggedJSONSerializer
|
||||||
|
from .sansio.sessions import NullSession as NullSession
|
||||||
|
from .sansio.sessions import SecureCookieSession as SecureCookieSession
|
||||||
|
from .sansio.sessions import SessionInterface as SansioSessionInterface
|
||||||
|
from .sansio.sessions import SessionMixin as SessionMixin
|
||||||
|
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
import typing_extensions as te
|
|
||||||
|
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .wrappers import Request
|
from .wrappers import Request
|
||||||
from .wrappers import Response
|
from .wrappers import Response
|
||||||
|
|
||||||
|
|
||||||
class SessionMixin(MutableMapping[str, t.Any]):
|
class SessionInterface(SansioSessionInterface):
|
||||||
"""Expands a basic dictionary with session attributes."""
|
"""The basic interface you have to implement in order to replace
|
||||||
|
the default session interface. The only methods you have to
|
||||||
@property
|
implement are :meth:`open_session` and :meth:`save_session`, the
|
||||||
def permanent(self) -> bool:
|
others have useful defaults which you don't need to change.
|
||||||
"""This reflects the ``'_permanent'`` key in the dict."""
|
|
||||||
return self.get("_permanent", False)
|
|
||||||
|
|
||||||
@permanent.setter
|
|
||||||
def permanent(self, value: bool) -> None:
|
|
||||||
self["_permanent"] = bool(value)
|
|
||||||
|
|
||||||
#: Some implementations can detect whether a session is newly
|
|
||||||
#: created, but that is not guaranteed. Use with caution. The mixin
|
|
||||||
# default is hard-coded ``False``.
|
|
||||||
new = False
|
|
||||||
|
|
||||||
#: Some implementations can detect changes to the session and set
|
|
||||||
#: this when that happens. The mixin default is hard coded to
|
|
||||||
#: ``True``.
|
|
||||||
modified = True
|
|
||||||
|
|
||||||
#: Some implementations can detect when session data is read or
|
|
||||||
#: written and set this when that happens. The mixin default is hard
|
|
||||||
#: coded to ``True``.
|
|
||||||
accessed = True
|
|
||||||
|
|
||||||
|
|
||||||
class SecureCookieSession(CallbackDict[str, t.Any], SessionMixin):
|
|
||||||
"""Base class for sessions based on signed cookies.
|
|
||||||
|
|
||||||
This session backend will set the :attr:`modified` and
|
|
||||||
:attr:`accessed` attributes. It cannot reliably track whether a
|
|
||||||
session is new (vs. empty), so :attr:`new` remains hard coded to
|
|
||||||
``False``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: When data is changed, this is set to ``True``. Only the session
|
|
||||||
#: dictionary itself is tracked; if the session contains mutable
|
|
||||||
#: data (for example a nested dict) then this must be set to
|
|
||||||
#: ``True`` manually when modifying that data. The session cookie
|
|
||||||
#: will only be written to the response if this is ``True``.
|
|
||||||
modified = False
|
|
||||||
|
|
||||||
#: When data is read or written, this is set to ``True``. Used by
|
|
||||||
# :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
|
|
||||||
#: header, which allows caching proxies to cache different pages for
|
|
||||||
#: different users.
|
|
||||||
accessed = False
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
initial: c.Mapping[str, t.Any] | c.Iterable[tuple[str, t.Any]] | None = None,
|
|
||||||
) -> None:
|
|
||||||
def on_update(self: te.Self) -> None:
|
|
||||||
self.modified = True
|
|
||||||
self.accessed = True
|
|
||||||
|
|
||||||
super().__init__(initial, on_update)
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> t.Any:
|
|
||||||
self.accessed = True
|
|
||||||
return super().__getitem__(key)
|
|
||||||
|
|
||||||
def get(self, key: str, default: t.Any = None) -> t.Any:
|
|
||||||
self.accessed = True
|
|
||||||
return super().get(key, default)
|
|
||||||
|
|
||||||
def setdefault(self, key: str, default: t.Any = None) -> t.Any:
|
|
||||||
self.accessed = True
|
|
||||||
return super().setdefault(key, default)
|
|
||||||
|
|
||||||
|
|
||||||
class NullSession(SecureCookieSession):
|
|
||||||
"""Class used to generate nicer error messages if sessions are not
|
|
||||||
available. Will still allow read-only access to the empty session
|
|
||||||
but fail on setting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
|
|
||||||
raise RuntimeError(
|
|
||||||
"The session is unavailable because no secret "
|
|
||||||
"key was set. Set the secret_key on the "
|
|
||||||
"application to something unique and secret."
|
|
||||||
)
|
|
||||||
|
|
||||||
__setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950
|
|
||||||
del _fail
|
|
||||||
|
|
||||||
|
|
||||||
class SessionInterface:
|
|
||||||
"""The basic interface you have to implement in order to replace the
|
|
||||||
default session interface which uses werkzeug's securecookie
|
|
||||||
implementation. The only methods you have to implement are
|
|
||||||
:meth:`open_session` and :meth:`save_session`, the others have
|
|
||||||
useful defaults which you don't need to change.
|
|
||||||
|
|
||||||
The session object returned by the :meth:`open_session` method has to
|
The session object returned by the :meth:`open_session` method has to
|
||||||
provide a dictionary like interface plus the properties and methods
|
provide a dictionary like interface plus the properties and methods
|
||||||
|
|
@ -146,21 +52,9 @@ class SessionInterface:
|
||||||
begin and end processing.
|
begin and end processing.
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
.. versionadded:: 0.8
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: :meth:`make_null_session` will look here for the class that should
|
|
||||||
#: be created when a null session is requested. Likewise the
|
|
||||||
#: :meth:`is_null_session` method will perform a typecheck against
|
|
||||||
#: this type.
|
|
||||||
null_session_class = NullSession
|
|
||||||
|
|
||||||
#: A flag that indicates if the session interface is pickle based.
|
|
||||||
#: This can be used by Flask extensions to make a decision in regards
|
|
||||||
#: to how to deal with the session object.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 0.10
|
|
||||||
pickle_based = False
|
|
||||||
|
|
||||||
def make_null_session(self, app: Flask) -> NullSession:
|
def make_null_session(self, app: Flask) -> NullSession:
|
||||||
"""Creates a null session which acts as a replacement object if the
|
"""Creates a null session which acts as a replacement object if the
|
||||||
real session support could not be loaded due to a configuration
|
real session support could not be loaded due to a configuration
|
||||||
|
|
@ -173,93 +67,6 @@ class SessionInterface:
|
||||||
"""
|
"""
|
||||||
return self.null_session_class()
|
return self.null_session_class()
|
||||||
|
|
||||||
def is_null_session(self, obj: object) -> bool:
|
|
||||||
"""Checks if a given object is a null session. Null sessions are
|
|
||||||
not asked to be saved.
|
|
||||||
|
|
||||||
This checks if the object is an instance of :attr:`null_session_class`
|
|
||||||
by default.
|
|
||||||
"""
|
|
||||||
return isinstance(obj, self.null_session_class)
|
|
||||||
|
|
||||||
def get_cookie_name(self, app: Flask) -> str:
|
|
||||||
"""The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``."""
|
|
||||||
return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return]
|
|
||||||
|
|
||||||
def get_cookie_domain(self, app: Flask) -> str | None:
|
|
||||||
"""The value of the ``Domain`` parameter on the session cookie. If not set,
|
|
||||||
browsers will only send the cookie to the exact domain it was set from.
|
|
||||||
Otherwise, they will send it to any subdomain of the given value as well.
|
|
||||||
|
|
||||||
Uses the :data:`SESSION_COOKIE_DOMAIN` config.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
Not set by default, does not fall back to ``SERVER_NAME``.
|
|
||||||
"""
|
|
||||||
return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return]
|
|
||||||
|
|
||||||
def get_cookie_path(self, app: Flask) -> str:
|
|
||||||
"""Returns the path for which the cookie should be valid. The
|
|
||||||
default implementation uses the value from the ``SESSION_COOKIE_PATH``
|
|
||||||
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
|
||||||
uses ``/`` if it's ``None``.
|
|
||||||
"""
|
|
||||||
return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] # type: ignore[no-any-return]
|
|
||||||
|
|
||||||
def get_cookie_httponly(self, app: Flask) -> bool:
|
|
||||||
"""Returns True if the session cookie should be httponly. This
|
|
||||||
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
|
|
||||||
config var.
|
|
||||||
"""
|
|
||||||
return app.config["SESSION_COOKIE_HTTPONLY"] # type: ignore[no-any-return]
|
|
||||||
|
|
||||||
def get_cookie_secure(self, app: Flask) -> bool:
|
|
||||||
"""Returns True if the cookie should be secure. This currently
|
|
||||||
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
|
|
||||||
"""
|
|
||||||
return app.config["SESSION_COOKIE_SECURE"] # type: ignore[no-any-return]
|
|
||||||
|
|
||||||
def get_cookie_samesite(self, app: Flask) -> str | None:
|
|
||||||
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
|
|
||||||
``SameSite`` attribute. This currently just returns the value of
|
|
||||||
the :data:`SESSION_COOKIE_SAMESITE` setting.
|
|
||||||
"""
|
|
||||||
return app.config["SESSION_COOKIE_SAMESITE"] # type: ignore[no-any-return]
|
|
||||||
|
|
||||||
def get_cookie_partitioned(self, app: Flask) -> bool:
|
|
||||||
"""Returns True if the cookie should be partitioned. By default, uses
|
|
||||||
the value of :data:`SESSION_COOKIE_PARTITIONED`.
|
|
||||||
|
|
||||||
.. versionadded:: 3.1
|
|
||||||
"""
|
|
||||||
return app.config["SESSION_COOKIE_PARTITIONED"] # type: ignore[no-any-return]
|
|
||||||
|
|
||||||
def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None:
|
|
||||||
"""A helper method that returns an expiration date for the session
|
|
||||||
or ``None`` if the session is linked to the browser session. The
|
|
||||||
default implementation returns now + the permanent session
|
|
||||||
lifetime configured on the application.
|
|
||||||
"""
|
|
||||||
if session.permanent:
|
|
||||||
return datetime.now(timezone.utc) + app.permanent_session_lifetime
|
|
||||||
return None
|
|
||||||
|
|
||||||
def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool:
|
|
||||||
"""Used by session backends to determine if a ``Set-Cookie`` header
|
|
||||||
should be set for this session cookie for this response. If the session
|
|
||||||
has been modified, the cookie is set. If the session is permanent and
|
|
||||||
the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
|
|
||||||
always set.
|
|
||||||
|
|
||||||
This check is usually skipped if the session was deleted.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
|
|
||||||
return session.modified or (
|
|
||||||
session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def open_session(self, app: Flask, request: Request) -> SessionMixin | None:
|
def open_session(self, app: Flask, request: Request) -> SessionMixin | None:
|
||||||
"""This is called at the beginning of each request, after
|
"""This is called at the beginning of each request, after
|
||||||
pushing the request context, before matching the URL.
|
pushing the request context, before matching the URL.
|
||||||
|
|
|
||||||
62
tox.ini
62
tox.ini
|
|
@ -1,62 +0,0 @@
|
||||||
[tox]
|
|
||||||
envlist =
|
|
||||||
py3{13,12,11,10,9}
|
|
||||||
pypy310
|
|
||||||
py313-min
|
|
||||||
py39-dev
|
|
||||||
style
|
|
||||||
typing
|
|
||||||
docs
|
|
||||||
skip_missing_interpreters = true
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
package = wheel
|
|
||||||
wheel_build_env = .pkg
|
|
||||||
envtmpdir = {toxworkdir}/tmp/{envname}
|
|
||||||
constrain_package_deps = true
|
|
||||||
use_frozen_constraints = true
|
|
||||||
deps =
|
|
||||||
-r requirements/tests.txt
|
|
||||||
min: -r requirements/tests-min.txt
|
|
||||||
dev: -r requirements/tests-dev.txt
|
|
||||||
commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs}
|
|
||||||
|
|
||||||
[testenv:style]
|
|
||||||
deps = pre-commit
|
|
||||||
skip_install = true
|
|
||||||
commands = pre-commit run --all-files
|
|
||||||
|
|
||||||
[testenv:typing]
|
|
||||||
deps = -r requirements/typing.txt
|
|
||||||
commands =
|
|
||||||
mypy
|
|
||||||
pyright
|
|
||||||
|
|
||||||
[testenv:docs]
|
|
||||||
deps = -r requirements/docs.txt
|
|
||||||
commands = sphinx-build -E -W -b dirhtml docs docs/_build/dirhtml
|
|
||||||
|
|
||||||
[testenv:update-actions]
|
|
||||||
labels = update
|
|
||||||
deps = gha-update
|
|
||||||
skip_install = true
|
|
||||||
commands = gha-update
|
|
||||||
|
|
||||||
[testenv:update-pre_commit]
|
|
||||||
labels = update
|
|
||||||
deps = pre-commit
|
|
||||||
skip_install = true
|
|
||||||
commands = pre-commit autoupdate -j4
|
|
||||||
|
|
||||||
[testenv:update-requirements]
|
|
||||||
labels = update
|
|
||||||
deps = pip-tools
|
|
||||||
skip_install = true
|
|
||||||
change_dir = requirements
|
|
||||||
commands =
|
|
||||||
pip-compile build.in -q {posargs:-U}
|
|
||||||
pip-compile docs.in -q {posargs:-U}
|
|
||||||
pip-compile tests.in -q {posargs:-U}
|
|
||||||
pip-compile tests-min.in -q
|
|
||||||
pip-compile typing.in -q {posargs:-U}
|
|
||||||
pip-compile dev.in -q {posargs:-U}
|
|
||||||
Loading…
Reference in New Issue