From b4eb707af0f668b2aefee91fad3e9066bceeeefc Mon Sep 17 00:00:00 2001
From: Carsten Schoenert <c.schoenert@t-online.de>
Date: Thu, 19 Dec 2024 16:16:41 +0200
Subject: [PATCH] New upstream version 1.2.2

---
 .github/workflows/lock.yaml         |   2 +-
 .github/workflows/pre-commit.yaml   |  16 ++++
 .github/workflows/publish.yaml      |  73 +++++++++++++++
 .github/workflows/tests.yaml        |  38 ++++----
 .gitignore                          |   1 +
 .pre-commit-config.yaml             |  36 ++------
 .readthedocs.yaml                   |   8 +-
 CONTRIBUTING.rst                    |   4 +-
 README.rst                          |   4 +-
 docs/changes.rst                    |   8 ++
 docs/conf.py                        |   8 +-
 docs/config.rst                     |   3 +
 docs/csrf.rst                       |   7 ++
 docs/form.rst                       |   2 +-
 docs/install.rst                    |   8 +-
 examples/recaptcha/app.py           |   1 -
 pyproject.toml                      |  50 +++++-----
 requirements/dev.txt                | 138 +++++++---------------------
 requirements/docs.txt               |  57 ++++++------
 requirements/style.txt              |  22 ++---
 requirements/tests.txt              |  16 +---
 src/flask_wtf/__init__.py           |  10 +-
 src/flask_wtf/file.py               |   5 +-
 src/flask_wtf/recaptcha/__init__.py |   2 +
 tests/test_file.py                  |  26 +++---
 tox.ini                             |   2 +-
 26 files changed, 273 insertions(+), 274 deletions(-)
 create mode 100644 .github/workflows/pre-commit.yaml
 create mode 100644 .github/workflows/publish.yaml

diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml
index c771673..9ea3038 100644
--- a/.github/workflows/lock.yaml
+++ b/.github/workflows/lock.yaml
@@ -8,7 +8,7 @@ jobs:
   lock:
     runs-on: ubuntu-latest
     steps:
-      - uses: dessant/lock-threads@v4
+      - uses: dessant/lock-threads@v5
         with:
           github-token: ${{ github.token }}
           issue-inactive-days: 14
diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml
new file mode 100644
index 0000000..683c85b
--- /dev/null
+++ b/.github/workflows/pre-commit.yaml
@@ -0,0 +1,16 @@
+name: pre-commit
+on:
+  pull_request:
+  push:
+    branches: [main, '*.x']
+jobs:
+  main:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+    - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
+      with:
+        python-version: 3.x
+    - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
+    - uses: pre-commit-ci/lite-action@9d882e7a565f7008d4faf128f27d1cb6503d4ebf # v1.0.2
+      if: ${{ !cancelled() }}
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..41e0810
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,73 @@
+name: Publish
+on:
+  push:
+    tags:
+      - '*'
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    outputs:
+      hash: ${{ steps.hash.outputs.hash }}
+    steps:
+      - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+      - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
+        with:
+          python-version: '3.x'
+          cache: pip
+      - run: pip install -e .
+      - run: pip install build
+      # Use the commit date instead of the current date during the build.
+      - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
+      - run: python -m build
+      # Generate hashes used for provenance.
+      - name: generate hash
+        id: hash
+        run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT
+      - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
+        with:
+          path: ./dist
+  provenance:
+    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@v2.0.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]
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+      - name: create release
+        run: >
+          gh release create --draft --repo ${{ github.repository }}
+          ${{ github.ref_name }}
+          *.intoto.jsonl/* artifact/*
+        env:
+          GH_TOKEN: ${{ github.token }}
+  publish-pypi:
+    needs: [provenance]
+    # Wait for approval before attempting to upload to PyPI. This allows reviewing the
+    # files in the draft release.
+    environment:
+      name: publish
+      url: https://pypi.org/project/flask-wtf/${{ github.ref_name }}
+    runs-on: ubuntu-latest
+    permissions:
+      id-token: write
+    steps:
+      - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+      - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0
+        with:
+          repository-url: https://test.pypi.org/legacy/
+          packages-dir: artifact/
+      - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0
+        with:
+          packages-dir: artifact/
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index b37e564..b1f9216 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -4,38 +4,34 @@ on:
     branches:
       - main
       - '*.x'
+    paths-ignore:
+      - 'docs/**'
+      - '*.md'
+      - '*.rst'
   pull_request:
-    branches:
-      - main
-      - '*.x'
     paths-ignore:
       - 'docs/**'
+      - '*.md'
       - '*.rst'
 jobs:
   tests:
-    name: ${{ matrix.name }}
-    runs-on: ubuntu-latest
+    name: ${{ matrix.name || matrix.python }}
+    runs-on: ${{ matrix.os || 'ubuntu-latest' }}
     strategy:
       fail-fast: false
       matrix:
         include:
-          - {name: '3.11', python: '3.11', tox: 'py311,py-no-babel'}
-          - {name: '3.10', python: '3.10', tox: py310}
-          - {name: '3.9', python: '3.9', tox: py39}
-          - {name: '3.8', python: '3.8', tox: py38}
-          - {name: 'PyPy310', python: 'pypy-3.10', tox: pypy310}
-          - {name: 'PyPy39', python: 'pypy-3.9', tox: pypy39}
+          - {python: '3.13'}
+          - {python: '3.12'}
+          - {python: '3.11'}
+          - {python: '3.10'}
+          - {python: '3.9'}
     steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-python@v4
+      - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+      - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
         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
       - run: pip install tox
-      - run: tox -e ${{ matrix.tox }}
+      - run: tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
diff --git a/.gitignore b/.gitignore
index 00d8dd2..9ba3a8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ __pycache__/
 /docs/_build/
 .coverage
 .coverage.*
+/htmlcov/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5574264..856b490 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,36 +1,14 @@
-ci:
-  autoupdate_schedule: monthly
 repos:
-  - repo: https://github.com/asottile/pyupgrade
-    rev: v3.10.1
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.7.0
     hooks:
-      - id: pyupgrade
-        args: ["--py37-plus"]
-  - repo: https://github.com/asottile/reorder-python-imports
-    rev: v3.10.0
-    hooks:
-      - id: reorder-python-imports
-        args: ["--application-directories", "src"]
-        additional_dependencies: ["setuptools>60.9"]
-  - repo: https://github.com/psf/black
-    rev: 23.7.0
-    hooks:
-      - id: black
-  - repo: https://github.com/PyCQA/flake8
-    rev: 6.1.0
-    hooks:
-      - id: flake8
-        additional_dependencies:
-          - flake8-bugbear
-          - flake8-implicit-str-concat
-          - flake8-pyproject
-  - 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.4.0
+    rev: v5.0.0
     hooks:
+      - id: check-merge-conflict
+      - id: debug-statements
       - id: fix-byte-order-marker
       - id: trailing-whitespace
       - id: end-of-file-fixer
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 44df8af..865c685 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -1,4 +1,8 @@
 version: 2
+build:
+  os: ubuntu-22.04
+  tools:
+    python: '3.12'
 python:
   install:
     - requirements: requirements/docs.txt
@@ -7,7 +11,3 @@ python:
 sphinx:
   builder: dirhtml
   fail_on_warning: true
-build:
-  os: "ubuntu-22.04"
-  tools:
-    python: "3.11"
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 41523a3..b93ddcb 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -83,7 +83,7 @@ First time setup
 
     .. code-block:: text
 
-        $ git clone https://github.com/wtforms/flask-wtf
+        $ git clone https://github.com/pallets-eco/flask-wtf
         $ cd flask-wtf
 
 -   Add your fork as a remote to push your work to. Replace
@@ -130,7 +130,7 @@ First time setup
 .. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git
 .. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address
 .. _GitHub account: https://github.com/join
-.. _Fork: https://github.com/wtforms/flask-wtf/fork
+.. _Fork: https://github.com/pallets-eco/flask-wtf/fork
 .. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork
 
 
diff --git a/README.rst b/README.rst
index 5f69eb4..fbbd923 100644
--- a/README.rst
+++ b/README.rst
@@ -10,6 +10,6 @@ Links
 -   Documentation: https://flask-wtf.readthedocs.io/
 -   Changes: https://flask-wtf.readthedocs.io/changes/
 -   PyPI Releases: https://pypi.org/project/Flask-WTF/
--   Source Code: https://github.com/wtforms/flask-wtf/
--   Issue Tracker: https://github.com/wtforms/flask-wtf/issues/
+-   Source Code: https://github.com/pallets-eco/flask-wtf/
+-   Issue Tracker: https://github.com/pallets-eco/flask-wtf/issues/
 -   Chat: https://discord.gg/pallets
diff --git a/docs/changes.rst b/docs/changes.rst
index db77082..16d5c0a 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -1,6 +1,14 @@
 Changes
 =======
 
+Version 1.2.2
+-------------
+
+Released 2024-10-20
+
+- Move the project to the pallets-eco organization. :pr:`602`
+- Stop support for Python 3.8. Start support for Python 3.13. :pr:`603`
+
 Version 1.2.1
 -------------
 
diff --git a/docs/conf.py b/docs/conf.py
index bd37efa..51e97a5 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -23,7 +23,7 @@ intersphinx_mapping = {
     "flask": ("https://flask.palletsprojects.com/", None),
     "wtforms": ("https://wtforms.readthedocs.io/", None),
 }
-issues_github_path = "wtforms/flask-wtf"
+issues_github_path = "pallets-eco/flask-wtf"
 
 # HTML -----------------------------------------------------------------
 
@@ -32,8 +32,10 @@ html_theme_options = {"index_sidebar_logo": False}
 html_context = {
     "project_links": [
         ProjectLink("PyPI Releases", "https://pypi.org/project/Flask-WTF/"),
-        ProjectLink("Source Code", "https://github.com/wtforms/flask-wtf/"),
-        ProjectLink("Issue Tracker", "https://github.com/wtforms/flask-wtf/issues/"),
+        ProjectLink("Source Code", "https://github.com/pallets-eco/flask-wtf/"),
+        ProjectLink(
+            "Issue Tracker", "https://github.com/pallets-eco/flask-wtf/issues/"
+        ),
         ProjectLink("Chat", "https://discord.gg/pallets"),
     ]
 }
diff --git a/docs/config.rst b/docs/config.rst
index b4decae..fba26c9 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -19,6 +19,9 @@ Configuration
 ``WTF_CSRF_TIME_LIMIT``    Max age in seconds for CSRF tokens. Default is
                            ``3600``. If set to ``None``, the CSRF token is valid
                            for the life of the session.
+                           If your webserver has a cache policy, make sure it is
+                           configured with at maximum this value, so user browsers
+                           won't display pages with expired CSRF tokens.
 ``WTF_CSRF_SSL_STRICT``    Whether to enforce the same origin policy by checking
                            that the referrer matches the host. Only applies to
                            HTTPS requests. Default is ``True``.
diff --git a/docs/csrf.rst b/docs/csrf.rst
index d9a8ff6..b8d0fa1 100644
--- a/docs/csrf.rst
+++ b/docs/csrf.rst
@@ -34,6 +34,13 @@ Like other Flask extensions, you can apply it lazily::
     this will use the Flask app's ``SECRET_KEY``. If you'd like to use a
     separate token you can set ``WTF_CSRF_SECRET_KEY``.
 
+.. warning::
+
+    Make sure your webserver cache policy wont't interfere with the CSRF protection.
+    If pages are cached longer than the ``WTF_CSRF_TIME_LIMIT`` value, then user browsers
+    may serve cached page including expired CSRF token, resulting in random *Invalid*
+    or *Expired* CSRF errors.
+
 HTML Forms
 ----------
 
diff --git a/docs/form.rst b/docs/form.rst
index b7edfe1..9a6dfac 100644
--- a/docs/form.rst
+++ b/docs/form.rst
@@ -182,4 +182,4 @@ And it can be easily setup in the templates:
 
 We have an example for you: `recaptcha@github`_.
 
-.. _`recaptcha@github`: https://github.com/wtforms/flask-wtf/tree/main/examples/recaptcha
+.. _`recaptcha@github`: https://github.com/pallets-eco/flask-wtf/tree/main/examples/recaptcha
diff --git a/docs/install.rst b/docs/install.rst
index 40fdaf5..f2c547d 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -19,12 +19,12 @@ Development
 The latest code is available from `GitHub`_. Clone the repository then install
 using pip. ::
 
-    git clone https://github.com/wtforms/flask-wtf
+    git clone https://github.com/pallets-eco/flask-wtf
     pip install -e ./flask-wtf
 
 Or install the latest build from an `archive`_. ::
 
-    pip install -U https://github.com/wtforms/flask-wtf/archive/main.tar.gz
+    pip install -U https://github.com/pallets-eco/flask-wtf/archive/main.tar.gz
 
-.. _GitHub: https://github.com/wtforms/flask-wtf
-.. _archive: https://github.com/wtforms/flask-wtf/archive/main.tar.gz
+.. _GitHub: https://github.com/pallets-eco/flask-wtf
+.. _archive: https://github.com/pallets-eco/flask-wtf/archive/main.tar.gz
diff --git a/examples/recaptcha/app.py b/examples/recaptcha/app.py
index 150cc51..fa34d87 100644
--- a/examples/recaptcha/app.py
+++ b/examples/recaptcha/app.py
@@ -10,7 +10,6 @@ from wtforms.validators import DataRequired
 from flask_wtf import FlaskForm
 from flask_wtf.recaptcha import RecaptchaField
 
-
 DEBUG = True
 SECRET_KEY = "secret"
 
diff --git a/pyproject.toml b/pyproject.toml
index ba5d915..154a6ef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,7 +16,7 @@ classifiers = [
     "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
     "Topic :: Software Development :: Libraries :: Application Frameworks",
 ]
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 dependencies = [
     "Flask",
     "WTForms",
@@ -27,8 +27,8 @@ dynamic = ["version"]
 [project.urls]
 Documentation = "https://flask-wtf.readthedocs.io/"
 Changes = "https://flask-wtf.readthedocs.io/changes/"
-"Source Code" = "https://github.com/wtforms/flask-wtf/"
-"Issue Tracker" = "https://github.com/wtforms/flask-wtf/issues/"
+"Source Code" = "https://github.com/pallets-eco/flask-wtf/"
+"Issue Tracker" = "https://github.com/pallets-eco/flask-wtf/issues/"
 Chat = "https://discord.gg/pallets"
 
 [project.optional-dependencies]
@@ -75,30 +75,22 @@ exclude_lines = [
     "except ImportError:",
 ]
 
-[tool.flake8]
-# B = bugbear
-# E = pycodestyle errors
-# F = flake8 pyflakes
-# W = pycodestyle warnings
-# B9 = bugbear opinions
-# ISC = implicit-str-concat
-select = ["B", "E", "F", "W", "B9", "ISC"]
-ignore = [
-    # slice notation whitespace, invalid
-    "E203",
-    # line length, handled by bugbear B950
-    "E501",
-    # bare except, handled by bugbear B001
-    "E722",
-    # bin op line break, invalid
-    "W503",
-    # requires 'strict' argument for 'zip'
-    # that needs python >= 3.10
-    "B905",
-]
-# up to 88 allowed by bugbear B950
-max-line-length = 80
-per-file-ignores = [
-    # __init__ modules export names
-    "**/__init__.py: F401, F403",
+[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
 ]
+
+[tool.ruff.lint.isort]
+force-single-line = true
+order-by-type = false
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 3be5b3c..4ea15a4 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -5,129 +5,59 @@
 #
 #    pip-compile-multi
 #
-alabaster==0.7.12
-    # via sphinx
-attrs==21.4.0
-    # via pytest
-babel==2.9.1
-    # via sphinx
-certifi==2023.7.22
-    # via requests
-cfgv==3.3.1
+-r docs.txt
+-r tests.txt
+build==1.2.2.post1
+    # via pip-tools
+cachetools==5.5.0
+    # via tox
+cfgv==3.4.0
     # via pre-commit
-charset-normalizer==2.0.12
-    # via requests
-click==8.1.1
+chardet==5.2.0
+    # via tox
+click==8.1.7
     # via
     #   pip-compile-multi
     #   pip-tools
-distlib==0.3.4
+colorama==0.4.6
+    # via tox
+distlib==0.3.9
     # via virtualenv
-docutils==0.17.1
-    # via sphinx
-filelock==3.6.0
+filelock==3.16.1
     # via
     #   tox
     #   virtualenv
-identify==2.4.12
+identify==2.6.1
     # via pre-commit
-idna==3.3
-    # via requests
-imagesize==1.3.0
-    # via sphinx
-iniconfig==1.1.1
-    # via pytest
-jinja2==3.1.1
-    # via sphinx
-markupsafe==2.1.1
-    # via jinja2
-nodeenv==1.6.0
+nodeenv==1.9.1
     # via pre-commit
-packaging==21.3
-    # via
-    #   pallets-sphinx-themes
-    #   pytest
-    #   sphinx
-    #   tox
-pallets-sphinx-themes==2.0.2
-    # via -r docs.in
-pep517==0.12.0
-    # via pip-tools
-pip-compile-multi==2.4.4
-    # via -r dev.in
-pip-tools==6.5.1
+pip-compile-multi==2.6.4
+    # via -r requirements/dev.in
+pip-tools==7.4.1
     # via pip-compile-multi
-platformdirs==2.5.1
-    # via virtualenv
-pluggy==1.0.0
-    # via
-    #   pytest
-    #   tox
-pre-commit==2.17.0
-    # via -r dev.in
-py==1.11.0
-    # via
-    #   pytest
-    #   tox
-pygments==2.15.0
-    # via sphinx
-pyparsing==3.0.7
-    # via packaging
-pytest==7.1.1
-    # via -r tests.in
-pytz==2022.1
-    # via babel
-pyyaml==6.0
-    # via pre-commit
-requests==2.31.0
-    # via sphinx
-six==1.16.0
+platformdirs==4.3.6
     # via
     #   tox
     #   virtualenv
-snowballstemmer==2.2.0
-    # via sphinx
-sphinx==4.5.0
-    # via
-    #   -r docs.in
-    #   pallets-sphinx-themes
-    #   sphinx-issues
-    #   sphinxcontrib-log-cabinet
-sphinx-issues==3.0.1
-    # via -r docs.in
-sphinxcontrib-applehelp==1.0.2
-    # via sphinx
-sphinxcontrib-devhelp==1.0.2
-    # via sphinx
-sphinxcontrib-htmlhelp==2.0.0
-    # via sphinx
-sphinxcontrib-jsmath==1.0.1
-    # via sphinx
-sphinxcontrib-log-cabinet==1.0.1
-    # via -r docs.in
-sphinxcontrib-qthelp==1.0.3
-    # via sphinx
-sphinxcontrib-serializinghtml==1.1.5
-    # via sphinx
-toml==0.10.2
-    # via
-    #   pre-commit
-    #   tox
-tomli==2.0.1
+pre-commit==4.0.1
+    # via -r requirements/dev.in
+pyproject-api==1.8.0
+    # via tox
+pyproject-hooks==1.2.0
     # via
-    #   pep517
-    #   pytest
-toposort==1.7
+    #   build
+    #   pip-tools
+pyyaml==6.0.2
+    # via pre-commit
+toposort==1.10
     # via pip-compile-multi
-tox==3.24.5
-    # via -r dev.in
-urllib3==1.26.9
-    # via requests
-virtualenv==20.14.0
+tox==4.23.0
+    # via -r requirements/dev.in
+virtualenv==20.27.0
     # via
     #   pre-commit
     #   tox
-wheel==0.38.1
+wheel==0.44.0
     # via pip-tools
 
 # The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/docs.txt b/requirements/docs.txt
index 1900670..edfe85d 100644
--- a/requirements/docs.txt
+++ b/requirements/docs.txt
@@ -5,61 +5,60 @@
 #
 #    pip-compile-multi
 #
-alabaster==0.7.12
+alabaster==1.0.0
     # via sphinx
-babel==2.9.1
+babel==2.16.0
     # via sphinx
-certifi==2023.7.22
+certifi==2024.8.30
     # via requests
-charset-normalizer==2.0.12
+charset-normalizer==3.4.0
     # via requests
-docutils==0.17.1
+docutils==0.21.2
     # via sphinx
-idna==3.3
+idna==3.10
     # via requests
-imagesize==1.3.0
+imagesize==1.4.1
     # via sphinx
-jinja2==3.1.1
+jinja2==3.1.4
     # via sphinx
-markupsafe==2.1.1
+markupsafe==3.0.1
     # via jinja2
-packaging==21.3
+packaging==24.1
     # via
     #   pallets-sphinx-themes
     #   sphinx
-pallets-sphinx-themes==2.0.2
-    # via -r docs.in
-pygments==2.15.0
+pallets-sphinx-themes==2.2.0
+    # via -r requirements/docs.in
+pygments==2.18.0
     # via sphinx
-pyparsing==3.0.7
-    # via packaging
-pytz==2022.1
-    # via babel
-requests==2.31.0
+requests==2.32.3
     # via sphinx
 snowballstemmer==2.2.0
     # via sphinx
-sphinx==4.5.0
+sphinx==8.1.3
     # via
-    #   -r docs.in
+    #   -r requirements/docs.in
     #   pallets-sphinx-themes
     #   sphinx-issues
+    #   sphinx-notfound-page
     #   sphinxcontrib-log-cabinet
-sphinx-issues==3.0.1
-    # via -r docs.in
-sphinxcontrib-applehelp==1.0.2
+sphinx-issues==5.0.0
+    # via -r requirements/docs.in
+sphinx-notfound-page==1.0.4
+    # via pallets-sphinx-themes
+sphinxcontrib-applehelp==2.0.0
     # via sphinx
-sphinxcontrib-devhelp==1.0.2
+sphinxcontrib-devhelp==2.0.0
     # via sphinx
-sphinxcontrib-htmlhelp==2.0.0
+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==1.0.3
+    # via -r requirements/docs.in
+sphinxcontrib-qthelp==2.0.0
     # via sphinx
-sphinxcontrib-serializinghtml==1.1.5
+sphinxcontrib-serializinghtml==2.0.0
     # via sphinx
-urllib3==1.26.9
+urllib3==2.2.3
     # via requests
diff --git a/requirements/style.txt b/requirements/style.txt
index 428dbe1..d23aa80 100644
--- a/requirements/style.txt
+++ b/requirements/style.txt
@@ -5,25 +5,21 @@
 #
 #    pip-compile-multi
 #
-cfgv==3.3.1
+cfgv==3.4.0
     # via pre-commit
-distlib==0.3.4
+distlib==0.3.9
     # via virtualenv
-filelock==3.6.0
+filelock==3.16.1
     # via virtualenv
-identify==2.4.12
+identify==2.6.1
     # via pre-commit
-nodeenv==1.6.0
+nodeenv==1.9.1
     # via pre-commit
-platformdirs==2.5.1
+platformdirs==4.3.6
     # via virtualenv
-pre-commit==2.17.0
+pre-commit==4.0.1
     # via -r requirements/style.in
-pyyaml==6.0
+pyyaml==6.0.2
     # via pre-commit
-six==1.16.0
-    # via virtualenv
-toml==0.10.2
-    # via pre-commit
-virtualenv==20.14.0
+virtualenv==20.27.0
     # via pre-commit
diff --git a/requirements/tests.txt b/requirements/tests.txt
index ad11afe..ddec200 100644
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -5,19 +5,11 @@
 #
 #    pip-compile-multi
 #
-attrs==21.4.0
+iniconfig==2.0.0
     # via pytest
-iniconfig==1.1.1
+packaging==24.1
     # via pytest
-packaging==21.3
+pluggy==1.5.0
     # via pytest
-pluggy==1.0.0
-    # via pytest
-py==1.11.0
-    # via pytest
-pyparsing==3.0.7
-    # via packaging
-pytest==7.1.1
+pytest==8.3.3
     # via -r requirements/tests.in
-tomli==2.0.1
-    # via pytest
diff --git a/src/flask_wtf/__init__.py b/src/flask_wtf/__init__.py
index be2649e..c152a76 100644
--- a/src/flask_wtf/__init__.py
+++ b/src/flask_wtf/__init__.py
@@ -5,4 +5,12 @@ from .recaptcha import Recaptcha
 from .recaptcha import RecaptchaField
 from .recaptcha import RecaptchaWidget
 
-__version__ = "1.2.1"
+__version__ = "1.2.2"
+__all__ = [
+    "CSRFProtect",
+    "FlaskForm",
+    "Form",
+    "Recaptcha",
+    "RecaptchaField",
+    "RecaptchaWidget",
+]
diff --git a/src/flask_wtf/file.py b/src/flask_wtf/file.py
index a720dff..98c4577 100644
--- a/src/flask_wtf/file.py
+++ b/src/flask_wtf/file.py
@@ -137,9 +137,8 @@ class FileSize:
                 raise ValidationError(
                     self.message
                     or field.gettext(
-                        "File must be between {min_size} and {max_size} bytes.".format(
-                            min_size=self.min_size, max_size=self.max_size
-                        )
+                        f"File must be between {self.min_size}"
+                        f" and {self.max_size} bytes."
                     )
                 )
 
diff --git a/src/flask_wtf/recaptcha/__init__.py b/src/flask_wtf/recaptcha/__init__.py
index 3100d37..c414a9f 100644
--- a/src/flask_wtf/recaptcha/__init__.py
+++ b/src/flask_wtf/recaptcha/__init__.py
@@ -1,3 +1,5 @@
 from .fields import RecaptchaField
 from .validators import Recaptcha
 from .widgets import RecaptchaWidget
+
+__all__ = ["RecaptchaField", "RecaptchaWidget", "Recaptcha"]
diff --git a/tests/test_file.py b/tests/test_file.py
index 66d261b..87654d4 100644
--- a/tests/test_file.py
+++ b/tests/test_file.py
@@ -67,7 +67,8 @@ def test_file_allowed(form):
 
 def test_file_allowed_uploadset(app, form):
     pytest.importorskip("flask_uploads")
-    from flask_uploads import UploadSet, configure_uploads
+    from flask_uploads import configure_uploads
+    from flask_uploads import UploadSet
 
     app.config["UPLOADS_DEFAULT_DEST"] = "uploads"
     txt = UploadSet("txt", extensions=("txt",))
@@ -114,10 +115,8 @@ def test_file_size_invalid_file_size_fails_validation(
     with path.open("rb") as file:
         f = form(file=FileStorage(file))
         assert not f.validate()
-        assert f.file.errors[
-            0
-        ] == "File must be between {min_size} and {max_size} bytes.".format(
-            min_size=min_size, max_size=max_size
+        assert (
+            f.file.errors[0] == f"File must be between {min_size} and {max_size} bytes."
         )
 
 
@@ -190,7 +189,8 @@ def test_files_allowed(form):
 
 def test_files_allowed_uploadset(app, form):
     pytest.importorskip("flask_uploads")
-    from flask_uploads import UploadSet, configure_uploads
+    from flask_uploads import configure_uploads
+    from flask_uploads import UploadSet
 
     app.config["UPLOADS_DEFAULT_DEST"] = "uploads"
     txt = UploadSet("txt", extensions=("txt",))
@@ -245,10 +245,9 @@ def test_file_size_invalid_file_sizes_fails_validation(
     with path.open("rb") as file:
         f = form(files=[FileStorage(file)])
         assert not f.validate()
-        assert f.files.errors[
-            0
-        ] == "File must be between {min_size} and {max_size} bytes.".format(
-            min_size=min_size, max_size=max_size
+        assert (
+            f.files.errors[0]
+            == f"File must be between {min_size} and {max_size} bytes."
         )
 
 
@@ -257,10 +256,9 @@ def test_files_length(form, min_num=2, max_num=3):
 
     f = form(files=[FileStorage("1")])
     assert not f.validate()
-    assert f.files.errors[
-        0
-    ] == "Field must be between {min_num} and {max_num} characters long.".format(
-        min_num=min_num, max_num=max_num
+    assert (
+        f.files.errors[0]
+        == f"Field must be between {min_num} and {max_num} characters long."
     )
 
     f = form(
diff --git a/tox.ini b/tox.ini
index 019c548..a04654e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 envlist =
-    py3{11,10,9,8},pypy3{10,9}
+    py3{13,12,11,10,9},pypy3{10,9}
     py-{no-babel}
     style
     docs
-- 
GitLab