diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 263d42b3..6d1759a8 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: 3.x - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index e6206667..ca55b4dd 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -10,7 +10,7 @@ jobs: hash: ${{ steps.hash.outputs.hash }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: '3.x' cache: pip @@ -23,7 +23,7 @@ jobs: - name: generate hash id: hash run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: path: ./dist provenance: @@ -33,7 +33,7 @@ jobs: 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 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 with: base64-subjects: ${{ needs.build.outputs.hash }} create-release: @@ -44,7 +44,7 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 - name: create release run: > gh release create --draft --repo ${{ github.repository }} @@ -63,7 +63,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - - uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 + - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 + - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: packages-dir: artifact/ diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index fcfb0a80..f44d905d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -25,7 +25,7 @@ jobs: - {name: Development Versions, python: '3.9', tox: py-dev} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: ${{ matrix.python }} allow-prereleases: true @@ -37,13 +37,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: '3.x' cache: pip cache-dependency-path: requirements*/*.txt - name: cache mypy - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: ./.mypy_cache key: mypy|${{ hashFiles('pyproject.toml') }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7db182a0..299c4584 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.3 + rev: v0.11.2 hooks: - id: ruff - id: ruff-format diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 865c6859..5196b205 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,5 +9,6 @@ python: - method: pip path: . sphinx: + configuration: docs/conf.py builder: dirhtml fail_on_warning: true diff --git a/CHANGES.rst b/CHANGES.rst index d2b4cfe1..0ee27514 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,11 @@ Version 3.1.1 Unreleased - Fix type hint for `cli_runner.invoke`. :issue:`5645` +- ``flask --help`` loads the app and plugins first to make sure all commands + are shown. :issue:5673` +- Mark sans-io base class as being able to handle views that return + ``AsyncIterable``. This is not accurate for Flask, but makes typing easier + for Quart. :pr:`5659` Version 3.1.0 @@ -113,6 +118,7 @@ Released 2023-05-01 - Set ``Vary: Cookie`` header when the session is accessed, modified, or refreshed. - Update Werkzeug requirement to >=2.3.3 to apply recent bug fixes. + :ghsa:`m2qf-hxjv-5gpq` Version 2.3.1 diff --git a/docs/conf.py b/docs/conf.py index a7b8f919..eca4f810 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,6 +26,7 @@ autodoc_preserve_defaults = True extlinks = { "issue": ("https://github.com/pallets/flask/issues/%s", "#%s"), "pr": ("https://github.com/pallets/flask/pull/%s", "#%s"), + "ghsa": ("https://github.com/pallets/flask/security/advisories/GHSA-%s", "GHSA-%s"), } intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index 32fd062b..0f248783 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -99,9 +99,9 @@ to the factory like this: .. code-block:: text - $ flask --app hello:create_app(local_auth=True) run + $ flask --app 'hello:create_app(local_auth=True)' run -Then the ``create_app`` factory in ``myapp`` is called with the keyword +Then the ``create_app`` factory in ``hello`` is called with the keyword argument ``local_auth=True``. See :doc:`/cli` for more detail. Factory Improvements diff --git a/docs/patterns/favicon.rst b/docs/patterns/favicon.rst index 21ea767f..b867854f 100644 --- a/docs/patterns/favicon.rst +++ b/docs/patterns/favicon.rst @@ -24,8 +24,11 @@ the root path of the domain you either need to configure the web server to serve the icon at the root or if you can't do that you're out of luck. If however your application is the root you can simply route a redirect:: - app.add_url_rule('/favicon.ico', - redirect_to=url_for('static', filename='favicon.ico')) + app.add_url_rule( + "/favicon.ico", + endpoint="favicon", + redirect_to=url_for("static", filename="favicon.ico"), + ) If you want to save the extra redirect request you can also write a view using :func:`~flask.send_from_directory`:: diff --git a/docs/patterns/mongoengine.rst b/docs/patterns/mongoengine.rst index 015e7b61..624988e4 100644 --- a/docs/patterns/mongoengine.rst +++ b/docs/patterns/mongoengine.rst @@ -80,7 +80,7 @@ Queries Use the class ``objects`` attribute to make queries. A keyword argument looks for an equal value on the field. :: - bttf = Movies.objects(title="Back To The Future").get_or_404() + bttf = Movie.objects(title="Back To The Future").get_or_404() Query operators may be used by concatenating them with the field name using a double-underscore. ``objects``, and queries returned by diff --git a/docs/web-security.rst b/docs/web-security.rst index f13bb7b8..d742056f 100644 --- a/docs/web-security.rst +++ b/docs/web-security.rst @@ -269,19 +269,6 @@ values (or any values that need secure signatures). .. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute -HTTP Public Key Pinning (HPKP) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This tells the browser to authenticate with the server using only the specific -certificate key to prevent MITM attacks. - -.. warning:: - Be careful when enabling this, as it is very difficult to undo if you set up - or upgrade your key incorrectly. - -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning - - Copy/Paste to Terminal ---------------------- diff --git a/requirements/dev.txt b/requirements/dev.txt index e1056206..2bc16c1e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -12,13 +12,13 @@ asgiref==3.8.1 # via # -r /Users/david/Projects/flask/requirements/tests.txt # -r /Users/david/Projects/flask/requirements/typing.txt -babel==2.16.0 +babel==2.17.0 # via # -r /Users/david/Projects/flask/requirements/docs.txt # sphinx -cachetools==5.5.0 +cachetools==5.5.2 # via tox -certifi==2024.8.30 +certifi==2025.1.31 # via # -r /Users/david/Projects/flask/requirements/docs.txt # requests @@ -30,13 +30,13 @@ cfgv==3.4.0 # via pre-commit chardet==5.2.0 # via tox -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via # -r /Users/david/Projects/flask/requirements/docs.txt # requests colorama==0.4.6 # via tox -cryptography==43.0.3 +cryptography==44.0.2 # via -r /Users/david/Projects/flask/requirements/typing.txt distlib==0.3.9 # via virtualenv @@ -45,11 +45,11 @@ docutils==0.21.2 # -r /Users/david/Projects/flask/requirements/docs.txt # sphinx # sphinx-tabs -filelock==3.16.1 +filelock==3.18.0 # via # tox # virtualenv -identify==2.6.2 +identify==2.6.9 # via pre-commit idna==3.10 # via @@ -59,12 +59,12 @@ imagesize==1.4.1 # via # -r /Users/david/Projects/flask/requirements/docs.txt # sphinx -iniconfig==2.0.0 +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.4 +jinja2==3.1.6 # via # -r /Users/david/Projects/flask/requirements/docs.txt # sphinx @@ -72,7 +72,7 @@ markupsafe==3.0.2 # via # -r /Users/david/Projects/flask/requirements/docs.txt # jinja2 -mypy==1.13.0 +mypy==1.15.0 # via -r /Users/david/Projects/flask/requirements/typing.txt mypy-extensions==1.0.0 # via @@ -95,7 +95,7 @@ packaging==24.2 # tox pallets-sphinx-themes==2.3.0 # via -r /Users/david/Projects/flask/requirements/docs.txt -platformdirs==4.3.6 +platformdirs==4.3.7 # via # tox # virtualenv @@ -105,26 +105,26 @@ pluggy==1.5.0 # -r /Users/david/Projects/flask/requirements/typing.txt # pytest # tox -pre-commit==4.0.1 +pre-commit==4.2.0 # via -r dev.in pycparser==2.22 # via # -r /Users/david/Projects/flask/requirements/typing.txt # cffi -pygments==2.18.0 +pygments==2.19.1 # via # -r /Users/david/Projects/flask/requirements/docs.txt # sphinx # sphinx-tabs -pyproject-api==1.8.0 +pyproject-api==1.9.0 # via tox -pyright==1.1.389 +pyright==1.1.398 # via -r /Users/david/Projects/flask/requirements/typing.txt -pytest==8.3.3 +pytest==8.3.5 # via # -r /Users/david/Projects/flask/requirements/tests.txt # -r /Users/david/Projects/flask/requirements/typing.txt -python-dotenv==1.0.1 +python-dotenv==1.1.0 # via # -r /Users/david/Projects/flask/requirements/tests.txt # -r /Users/david/Projects/flask/requirements/typing.txt @@ -134,18 +134,22 @@ 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.1.3 +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.0.4 +sphinx-notfound-page==1.1.0 # via # -r /Users/david/Projects/flask/requirements/docs.txt # pallets-sphinx-themes @@ -177,22 +181,22 @@ sphinxcontrib-serializinghtml==2.0.0 # via # -r /Users/david/Projects/flask/requirements/docs.txt # sphinx -tox==4.23.2 +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.12.2 +typing-extensions==4.13.0 # via # -r /Users/david/Projects/flask/requirements/typing.txt # mypy # pyright -urllib3==2.2.3 +urllib3==2.3.0 # via # -r /Users/david/Projects/flask/requirements/docs.txt # requests -virtualenv==20.27.1 +virtualenv==20.29.3 # via # pre-commit # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index 47c22099..ac300f44 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -6,11 +6,11 @@ # alabaster==1.0.0 # via sphinx -babel==2.16.0 +babel==2.17.0 # via sphinx -certifi==2024.8.30 +certifi==2025.1.31 # via requests -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via requests docutils==0.21.2 # via @@ -20,7 +20,7 @@ idna==3.10 # via requests imagesize==1.4.1 # via sphinx -jinja2==3.1.4 +jinja2==3.1.6 # via sphinx markupsafe==3.0.2 # via jinja2 @@ -30,22 +30,24 @@ packaging==24.2 # sphinx pallets-sphinx-themes==2.3.0 # via -r docs.in -pygments==2.18.0 +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.1.3 +sphinx==8.2.3 # via # -r docs.in # pallets-sphinx-themes # sphinx-notfound-page # sphinx-tabs # sphinxcontrib-log-cabinet -sphinx-notfound-page==1.0.4 +sphinx-notfound-page==1.1.0 # via pallets-sphinx-themes sphinx-tabs==3.4.7 # via -r docs.in @@ -63,5 +65,5 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -urllib3==2.2.3 +urllib3==2.3.0 # via requests diff --git a/requirements/tests.txt b/requirements/tests.txt index cc8b9a2e..9457c2b7 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -6,13 +6,13 @@ # asgiref==3.8.1 # via -r tests.in -iniconfig==2.0.0 +iniconfig==2.1.0 # via pytest packaging==24.2 # via pytest pluggy==1.5.0 # via pytest -pytest==8.3.3 +pytest==8.3.5 # via -r tests.in -python-dotenv==1.0.1 +python-dotenv==1.1.0 # via -r tests.in diff --git a/requirements/typing.txt b/requirements/typing.txt index 7eddb5f1..cb9a05f9 100644 --- a/requirements/typing.txt +++ b/requirements/typing.txt @@ -8,11 +8,11 @@ asgiref==3.8.1 # via -r typing.in cffi==1.17.1 # via cryptography -cryptography==43.0.3 +cryptography==44.0.2 # via -r typing.in -iniconfig==2.0.0 +iniconfig==2.1.0 # via pytest -mypy==1.13.0 +mypy==1.15.0 # via -r typing.in mypy-extensions==1.0.0 # via mypy @@ -24,17 +24,17 @@ pluggy==1.5.0 # via pytest pycparser==2.22 # via cffi -pyright==1.1.389 +pyright==1.1.398 # via -r typing.in -pytest==8.3.3 +pytest==8.3.5 # via -r typing.in -python-dotenv==1.0.1 +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.12.2 +typing-extensions==4.13.0 # via # mypy # pyright diff --git a/src/flask/app.py b/src/flask/app.py index 905b2477..d2743c41 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -265,9 +265,9 @@ class Flask(App): # For one, it might be created while the server is running (e.g. during # development). Also, Google App Engine stores static files somewhere if self.has_static_folder: - assert ( - bool(static_host) == host_matching - ), "Invalid static_host/host_matching combination" + assert bool(static_host) == host_matching, ( + "Invalid static_host/host_matching combination" + ) # Use a weakref to avoid creating a reference cycle between the app # and the view function (see #3761). self_ref = weakref.ref(self) diff --git a/src/flask/cli.py b/src/flask/cli.py index dd03f3c5..ed11f256 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -684,7 +684,9 @@ class FlaskGroup(AppGroup): return super().make_context(info_name, args, parent=parent, **extra) def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]: - if not args and self.no_args_is_help: + if (not args and self.no_args_is_help) or ( + len(args) == 1 and args[0] in self.get_help_option_names(ctx) + ): # Attempt to load --env-file and --app early in case they # were given as env vars. Otherwise no_args_is_help will not # see commands from app.cli. diff --git a/src/flask/testing.py b/src/flask/testing.py index a62c4836..da156cc1 100644 --- a/src/flask/testing.py +++ b/src/flask/testing.py @@ -58,9 +58,9 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder): ) -> None: assert not (base_url or subdomain or url_scheme) or ( base_url is not None - ) != bool( - subdomain or url_scheme - ), 'Cannot pass "subdomain" or "url_scheme" with "base_url".' + ) != bool(subdomain or url_scheme), ( + 'Cannot pass "subdomain" or "url_scheme" with "base_url".' + ) if base_url is None: http_host = app.config.get("SERVER_NAME") or "localhost" diff --git a/src/flask/typing.py b/src/flask/typing.py index e7234e96..6b70c409 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -1,5 +1,6 @@ from __future__ import annotations +import collections.abc as cabc import typing as t if t.TYPE_CHECKING: # pragma: no cover @@ -17,6 +18,8 @@ ResponseValue = t.Union[ t.Mapping[str, t.Any], t.Iterator[str], t.Iterator[bytes], + cabc.AsyncIterable[str], # for Quart, until App is generic. + cabc.AsyncIterable[bytes], ] # the possible types for an individual HTTP header diff --git a/tests/conftest.py b/tests/conftest.py index 58cf85d8..214f5203 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ import os -import pkgutil import sys import pytest @@ -96,37 +95,6 @@ def leak_detector(): assert leaks == [] -@pytest.fixture(params=(True, False)) -def limit_loader(request, monkeypatch): - """Patch pkgutil.get_loader to give loader without get_filename or archive. - - This provides for tests where a system has custom loaders, e.g. Google App - Engine's HardenedModulesHook, which have neither the `get_filename` method - nor the `archive` attribute. - - This fixture will run the testcase twice, once with and once without the - limitation/mock. - """ - if not request.param: - return - - class LimitedLoader: - def __init__(self, loader): - self.loader = loader - - def __getattr__(self, name): - if name in {"archive", "get_filename"}: - raise AttributeError(f"Mocking a loader which does not have {name!r}.") - return getattr(self.loader, name) - - old_get_loader = pkgutil.get_loader - - def get_loader(*args, **kwargs): - return LimitedLoader(old_get_loader(*args, **kwargs)) - - monkeypatch.setattr(pkgutil, "get_loader", get_loader) - - @pytest.fixture def modules_tmp_path(tmp_path, monkeypatch): """A temporary directory added to sys.path.""" diff --git a/tests/test_instance_config.py b/tests/test_instance_config.py index 1918bd99..835a8784 100644 --- a/tests/test_instance_config.py +++ b/tests/test_instance_config.py @@ -63,7 +63,7 @@ def test_uninstalled_namespace_paths(tmp_path, monkeypatch, purge_module): def test_installed_module_paths( - modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages, limit_loader + modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages ): (site_packages / "site_app.py").write_text( "import flask\napp = flask.Flask(__name__)\n" @@ -78,7 +78,7 @@ def test_installed_module_paths( def test_installed_package_paths( - limit_loader, modules_tmp_path, modules_tmp_path_prefix, purge_module, monkeypatch + modules_tmp_path, modules_tmp_path_prefix, purge_module, monkeypatch ): installed_path = modules_tmp_path / "path" installed_path.mkdir() @@ -97,7 +97,7 @@ def test_installed_package_paths( def test_prefix_package_paths( - limit_loader, modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages + modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages ): app = site_packages / "site_package" app.mkdir()