diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..45198266 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "name": "pallets/flask", + "image": "mcr.microsoft.com/devcontainers/python:3", + "customizations": { + "vscode": { + "settings": { + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "python.terminal.activateEnvInCurrentTerminal": true, + "python.terminal.launchArgs": [ + "-X", + "dev" + ] + } + } + }, + "onCreateCommand": ".devcontainer/on-create-command.sh" +} diff --git a/.devcontainer/on-create-command.sh b/.devcontainer/on-create-command.sh new file mode 100755 index 00000000..eaebea61 --- /dev/null +++ b/.devcontainer/on-create-command.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +python3 -m venv --upgrade-deps .venv +. .venv/bin/activate +pip install -r requirements/dev.txt +pip install -e . +pre-commit install --install-hooks diff --git a/.editorconfig b/.editorconfig index e32c8029..2ff985a6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,5 +9,5 @@ end_of_line = lf charset = utf-8 max_line_length = 88 -[*.{yml,yaml,json,js,css,html}] +[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index c2a15eee..0917c797 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -5,7 +5,7 @@ about: Report a bug in Flask (not other projects which depend on Flask) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index abe39156..a8f9f0b7 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,11 @@ blank_issues_enabled: false contact_links: - name: Security issue - url: security@palletsprojects.com - about: Do not report security issues publicly. Email our security contact. - - name: Questions - url: https://stackoverflow.com/questions/tagged/flask?tab=Frequent - about: Search for and ask questions about your code on Stack Overflow. - - name: Questions and discussions + url: https://github.com/pallets/flask/security/advisories/new + about: Do not report security issues publicly. Create a private advisory. + - name: Questions on GitHub Discussions + url: https://github.com/pallets/flask/discussions/ + about: Ask questions about your own code on the Discussions tab. + - name: Questions on Discord url: https://discord.gg/pallets - about: Discuss questions about your code on our Discord chat. + about: Ask questions about your own code on our Discord chat. diff --git a/.github/SECURITY.md b/.github/SECURITY.md deleted file mode 100644 index fcfac71b..00000000 --- a/.github/SECURITY.md +++ /dev/null @@ -1,19 +0,0 @@ -# Security Policy - -If you believe you have identified a security issue with a Pallets -project, **do not open a public issue**. To responsibly report a -security issue, please email security@palletsprojects.com. A security -team member will contact you acknowledging the report and how to -continue. - -Be sure to include as much detail as necessary in your report. As with -reporting normal issues, a minimal reproducible example will help the -maintainers address the issue faster. If you are able, you may also -include a fix for the issue generated with `git format-patch`. - -The current and previous release will receive security patches, with -older versions evaluated based on usage information and severity. - -After fixing an issue, we will make a security release along with an -announcement on our blog. We may obtain a CVE id as well. You may -include a name and link if you would like to be credited for the report. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 90f94bc3..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: 2 -updates: -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - day: "monday" - time: "16:00" - timezone: "UTC" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 29fd35f8..eb124d25 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,7 @@ -- fixes # +fixes # +--> - -Checklist: - -- [ ] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change. -- [ ] Add or update relevant docs, in the docs folder and in code. -- [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue. -- [ ] Add `.. versionchanged::` entries in any relevant code docs. -- [ ] Run `pre-commit` hooks and fix any issues. -- [ ] Run `pytest` and `tox`, no tests failed. diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml index b4f76338..533151a8 100644 --- a/.github/workflows/lock.yaml +++ b/.github/workflows/lock.yaml @@ -1,15 +1,26 @@ -name: 'Lock threads' +name: Lock inactive closed issues +# Lock closed issues that have not received any further activity for two weeks. +# This does not close open issues, only humans may do that. It is easier to +# respond to new issues with fresh examples rather than continuing discussions +# on old issues. on: schedule: - cron: '0 0 * * *' - +permissions: {} +concurrency: + group: lock + cancel-in-progress: true jobs: lock: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + discussions: write steps: - - uses: dessant/lock-threads@v3 + - uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0 with: - github-token: ${{ github.token }} issue-inactive-days: 14 pr-inactive-days: 14 + discussion-inactive-days: 14 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 00000000..eff10995 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,29 @@ +name: pre-commit +on: + pull_request: + push: + branches: [main, stable] +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + with: + enable-cache: true + prune-cache: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + id: setup-python + with: + python-version-file: pyproject.toml + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }} + - run: uv run --locked --no-default-groups --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..0c4f301a --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,62 @@ +name: Publish +on: + push: + tags: ['*'] +permissions: {} +concurrency: + group: publish-${{ github.event.push.ref }} + cancel-in-progress: true +jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + with: + enable-cache: false + prune-cache: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version-file: pyproject.toml + - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV + - run: uv build + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + id: upload-artifact + with: + name: dist + path: dist/ + if-no-files-found: error + create-release: + needs: [build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + artifact-ids: ${{ needs.build.outputs.artifact-id }} + path: dist/ + - name: create release + run: gh release create --draft --repo ${GITHUB_REPOSITORY} ${GITHUB_REF_NAME} dist/* + env: + GH_TOKEN: ${{ github.token }} + publish-pypi: + needs: [build] + environment: + name: publish + url: https://pypi.org/project/Flask/${{ github.ref_name }} + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + artifact-ids: ${{ needs.build.outputs.artifact-id }} + path: dist/ + - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + with: + packages-dir: "dist/" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 32b60de4..97064c8c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,52 +1,63 @@ name: Tests on: - push: - branches: - - main - - '*.x' - paths-ignore: - - 'docs/**' - - '*.md' - - '*.rst' pull_request: - branches: - - main - - '*.x' - paths-ignore: - - 'docs/**' - - '*.md' - - '*.rst' + paths-ignore: ['docs/**', 'README.md'] + push: + branches: [main, stable] + paths-ignore: ['docs/**', 'README.md'] +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: tests: - name: ${{ matrix.name }} - runs-on: ${{ matrix.os }} + name: ${{ matrix.name || matrix.python }} + runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: include: - - {name: Linux, python: '3.10', os: ubuntu-latest, tox: py310} - - {name: Windows, python: '3.10', os: windows-latest, tox: py310} - - {name: Mac, python: '3.10', os: macos-latest, tox: py310} - - {name: '3.11-dev', python: '3.11-dev', os: ubuntu-latest, tox: py311} - - {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39} - - {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38} - - {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37} - - {name: '3.6', python: '3.6', os: ubuntu-latest, tox: py36} - - {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37} - - {name: 'Pallets Minimum Versions', python: '3.10', os: ubuntu-latest, tox: py-min} - - {name: 'Pallets Development Versions', python: '3.7', os: ubuntu-latest, tox: py-dev} - - {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing} + - {python: '3.14'} + - {python: '3.14t'} + - {name: Windows, python: '3.14', os: windows-latest} + - {name: Mac, python: '3.14', os: macos-latest} + - {python: '3.13'} + - {python: '3.12'} + - {python: '3.11'} + - {python: '3.10'} + - {name: PyPy, python: 'pypy-3.11', tox: pypy3.11} + - {name: Minimum Versions, python: '3.14', tox: tests-min} + - {name: Development Versions, python: '3.10', tox: tests-dev} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + with: + enable-cache: true + prune-cache: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python }} - cache: 'pip' - cache-dependency-path: 'requirements/*.txt' - - name: update pip - run: | - pip install -U wheel - pip install -U setuptools - python -m pip install -U pip - - run: pip install tox - - run: tox -e ${{ matrix.tox }} + - run: uv run --locked --no-default-groups --group dev tox run + env: + TOX_ENV: ${{ matrix.tox || format('py{0}', matrix.python) }} + typing: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + with: + enable-cache: true + prune-cache: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version-file: pyproject.toml + - name: cache mypy + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: ./.mypy_cache + key: mypy|${{ hashFiles('pyproject.toml') }} + - run: uv run --locked --no-default-groups --group dev tox run -e typing diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml new file mode 100644 index 00000000..04082427 --- /dev/null +++ b/.github/workflows/zizmor.yaml @@ -0,0 +1,22 @@ +name: GitHub Actions security analysis with zizmor +on: + pull_request: + paths: ["**/*.yaml?"] + push: + branches: [main, stable] + paths: ["**/*.yaml?"] +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + zizmor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 + with: + advanced-security: false + annotations: true diff --git a/.gitignore b/.gitignore index e50a290e..8441e5a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,8 @@ -.DS_Store -.env -.flaskenv -*.pyc -*.pyo -env/ -venv/ -.venv/ -env* -dist/ -build/ -*.egg -*.egg-info/ -_mailinglist -.tox/ -.cache/ -.pytest_cache/ .idea/ -docs/_build/ -.vscode - -# Coverage reports +.vscode/ +__pycache__/ +dist/ +.coverage* htmlcov/ -.coverage -.coverage.* -*,cover +.tox/ +docs/_build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b2c6685..5d1c89cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,38 +1,23 @@ -ci: - autoupdate_branch: "2.0.x" - autoupdate_schedule: monthly repos: - - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 5e2fb545eba1ea9dc051f6f962d52fe8f76a9794 # frozen: v0.15.13 hooks: - - id: pyupgrade - args: ["--py36-plus"] - - repo: https://github.com/asottile/reorder_python_imports - rev: v2.7.1 + - id: ruff-check + - id: ruff-format + - repo: https://github.com/astral-sh/uv-pre-commit + rev: fa60a193803535a9e2accdb3ca4b1b584b1150cb # frozen: 0.11.15 hooks: - - id: reorder-python-imports - name: Reorder Python imports (src, tests) - files: "^(?!examples/)" - args: ["--application-directories", "src"] - additional_dependencies: ["setuptools>60.9"] - - repo: https://github.com/psf/black - rev: 22.1.0 + - id: uv-lock + - repo: https://github.com/codespell-project/codespell + rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2 hooks: - - id: black - - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear - - flake8-implicit-str-concat - - repo: https://github.com/peterdemin/pip-compile-multi - rev: v2.4.3 - hooks: - - id: pip-compile-multi-verify + - id: codespell + args: ['--write-changes'] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.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 346900b2..acbd83f9 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,13 +1,10 @@ version: 2 build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.10" -python: - install: - - requirements: requirements/docs.txt - - method: pip - path: . -sphinx: - builder: dirhtml - fail_on_warning: true + python: '3.13' + commands: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html diff --git a/CHANGES.rst b/CHANGES.rst index 2f32a551..232b144a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,488 @@ -.. currentmodule:: flask +Version 3.2.0 +------------- + +Unreleased + +- Drop support for Python 3.9. :pr:`5730` +- Remove previously deprecated code: ``__version__``. :pr:`5648` +- ``RequestContext`` has merged with ``AppContext``. ``RequestContext`` is now + a deprecated alias. If an app context is already pushed, it is not reused + when dispatching a request. This greatly simplifies the internal code for tracking + the active context. :issue:`5639` +- Many ``Flask`` methods involved in request dispatch now take the current + ``AppContext`` as the first parameter, instead of using the proxy objects. + If subclasses were overriding these methods, the old signature is detected, + shows a deprecation warning, and will continue to work during the + deprecation period. :issue:`5815` +- All teardown callbacks are called, even if any raise an error. :pr:`5928` +- The ``should_ignore_error`` is deprecated. Handle errors as needed in + teardown handlers instead. :issue:`5816` +- ``template_filter``, ``template_test``, and ``template_global`` decorators + can be used without parentheses. :issue:`5729` +- ``redirect`` returns a ``303`` status code by default instead of ``302``. + This tells the client to always switch to ``GET``, rather than only + switching ``POST`` to ``GET``. This preserves the current behavior of + ``GET`` and ``POST`` redirects, and is also correct for frontend libraries + such as HTMX. :issue:`5895` +- ``provide_automatic_options=True`` can be used to enable it for a view when + it's disabled in config. Previously, only disabling worked. :issue:`5916` +- ``Flask.select_jinja_autoescape`` uses case-insensitive comparison instead + of only lower case file extensions. :pr:`6012` + + +Version 3.1.3 +------------- + +Released 2026-02-18 + +- The session is marked as accessed for operations that only access the keys + but not the values, such as ``in`` and ``len``. :ghsa:`68rp-wp8r-4726` + + +Version 3.1.2 +------------- + +Released 2025-08-19 + +- ``stream_with_context`` does not fail inside async views. :issue:`5774` +- When using ``follow_redirects`` in the test client, the final state + of ``session`` is correct. :issue:`5786` +- Relax type hint for passing bytes IO to ``send_file``. :issue:`5776` + + +Version 3.1.1 +------------- + +Released 2025-05-13 + +- Fix signing key selection order when key rotation is enabled via + ``SECRET_KEY_FALLBACKS``. :ghsa:`4grg-w6v8-c28g` +- 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 +------------- + +Released 2024-11-13 + +- Drop support for Python 3.8. :pr:`5623` +- Update minimum dependency versions to latest feature releases. + Werkzeug >= 3.1, ItsDangerous >= 2.2, Blinker >= 1.9. :pr:`5624,5633` +- Provide a configuration option to control automatic option + responses. :pr:`5496` +- ``Flask.open_resource``/``open_instance_resource`` and + ``Blueprint.open_resource`` take an ``encoding`` parameter to use when + opening in text mode. It defaults to ``utf-8``. :issue:`5504` +- ``Request.max_content_length`` can be customized per-request instead of only + through the ``MAX_CONTENT_LENGTH`` config. Added + ``MAX_FORM_MEMORY_SIZE`` and ``MAX_FORM_PARTS`` config. Added documentation + about resource limits to the security page. :issue:`5625` +- Add support for the ``Partitioned`` cookie attribute (CHIPS), with the + ``SESSION_COOKIE_PARTITIONED`` config. :issue:`5472` +- ``-e path`` takes precedence over default ``.env`` and ``.flaskenv`` files. + ``load_dotenv`` loads default files in addition to a path unless + ``load_defaults=False`` is passed. :issue:`5628` +- Support key rotation with the ``SECRET_KEY_FALLBACKS`` config, a list of old + secret keys that can still be used for unsigning. Extensions will need to + add support. :issue:`5621` +- Fix how setting ``host_matching=True`` or ``subdomain_matching=False`` + interacts with ``SERVER_NAME``. Setting ``SERVER_NAME`` no longer restricts + requests to only that domain. :issue:`5553` +- ``Request.trusted_hosts`` is checked during routing, and can be set through + the ``TRUSTED_HOSTS`` config. :issue:`5636` + + +Version 3.0.3 +------------- + +Released 2024-04-07 + +- The default ``hashlib.sha1`` may not be available in FIPS builds. Don't + access it at import time so the developer has time to change the default. + :issue:`5448` +- Don't initialize the ``cli`` attribute in the sansio scaffold, but rather in + the ``Flask`` concrete class. :pr:`5270` + + +Version 3.0.2 +------------- + +Released 2024-02-03 + +- Correct type for ``jinja_loader`` property. :issue:`5388` +- Fix error with ``--extra-files`` and ``--exclude-patterns`` CLI options. + :issue:`5391` + + +Version 3.0.1 +------------- + +Released 2024-01-18 + +- Correct type for ``path`` argument to ``send_file``. :issue:`5336` +- Fix a typo in an error message for the ``flask run --key`` option. :pr:`5344` +- Session data is untagged without relying on the built-in ``json.loads`` + ``object_hook``. This allows other JSON providers that don't implement that. + :issue:`5381` +- Address more type findings when using mypy strict mode. :pr:`5383` + + +Version 3.0.0 +------------- + +Released 2023-09-30 + +- Remove previously deprecated code. :pr:`5223` +- Deprecate the ``__version__`` attribute. Use feature detection, or + ``importlib.metadata.version("flask")``, instead. :issue:`5230` +- Restructure the code such that the Flask (app) and Blueprint + classes have Sans-IO bases. :pr:`5127` +- Allow self as an argument to url_for. :pr:`5264` +- Require Werkzeug >= 3.0.0. + + +Version 2.3.3 +------------- + +Released 2023-08-21 + +- Python 3.12 compatibility. +- Require Werkzeug >= 2.3.7. +- Use ``flit_core`` instead of ``setuptools`` as build backend. +- Refactor how an app's root and instance paths are determined. :issue:`5160` + + +Version 2.3.2 +------------- + +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 +------------- + +Released 2023-04-25 + +- Restore deprecated ``from flask import Markup``. :issue:`5084` + + +Version 2.3.0 +------------- + +Released 2023-04-25 + +- Drop support for Python 3.7. :pr:`5072` +- Update minimum requirements to the latest versions: Werkzeug>=2.3.0, Jinja2>3.1.2, + itsdangerous>=2.1.2, click>=8.1.3. +- Remove previously deprecated code. :pr:`4995` + + - The ``push`` and ``pop`` methods of the deprecated ``_app_ctx_stack`` and + ``_request_ctx_stack`` objects are removed. ``top`` still exists to give + extensions more time to update, but it will be removed. + - The ``FLASK_ENV`` environment variable, ``ENV`` config key, and ``app.env`` + property are removed. + - The ``session_cookie_name``, ``send_file_max_age_default``, ``use_x_sendfile``, + ``propagate_exceptions``, and ``templates_auto_reload`` properties on ``app`` + are removed. + - The ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and + ``JSONIFY_PRETTYPRINT_REGULAR`` config keys are removed. + - The ``app.before_first_request`` and ``bp.before_app_first_request`` decorators + are removed. + - ``json_encoder`` and ``json_decoder`` attributes on app and blueprint, and the + corresponding ``json.JSONEncoder`` and ``JSONDecoder`` classes, are removed. + - The ``json.htmlsafe_dumps`` and ``htmlsafe_dump`` functions are removed. + - Calling setup methods on blueprints after registration is an error instead of a + warning. :pr:`4997` + +- Importing ``escape`` and ``Markup`` from ``flask`` is deprecated. Import them + directly from ``markupsafe`` instead. :pr:`4996` +- The ``app.got_first_request`` property is deprecated. :pr:`4997` +- The ``locked_cached_property`` decorator is deprecated. Use a lock inside the + decorated function if locking is needed. :issue:`4993` +- Signals are always available. ``blinker>=1.6.2`` is a required dependency. The + ``signals_available`` attribute is deprecated. :issue:`5056` +- Signals support ``async`` subscriber functions. :pr:`5049` +- Remove uses of locks that could cause requests to block each other very briefly. + :issue:`4993` +- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. + :pr:`4947` +- Ensure subdomains are applied with nested blueprints. :issue:`4834` +- ``config.from_file`` can use ``text=False`` to indicate that the parser wants a + binary file instead. :issue:`4989` +- If a blueprint is created with an empty name it raises a ``ValueError``. + :issue:`5010` +- ``SESSION_COOKIE_DOMAIN`` does not fall back to ``SERVER_NAME``. The default is not + to set the domain, which modern browsers interpret as an exact match rather than + a subdomain match. Warnings about ``localhost`` and IP addresses are also removed. + :issue:`5051` +- The ``routes`` command shows each rule's ``subdomain`` or ``host`` when domain + matching is in use. :issue:`5004` +- Use postponed evaluation of annotations. :pr:`5071` + + +Version 2.2.5 +------------- + +Released 2023-05-02 + +- Update for compatibility with Werkzeug 2.3.3. +- Set ``Vary: Cookie`` header when the session is accessed, modified, or refreshed. + + +Version 2.2.4 +------------- + +Released 2023-04-25 + +- Update for compatibility with Werkzeug 2.3. + + +Version 2.2.3 +------------- + +Released 2023-02-15 + +- Autoescape is enabled by default for ``.svg`` template files. :issue:`4831` +- Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892` +- Add ``--debug`` option to the ``flask run`` command. :issue:`4777` + + +Version 2.2.2 +------------- + +Released 2022-08-08 + +- Update Werkzeug dependency to >= 2.2.2. This includes fixes related + to the new faster router, header parsing, and the development + server. :pr:`4754` +- Fix the default value for ``app.env`` to be ``"production"``. This + attribute remains deprecated. :issue:`4740` + + +Version 2.2.1 +------------- + +Released 2022-08-03 + +- Setting or accessing ``json_encoder`` or ``json_decoder`` raises a + deprecation warning. :issue:`4732` + + +Version 2.2.0 +------------- + +Released 2022-08-01 + +- Remove previously deprecated code. :pr:`4667` + + - Old names for some ``send_file`` parameters have been removed. + ``download_name`` replaces ``attachment_filename``, ``max_age`` + replaces ``cache_timeout``, and ``etag`` replaces ``add_etags``. + Additionally, ``path`` replaces ``filename`` in + ``send_from_directory``. + - The ``RequestContext.g`` property returning ``AppContext.g`` is + removed. + +- Update Werkzeug dependency to >= 2.2. +- The app and request contexts are managed using Python context vars + directly rather than Werkzeug's ``LocalStack``. This should result + in better performance and memory use. :pr:`4682` + + - Extension maintainers, be aware that ``_app_ctx_stack.top`` + and ``_request_ctx_stack.top`` are deprecated. Store data on + ``g`` instead using a unique prefix, like + ``g._extension_name_attr``. + +- The ``FLASK_ENV`` environment variable and ``app.env`` attribute are + deprecated, removing the distinction between development and debug + mode. Debug mode should be controlled directly using the ``--debug`` + option or ``app.run(debug=True)``. :issue:`4714` +- Some attributes that proxied config keys on ``app`` are deprecated: + ``session_cookie_name``, ``send_file_max_age_default``, + ``use_x_sendfile``, ``propagate_exceptions``, and + ``templates_auto_reload``. Use the relevant config keys instead. + :issue:`4716` +- Add new customization points to the ``Flask`` app object for many + previously global behaviors. + + - ``flask.url_for`` will call ``app.url_for``. :issue:`4568` + - ``flask.abort`` will call ``app.aborter``. + ``Flask.aborter_class`` and ``Flask.make_aborter`` can be used + to customize this aborter. :issue:`4567` + - ``flask.redirect`` will call ``app.redirect``. :issue:`4569` + - ``flask.json`` is an instance of ``JSONProvider``. A different + provider can be set to use a different JSON library. + ``flask.jsonify`` will call ``app.json.response``, other + functions in ``flask.json`` will call corresponding functions in + ``app.json``. :pr:`4692` + +- JSON configuration is moved to attributes on the default + ``app.json`` provider. ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, + ``JSONIFY_MIMETYPE``, and ``JSONIFY_PRETTYPRINT_REGULAR`` are + deprecated. :pr:`4692` +- Setting custom ``json_encoder`` and ``json_decoder`` classes on the + app or a blueprint, and the corresponding ``json.JSONEncoder`` and + ``JSONDecoder`` classes, are deprecated. JSON behavior can now be + overridden using the ``app.json`` provider interface. :pr:`4692` +- ``json.htmlsafe_dumps`` and ``json.htmlsafe_dump`` are deprecated, + the function is built-in to Jinja now. :pr:`4692` +- Refactor ``register_error_handler`` to consolidate error checking. + Rewrite some error messages to be more consistent. :issue:`4559` +- Use Blueprint decorators and functions intended for setup after + registering the blueprint will show a warning. In the next version, + this will become an error just like the application setup methods. + :issue:`4571` +- ``before_first_request`` is deprecated. Run setup code when creating + the application instead. :issue:`4605` +- Added the ``View.init_every_request`` class attribute. If a view + subclass sets this to ``False``, the view will not create a new + instance on every request. :issue:`2520`. +- A ``flask.cli.FlaskGroup`` Click group can be nested as a + sub-command in a custom CLI. :issue:`3263` +- Add ``--app`` and ``--debug`` options to the ``flask`` CLI, instead + of requiring that they are set through environment variables. + :issue:`2836` +- Add ``--env-file`` option to the ``flask`` CLI. This allows + specifying a dotenv file to load in addition to ``.env`` and + ``.flaskenv``. :issue:`3108` +- It is no longer required to decorate custom CLI commands on + ``app.cli`` or ``blueprint.cli`` with ``@with_appcontext``, an app + context will already be active at that point. :issue:`2410` +- ``SessionInterface.get_expiration_time`` uses a timezone-aware + value. :pr:`4645` +- View functions can return generators directly instead of wrapping + them in a ``Response``. :pr:`4629` +- Add ``stream_template`` and ``stream_template_string`` functions to + render a template as a stream of pieces. :pr:`4629` +- A new implementation of context preservation during debugging and + testing. :pr:`4666` + + - ``request``, ``g``, and other context-locals point to the + correct data when running code in the interactive debugger + console. :issue:`2836` + - Teardown functions are always run at the end of the request, + even if the context is preserved. They are also run after the + preserved context is popped. + - ``stream_with_context`` preserves context separately from a + ``with client`` block. It will be cleaned up when + ``response.get_data()`` or ``response.close()`` is called. + +- Allow returning a list from a view function, to convert it to a + JSON response like a dict is. :issue:`4672` +- When type checking, allow ``TypedDict`` to be returned from view + functions. :pr:`4695` +- Remove the ``--eager-loading/--lazy-loading`` options from the + ``flask run`` command. The app is always eager loaded the first + time, then lazily loaded in the reloader. The reloader always prints + errors immediately but continues serving. Remove the internal + ``DispatchingApp`` middleware used by the previous implementation. + :issue:`4715` + + +Version 2.1.3 +------------- + +Released 2022-07-13 + +- Inline some optional imports that are only used for certain CLI + commands. :pr:`4606` +- Relax type annotation for ``after_request`` functions. :issue:`4600` +- ``instance_path`` for namespace packages uses the path closest to + the imported submodule. :issue:`4610` +- Clearer error message when ``render_template`` and + ``render_template_string`` are used outside an application context. + :pr:`4693` + + +Version 2.1.2 +------------- + +Released 2022-04-28 + +- Fix type annotation for ``json.loads``, it accepts str or bytes. + :issue:`4519` +- The ``--cert`` and ``--key`` options on ``flask run`` can be given + in either order. :issue:`4459` + + +Version 2.1.1 +------------- + +Released on 2022-03-30 + +- Set the minimum required version of importlib_metadata to 3.6.0, + which is required on Python < 3.10. :issue:`4502` + + +Version 2.1.0 +------------- + +Released 2022-03-28 + +- Drop support for Python 3.6. :pr:`4335` +- Update Click dependency to >= 8.0. :pr:`4008` +- Remove previously deprecated code. :pr:`4337` + + - The CLI does not pass ``script_info`` to app factory functions. + - ``config.from_json`` is replaced by + ``config.from_file(name, load=json.load)``. + - ``json`` functions no longer take an ``encoding`` parameter. + - ``safe_join`` is removed, use ``werkzeug.utils.safe_join`` + instead. + - ``total_seconds`` is removed, use ``timedelta.total_seconds`` + instead. + - The same blueprint cannot be registered with the same name. Use + ``name=`` when registering to specify a unique name. + - The test client's ``as_tuple`` parameter is removed. Use + ``response.request.environ`` instead. :pr:`4417` + +- Some parameters in ``send_file`` and ``send_from_directory`` were + renamed in 2.0. The deprecation period for the old names is extended + to 2.2. Be sure to test with deprecation warnings visible. + + - ``attachment_filename`` is renamed to ``download_name``. + - ``cache_timeout`` is renamed to ``max_age``. + - ``add_etags`` is renamed to ``etag``. + - ``filename`` is renamed to ``path``. + +- The ``RequestContext.g`` property is deprecated. Use ``g`` directly + or ``AppContext.g`` instead. :issue:`3898` +- ``copy_current_request_context`` can decorate async functions. + :pr:`4303` +- The CLI uses ``importlib.metadata`` instead of ``pkg_resources`` to + load command entry points. :issue:`4419` +- Overriding ``FlaskClient.open`` will not cause an error on redirect. + :issue:`3396` +- Add an ``--exclude-patterns`` option to the ``flask run`` CLI + command to specify patterns that will be ignored by the reloader. + :issue:`4188` +- When using lazy loading (the default with the debugger), the Click + context from the ``flask run`` command remains available in the + loader thread. :issue:`4460` +- Deleting the session cookie uses the ``httponly`` flag. + :issue:`4485` +- Relax typing for ``errorhandler`` to allow the user to use more + precise types and decorate the same function multiple times. + :issue:`4095, 4295, 4297` +- Fix typing for ``__exit__`` methods for better compatibility with + ``ExitStack``. :issue:`4474` +- From Werkzeug, for redirect responses the ``Location`` header URL + will remain relative, and exclude the scheme and domain, by default. + :pr:`4496` +- Add ``Config.from_prefixed_env()`` to load config values from + environment variables that start with ``FLASK_`` or another prefix. + This parses values as JSON by default, and allows setting keys in + nested dicts. :pr:`4479` + Version 2.0.3 ------------- @@ -65,7 +549,7 @@ Released 2021-05-21 the endpoint name. :issue:`4041` - Combine URL prefixes when nesting blueprints that were created with a ``url_prefix`` value. :issue:`4037` -- Roll back a change to the order that URL matching was done. The +- Revert a change to the order that URL matching was done. The URL is again matched after the session is loaded, so the session is available in custom URL converters. :issue:`4053` - Re-add deprecated ``Config.from_json``, which was accidentally @@ -103,17 +587,17 @@ Released 2021-05-11 ``click.get_current_context().obj`` if it's needed. :issue:`3552` - The CLI shows better error messages when the app failed to load when looking up commands. :issue:`2741` -- Add :meth:`sessions.SessionInterface.get_cookie_name` to allow - setting the session cookie name dynamically. :pr:`3369` -- Add :meth:`Config.from_file` to load config using arbitrary file +- Add ``SessionInterface.get_cookie_name`` to allow setting the + session cookie name dynamically. :pr:`3369` +- Add ``Config.from_file`` to load config using arbitrary file loaders, such as ``toml.load`` or ``json.load``. - :meth:`Config.from_json` is deprecated in favor of this. :pr:`3398` + ``Config.from_json`` is deprecated in favor of this. :pr:`3398` - The ``flask run`` command will only defer errors on reload. Errors present during the initial call will cause the server to exit with the traceback immediately. :issue:`3431` -- :func:`send_file` raises a :exc:`ValueError` when passed an - :mod:`io` object in text mode. Previously, it would respond with - 200 OK and an empty file. :issue:`3358` +- ``send_file`` raises a ``ValueError`` when passed an ``io`` object + in text mode. Previously, it would respond with 200 OK and an empty + file. :issue:`3358` - When using ad-hoc certificates, check for the cryptography library instead of PyOpenSSL. :pr:`3492` - When specifying a factory function with ``FLASK_APP``, keyword @@ -224,31 +708,29 @@ Released 2019-07-04 base ``HTTPException``. This makes error handler behavior more consistent. :pr:`3266` - - :meth:`Flask.finalize_request` is called for all unhandled + - ``Flask.finalize_request`` is called for all unhandled exceptions even if there is no ``500`` error handler. -- :attr:`Flask.logger` takes the same name as - :attr:`Flask.name` (the value passed as - ``Flask(import_name)``. This reverts 1.0's behavior of always - logging to ``"flask.app"``, in order to support multiple apps in the - same process. A warning will be shown if old configuration is +- ``Flask.logger`` takes the same name as ``Flask.name`` (the value + passed as ``Flask(import_name)``. This reverts 1.0's behavior of + always logging to ``"flask.app"``, in order to support multiple apps + in the same process. A warning will be shown if old configuration is detected that needs to be moved. :issue:`2866` -- :meth:`flask.RequestContext.copy` includes the current session - object in the request context copy. This prevents ``session`` - pointing to an out-of-date object. :issue:`2935` +- ``RequestContext.copy`` includes the current session object in the + request context copy. This prevents ``session`` pointing to an + out-of-date object. :issue:`2935` - Using built-in RequestContext, unprintable Unicode characters in Host header will result in a HTTP 400 response and not HTTP 500 as previously. :pr:`2994` -- :func:`send_file` supports :class:`~os.PathLike` objects as - described in PEP 0519, to support :mod:`pathlib` in Python 3. - :pr:`3059` -- :func:`send_file` supports :class:`~io.BytesIO` partial content. +- ``send_file`` supports ``PathLike`` objects as described in + :pep:`519`, to support ``pathlib`` in Python 3. :pr:`3059` +- ``send_file`` supports ``BytesIO`` partial content. :issue:`2957` -- :func:`open_resource` accepts the "rt" file mode. This still does - the same thing as "r". :issue:`3163` -- The :attr:`MethodView.methods` attribute set in a base class is used - by subclasses. :issue:`3138` -- :attr:`Flask.jinja_options` is a ``dict`` instead of an +- ``open_resource`` accepts the "rt" file mode. This still does the + same thing as "r". :issue:`3163` +- The ``MethodView.methods`` attribute set in a base class is used by + subclasses. :issue:`3138` +- ``Flask.jinja_options`` is a ``dict`` instead of an ``ImmutableDict`` to allow easier configuration. Changes must still be made before creating the environment. :pr:`3190` - Flask's ``JSONMixin`` for the request and response wrappers was @@ -262,15 +744,14 @@ Released 2019-07-04 :issue:`3134` - Support empty ``static_folder`` without requiring setting an empty ``static_url_path`` as well. :pr:`3124` -- :meth:`jsonify` supports :class:`dataclasses.dataclass` objects. - :pr:`3195` -- Allow customizing the :attr:`Flask.url_map_class` used for routing. +- ``jsonify`` supports ``dataclass`` objects. :pr:`3195` +- Allow customizing the ``Flask.url_map_class`` used for routing. :pr:`3069` - The development server port can be set to 0, which tells the OS to pick an available port. :issue:`2926` -- The return value from :meth:`cli.load_dotenv` is more consistent - with the documentation. It will return ``False`` if python-dotenv is - not installed, or if the given path isn't a file. :issue:`2937` +- The return value from ``cli.load_dotenv`` is more consistent with + the documentation. It will return ``False`` if python-dotenv is not + installed, or if the given path isn't a file. :issue:`2937` - Signaling support has a stub for the ``connect_via`` method when the Blinker library is not installed. :pr:`3208` - Add an ``--extra-files`` option to the ``flask run`` CLI command to @@ -309,7 +790,7 @@ Released 2019-07-04 requires upgrading to Werkzeug 0.15.5. :issue:`3249` - ``send_file`` url quotes the ":" and "/" characters for more compatible UTF-8 filename support in some browsers. :issue:`3074` -- Fixes for PEP451 import loaders and pytest 5.x. :issue:`3275` +- Fixes for :pep:`451` import loaders and pytest 5.x. :issue:`3275` - Show message about dotenv on stderr instead of stdout. :issue:`3285` @@ -318,16 +799,16 @@ Version 1.0.3 Released 2019-05-17 -- :func:`send_file` encodes filenames as ASCII instead of Latin-1 +- ``send_file`` encodes filenames as ASCII instead of Latin-1 (ISO-8859-1). This fixes compatibility with Gunicorn, which is - stricter about header encodings than PEP 3333. :issue:`2766` + stricter about header encodings than :pep:`3333`. :issue:`2766` - Allow custom CLIs using ``FlaskGroup`` to set the debug flag without it always being overwritten based on environment variables. :pr:`2765` - ``flask --version`` outputs Werkzeug's version and simplifies the Python version. :pr:`2825` -- :func:`send_file` handles an ``attachment_filename`` that is a - native Python 2 string (bytes) with UTF-8 coded bytes. :issue:`2933` +- ``send_file`` handles an ``attachment_filename`` that is a native + Python 2 string (bytes) with UTF-8 coded bytes. :issue:`2933` - A catch-all error handler registered for ``HTTPException`` will not handle ``RoutingException``, which is used internally during routing. This fixes the unexpected behavior that had been introduced @@ -375,32 +856,30 @@ Released 2018-04-26 - Bump minimum dependency versions to the latest stable versions: Werkzeug >= 0.14, Jinja >= 2.10, itsdangerous >= 0.24, Click >= 5.1. :issue:`2586` -- Skip :meth:`app.run ` when a Flask application is run - from the command line. This avoids some behavior that was confusing - to debug. -- Change the default for :data:`JSONIFY_PRETTYPRINT_REGULAR` to - ``False``. :func:`~json.jsonify` returns a compact format by - default, and an indented format in debug mode. :pr:`2193` -- :meth:`Flask.__init__ ` accepts the ``host_matching`` - argument and sets it on :attr:`~Flask.url_map`. :issue:`1559` -- :meth:`Flask.__init__ ` accepts the ``static_host`` argument - and passes it as the ``host`` argument when defining the static - route. :issue:`1559` -- :func:`send_file` supports Unicode in ``attachment_filename``. +- Skip ``app.run`` when a Flask application is run from the command + line. This avoids some behavior that was confusing to debug. +- Change the default for ``JSONIFY_PRETTYPRINT_REGULAR`` to + ``False``. ``~json.jsonify`` returns a compact format by default, + and an indented format in debug mode. :pr:`2193` +- ``Flask.__init__`` accepts the ``host_matching`` argument and sets + it on ``Flask.url_map``. :issue:`1559` +- ``Flask.__init__`` accepts the ``static_host`` argument and passes + it as the ``host`` argument when defining the static route. + :issue:`1559` +- ``send_file`` supports Unicode in ``attachment_filename``. :pr:`2223` -- Pass ``_scheme`` argument from :func:`url_for` to - :meth:`~Flask.handle_url_build_error`. :pr:`2017` -- :meth:`~Flask.add_url_rule` accepts the - ``provide_automatic_options`` argument to disable adding the - ``OPTIONS`` method. :pr:`1489` -- :class:`~views.MethodView` subclasses inherit method handlers from - base classes. :pr:`1936` +- Pass ``_scheme`` argument from ``url_for`` to + ``Flask.handle_url_build_error``. :pr:`2017` +- ``Flask.add_url_rule`` accepts the ``provide_automatic_options`` + argument to disable adding the ``OPTIONS`` method. :pr:`1489` +- ``MethodView`` subclasses inherit method handlers from base classes. + :pr:`1936` - Errors caused while opening the session at the beginning of the request are handled by the app's error handlers. :pr:`2254` -- Blueprints gained :attr:`~Blueprint.json_encoder` and - :attr:`~Blueprint.json_decoder` attributes to override the app's +- Blueprints gained ``Blueprint.json_encoder`` and + ``Blueprint.json_decoder`` attributes to override the app's encoder and decoder. :pr:`1898` -- :meth:`Flask.make_response` raises ``TypeError`` instead of +- ``Flask.make_response`` raises ``TypeError`` instead of ``ValueError`` for bad response types. The error messages have been improved to describe why the type is invalid. :pr:`2256` - Add ``routes`` CLI command to output routes registered on the @@ -415,52 +894,49 @@ Released 2018-04-26 ``make_app`` from ``FLASK_APP``. :pr:`2297` - Factory functions are not required to take a ``script_info`` parameter to work with the ``flask`` command. If they take a single - parameter or a parameter named ``script_info``, the - :class:`~cli.ScriptInfo` object will be passed. :pr:`2319` + parameter or a parameter named ``script_info``, the ``ScriptInfo`` + object will be passed. :pr:`2319` - ``FLASK_APP`` can be set to an app factory, with arguments if needed, for example ``FLASK_APP=myproject.app:create_app('dev')``. :pr:`2326` - ``FLASK_APP`` can point to local packages that are not installed in editable mode, although ``pip install -e`` is still preferred. :pr:`2414` -- The :class:`~views.View` class attribute - :attr:`~views.View.provide_automatic_options` is set in - :meth:`~views.View.as_view`, to be detected by - :meth:`~Flask.add_url_rule`. :pr:`2316` +- The ``View`` class attribute + ``View.provide_automatic_options`` is set in ``View.as_view``, to be + detected by ``Flask.add_url_rule``. :pr:`2316` - Error handling will try handlers registered for ``blueprint, code``, ``app, code``, ``blueprint, exception``, ``app, exception``. :pr:`2314` - ``Cookie`` is added to the response's ``Vary`` header if the session is accessed at all during the request (and not deleted). :pr:`2288` -- :meth:`~Flask.test_request_context` accepts ``subdomain`` and +- ``Flask.test_request_context`` accepts ``subdomain`` and ``url_scheme`` arguments for use when building the base URL. :pr:`1621` -- Set :data:`APPLICATION_ROOT` to ``'/'`` by default. This was already - the implicit default when it was set to ``None``. -- :data:`TRAP_BAD_REQUEST_ERRORS` is enabled by default in debug mode. +- Set ``APPLICATION_ROOT`` to ``'/'`` by default. This was already the + implicit default when it was set to ``None``. +- ``TRAP_BAD_REQUEST_ERRORS`` is enabled by default in debug mode. ``BadRequestKeyError`` has a message with the bad key in debug mode instead of the generic bad request message. :pr:`2348` -- Allow registering new tags with - :class:`~json.tag.TaggedJSONSerializer` to support storing other - types in the session cookie. :pr:`2352` +- Allow registering new tags with ``TaggedJSONSerializer`` to support + storing other types in the session cookie. :pr:`2352` - Only open the session if the request has not been pushed onto the - context stack yet. This allows :func:`~stream_with_context` - generators to access the same session that the containing view uses. - :pr:`2354` + context stack yet. This allows ``stream_with_context`` generators to + access the same session that the containing view uses. :pr:`2354` - Add ``json`` keyword argument for the test client request methods. This will dump the given object as JSON and set the appropriate content type. :pr:`2358` -- Extract JSON handling to a mixin applied to both the - :class:`Request` and :class:`Response` classes. This adds the - :meth:`~Response.is_json` and :meth:`~Response.get_json` methods to - the response to make testing JSON response much easier. :pr:`2358` +- Extract JSON handling to a mixin applied to both the ``Request`` and + ``Response`` classes. This adds the ``Response.is_json`` and + ``Response.get_json`` methods to the response to make testing JSON + response much easier. :pr:`2358` - Removed error handler caching because it caused unexpected results for some exception inheritance hierarchies. Register handlers explicitly for each exception if you want to avoid traversing the MRO. :pr:`2362` - Fix incorrect JSON encoding of aware, non-UTC datetimes. :pr:`2374` -- Template auto reloading will honor debug mode even even if - :attr:`~Flask.jinja_env` was already accessed. :pr:`2373` +- Template auto reloading will honor debug mode even if + ``Flask.jinja_env`` was already accessed. :pr:`2373` - The following old deprecated code was removed. :issue:`2385` - ``flask.ext`` - import extensions directly by their name instead @@ -468,57 +944,55 @@ Released 2018-04-26 ``import flask.ext.sqlalchemy`` becomes ``import flask_sqlalchemy``. - ``Flask.init_jinja_globals`` - extend - :meth:`Flask.create_jinja_environment` instead. + ``Flask.create_jinja_environment`` instead. - ``Flask.error_handlers`` - tracked by - :attr:`Flask.error_handler_spec`, use :meth:`Flask.errorhandler` + ``Flask.error_handler_spec``, use ``Flask.errorhandler`` to register handlers. - ``Flask.request_globals_class`` - use - :attr:`Flask.app_ctx_globals_class` instead. - - ``Flask.static_path`` - use :attr:`Flask.static_url_path` - instead. - - ``Request.module`` - use :attr:`Request.blueprint` instead. + ``Flask.app_ctx_globals_class`` instead. + - ``Flask.static_path`` - use ``Flask.static_url_path`` instead. + - ``Request.module`` - use ``Request.blueprint`` instead. -- The :attr:`Request.json` property is no longer deprecated. - :issue:`1421` -- Support passing a :class:`~werkzeug.test.EnvironBuilder` or ``dict`` - to :meth:`test_client.open `. :pr:`2412` -- The ``flask`` command and :meth:`Flask.run` will load environment +- The ``Request.json`` property is no longer deprecated. :issue:`1421` +- Support passing a ``EnvironBuilder`` or ``dict`` to + ``test_client.open``. :pr:`2412` +- The ``flask`` command and ``Flask.run`` will load environment variables from ``.env`` and ``.flaskenv`` files if python-dotenv is installed. :pr:`2416` - When passing a full URL to the test client, the scheme in the URL is - used instead of :data:`PREFERRED_URL_SCHEME`. :pr:`2430` -- :attr:`Flask.logger` has been simplified. ``LOGGER_NAME`` and + used instead of ``PREFERRED_URL_SCHEME``. :pr:`2430` +- ``Flask.logger`` has been simplified. ``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` config was removed. The logger is always named ``flask.app``. The level is only set on first access, it - doesn't check :attr:`Flask.debug` each time. Only one format is - used, not different ones depending on :attr:`Flask.debug`. No - handlers are removed, and a handler is only added if no handlers are - already configured. :pr:`2436` + doesn't check ``Flask.debug`` each time. Only one format is used, + not different ones depending on ``Flask.debug``. No handlers are + removed, and a handler is only added if no handlers are already + configured. :pr:`2436` - Blueprint view function names may not contain dots. :pr:`2450` - Fix a ``ValueError`` caused by invalid ``Range`` requests in some cases. :issue:`2526` - The development server uses threads by default. :pr:`2529` -- Loading config files with ``silent=True`` will ignore - :data:`~errno.ENOTDIR` errors. :pr:`2581` +- Loading config files with ``silent=True`` will ignore ``ENOTDIR`` + errors. :pr:`2581` - Pass ``--cert`` and ``--key`` options to ``flask run`` to run the development server over HTTPS. :pr:`2606` -- Added :data:`SESSION_COOKIE_SAMESITE` to control the ``SameSite`` +- Added ``SESSION_COOKIE_SAMESITE`` to control the ``SameSite`` attribute on the session cookie. :pr:`2607` -- Added :meth:`~flask.Flask.test_cli_runner` to create a Click runner - that can invoke Flask CLI commands for testing. :pr:`2636` +- Added ``Flask.test_cli_runner`` to create a Click runner that can + invoke Flask CLI commands for testing. :pr:`2636` - Subdomain matching is disabled by default and setting - :data:`SERVER_NAME` does not implicitly enable it. It can be enabled - by passing ``subdomain_matching=True`` to the ``Flask`` constructor. + ``SERVER_NAME`` does not implicitly enable it. It can be enabled by + passing ``subdomain_matching=True`` to the ``Flask`` constructor. :pr:`2635` - A single trailing slash is stripped from the blueprint ``url_prefix`` when it is registered with the app. :pr:`2629` -- :meth:`Request.get_json` doesn't cache the result if parsing fails - when ``silent`` is true. :issue:`2651` -- :func:`Request.get_json` no longer accepts arbitrary encodings. - Incoming JSON should be encoded using UTF-8 per :rfc:`8259`, but - Flask will autodetect UTF-8, -16, or -32. :pr:`2691` -- Added :data:`MAX_COOKIE_SIZE` and :attr:`Response.max_cookie_size` - to control when Werkzeug warns about large cookies that browsers may +- ``Request.get_json`` doesn't cache the result if parsing fails when + ``silent`` is true. :issue:`2651` +- ``Request.get_json`` no longer accepts arbitrary encodings. Incoming + JSON should be encoded using UTF-8 per :rfc:`8259`, but Flask will + autodetect UTF-8, -16, or -32. :pr:`2691` +- Added ``MAX_COOKIE_SIZE`` and ``Response.max_cookie_size`` to + control when Werkzeug warns about large cookies that browsers may ignore. :pr:`2693` - Updated documentation theme to make docs look better in small windows. :pr:`2709` @@ -548,7 +1022,7 @@ Version 0.12.3 Released 2018-04-26 -- :func:`Request.get_json` no longer accepts arbitrary encodings. +- ``Request.get_json`` no longer accepts arbitrary encodings. Incoming JSON should be encoded using UTF-8 per :rfc:`8259`, but Flask will autodetect UTF-8, -16, or -32. :issue:`2692` - Fix a Python warning about imports when using ``python -m flask``. @@ -618,13 +1092,12 @@ Version 0.11 Released 2016-05-29, codename Absinthe -- Added support to serializing top-level arrays to - :func:`flask.jsonify`. This introduces a security risk in ancient - browsers. +- Added support to serializing top-level arrays to ``jsonify``. This + introduces a security risk in ancient browsers. - Added before_render_template signal. -- Added ``**kwargs`` to :meth:`flask.Test.test_client` to support - passing additional keyword arguments to the constructor of - :attr:`flask.Flask.test_client_class`. +- Added ``**kwargs`` to ``Flask.test_client`` to support passing + additional keyword arguments to the constructor of + ``Flask.test_client_class``. - Added ``SESSION_REFRESH_EACH_REQUEST`` config key that controls the set-cookie behavior. If set to ``True`` a permanent session will be refreshed each request and get their lifetime extended, if set to @@ -634,9 +1107,9 @@ Released 2016-05-29, codename Absinthe - Made Flask support custom JSON mimetypes for incoming data. - Added support for returning tuples in the form ``(response, headers)`` from a view function. -- Added :meth:`flask.Config.from_json`. -- Added :attr:`flask.Flask.config_class`. -- Added :meth:`flask.Config.get_namespace`. +- Added ``Config.from_json``. +- Added ``Flask.config_class``. +- Added ``Config.get_namespace``. - Templates are no longer automatically reloaded outside of debug mode. This can be configured with the new ``TEMPLATES_AUTO_RELOAD`` config key. @@ -644,7 +1117,7 @@ Released 2016-05-29, codename Absinthe loader. - Added support for explicit root paths when using Python 3.3's namespace packages. -- Added :command:`flask` and the ``flask.cli`` module to start the +- Added ``flask`` and the ``flask.cli`` module to start the local debug server through the click CLI system. This is recommended over the old ``flask.run()`` method as it works faster and more reliable due to a different design and also replaces @@ -655,7 +1128,7 @@ Released 2016-05-29, codename Absinthe an extension author to create exceptions that will by default result in the HTTP error of their choosing, but may be caught with a custom error handler if desired. -- Added :meth:`flask.Config.from_mapping`. +- Added ``Config.from_mapping``. - Flask will now log by default even if debug is disabled. The log format is now hardcoded but the default log handling can be disabled through the ``LOGGER_HANDLER_POLICY`` configuration key. @@ -673,9 +1146,7 @@ Released 2016-05-29, codename Absinthe space included by default after separators. - JSON responses are now terminated with a newline character, because it is a convention that UNIX text files end with a newline and some - clients don't deal well when this newline is missing. This came up - originally as a part of - https://github.com/postmanlabs/httpbin/issues/168. :pr:`1262` + clients don't deal well when this newline is missing. :pr:`1262` - The automatically provided ``OPTIONS`` method is now correctly disabled if the user registered an overriding rule with the lowercase-version ``options``. :issue:`1288` @@ -695,9 +1166,9 @@ Released 2016-05-29, codename Absinthe - Exceptions during teardown handling will no longer leave bad application contexts lingering around. - Fixed broken ``test_appcontext_signals()`` test case. -- Raise an :exc:`AttributeError` in :func:`flask.helpers.find_package` - with a useful message explaining why it is raised when a PEP 302 - import hook is used without an ``is_package()`` method. +- Raise an ``AttributeError`` in ``helpers.find_package`` with a + useful message explaining why it is raised when a :pep:`302` import + hook is used without an ``is_package()`` method. - Fixed an issue causing exceptions raised before entering a request or app context to be passed to teardown handlers. - Fixed an issue with query parameters getting removed from requests @@ -743,7 +1214,7 @@ Released 2013-06-13, codename Limoncello - Set the content-length header for x-sendfile. - ``tojson`` filter now does not escape script blocks in HTML5 parsers. -- ``tojson`` used in templates is now safe by default due. This was +- ``tojson`` used in templates is now safe by default. This was allowed due to the different escaping behavior. - Flask will now raise an error if you attempt to register a new function on an already used endpoint. @@ -813,12 +1284,12 @@ Version 0.9 Released 2012-07-01, codename Campari -- The :func:`flask.Request.on_json_loading_failed` now returns a JSON - formatted response by default. -- The :func:`flask.url_for` function now can generate anchors to the - generated links. -- The :func:`flask.url_for` function now can also explicitly generate - URL rules specific to a given HTTP method. +- The ``Request.on_json_loading_failed`` now returns a JSON formatted + response by default. +- The ``url_for`` function now can generate anchors to the generated + links. +- The ``url_for`` function now can also explicitly generate URL rules + specific to a given HTTP method. - Logger now only returns the debug log setting if it was not set explicitly. - Unregister a circular dependency between the WSGI environment and @@ -830,42 +1301,41 @@ Released 2012-07-01, codename Campari - Session is now stored after callbacks so that if the session payload is stored in the session you can still modify it in an after request callback. -- The :class:`flask.Flask` class will avoid importing the provided - import name if it can (the required first parameter), to benefit - tools which build Flask instances programmatically. The Flask class - will fall back to using import on systems with custom module hooks, - e.g. Google App Engine, or when the import name is inside a zip - archive (usually a .egg) prior to Python 2.7. +- The ``Flask`` class will avoid importing the provided import name if + it can (the required first parameter), to benefit tools which build + Flask instances programmatically. The Flask class will fall back to + using import on systems with custom module hooks, e.g. Google App + Engine, or when the import name is inside a zip archive (usually an + egg) prior to Python 2.7. - Blueprints now have a decorator to add custom template filters - application wide, :meth:`flask.Blueprint.app_template_filter`. + application wide, ``Blueprint.app_template_filter``. - The Flask and Blueprint classes now have a non-decorator method for adding custom template filters application wide, - :meth:`flask.Flask.add_template_filter` and - :meth:`flask.Blueprint.add_app_template_filter`. -- The :func:`flask.get_flashed_messages` function now allows rendering - flashed message categories in separate blocks, through a - ``category_filter`` argument. -- The :meth:`flask.Flask.run` method now accepts ``None`` for ``host`` - and ``port`` arguments, using default values when ``None``. This - allows for calling run using configuration values, e.g. + ``Flask.add_template_filter`` and + ``Blueprint.add_app_template_filter``. +- The ``get_flashed_messages`` function now allows rendering flashed + message categories in separate blocks, through a ``category_filter`` + argument. +- The ``Flask.run`` method now accepts ``None`` for ``host`` and + ``port`` arguments, using default values when ``None``. This allows + for calling run using configuration values, e.g. ``app.run(app.config.get('MYHOST'), app.config.get('MYPORT'))``, with proper behavior whether or not a config file is provided. -- The :meth:`flask.render_template` method now accepts a either an - iterable of template names or a single template name. Previously, it - only accepted a single template name. On an iterable, the first - template found is rendered. -- Added :meth:`flask.Flask.app_context` which works very similar to - the request context but only provides access to the current - application. This also adds support for URL generation without an - active request context. +- The ``render_template`` method now accepts a either an iterable of + template names or a single template name. Previously, it only + accepted a single template name. On an iterable, the first template + found is rendered. +- Added ``Flask.app_context`` which works very similar to the request + context but only provides access to the current application. This + also adds support for URL generation without an active request + context. - View functions can now return a tuple with the first instance being - an instance of :class:`flask.Response`. This allows for returning + an instance of ``Response``. This allows for returning ``jsonify(error="error msg"), 400`` from a view function. -- :class:`~flask.Flask` and :class:`~flask.Blueprint` now provide a - :meth:`~flask.Flask.get_send_file_max_age` hook for subclasses to - override behavior of serving static files from Flask when using - :meth:`flask.Flask.send_static_file` (used for the default static - file handler) and :func:`~flask.helpers.send_file`. This hook is +- ``Flask`` and ``Blueprint`` now provide a ``get_send_file_max_age`` + hook for subclasses to override behavior of serving static files + from Flask when using ``Flask.send_static_file`` (used for the + default static file handler) and ``helpers.send_file``. This hook is provided a filename, which for example allows changing cache controls by file extension. The default max-age for ``send_file`` and static files can be configured through a new @@ -877,14 +1347,13 @@ Released 2012-07-01, codename Campari - Changed the behavior of tuple return values from functions. They are no longer arguments to the response object, they now have a defined meaning. -- Added :attr:`flask.Flask.request_globals_class` to allow a specific - class to be used on creation of the :data:`~flask.g` instance of - each request. +- Added ``Flask.request_globals_class`` to allow a specific class to + be used on creation of the ``g`` instance of each request. - Added ``required_methods`` attribute to view functions to force-add methods on registration. -- Added :func:`flask.after_this_request`. -- Added :func:`flask.stream_with_context` and the ability to push - contexts multiple times without producing unexpected behavior. +- Added ``flask.after_this_request``. +- Added ``flask.stream_with_context`` and the ability to push contexts + multiple times without producing unexpected behavior. Version 0.8.1 @@ -917,8 +1386,8 @@ Released 2011-09-29, codename Rakija earlier feedback when users forget to import view code ahead of time. - Added the ability to register callbacks that are only triggered once - at the beginning of the first request. - (:meth:`Flask.before_first_request`) + at the beginning of the first request with + ``Flask.before_first_request``. - Malformed JSON data will now trigger a bad request HTTP exception instead of a value error which usually would result in a 500 internal server error if not handled. This is a backwards @@ -930,25 +1399,25 @@ Released 2011-09-29, codename Rakija version control so it's the perfect place to put configuration files etc. - Added the ``APPLICATION_ROOT`` configuration variable. -- Implemented :meth:`~flask.testing.TestClient.session_transaction` to - easily modify sessions from the test environment. +- Implemented ``TestClient.session_transaction`` to easily modify + sessions from the test environment. - Refactored test client internally. The ``APPLICATION_ROOT`` configuration variable as well as ``SERVER_NAME`` are now properly used by the test client as defaults. -- Added :attr:`flask.views.View.decorators` to support simpler - decorating of pluggable (class-based) views. +- Added ``View.decorators`` to support simpler decorating of pluggable + (class-based) views. - Fixed an issue where the test client if used with the "with" statement did not trigger the execution of the teardown handlers. - Added finer control over the session cookie parameters. - HEAD requests to a method view now automatically dispatch to the ``get`` method if no handler was implemented. -- Implemented the virtual :mod:`flask.ext` package to import - extensions from. +- Implemented the virtual ``flask.ext`` package to import extensions + from. - The context preservation on exceptions is now an integral component of Flask itself and no longer of the test client. This cleaned up some internal logic and lowers the odds of runaway request contexts in unittests. -- Fixed the Jinja2 environment's ``list_templates`` method not +- Fixed the Jinja environment's ``list_templates`` method not returning the correct names when blueprints or modules were involved. @@ -976,14 +1445,13 @@ Version 0.7 Released 2011-06-28, codename Grappa -- Added :meth:`~flask.Flask.make_default_options_response` which can - be used by subclasses to alter the default behavior for ``OPTIONS`` - responses. -- Unbound locals now raise a proper :exc:`RuntimeError` instead of an - :exc:`AttributeError`. +- Added ``Flask.make_default_options_response`` which can be used by + subclasses to alter the default behavior for ``OPTIONS`` responses. +- Unbound locals now raise a proper ``RuntimeError`` instead of an + ``AttributeError``. - Mimetype guessing and etag support based on file objects is now - deprecated for :func:`flask.send_file` because it was unreliable. - Pass filenames instead or attach your own etags and provide a proper + deprecated for ``send_file`` because it was unreliable. Pass + filenames instead or attach your own etags and provide a proper mimetype by hand. - Static file handling for modules now requires the name of the static folder to be supplied explicitly. The previous autodetection was not @@ -1009,15 +1477,15 @@ Released 2011-06-28, codename Grappa at the end of a request regardless of whether an exception occurred. Also the behavior for ``after_request`` was changed. It's now no longer executed when an exception is raised. -- Implemented :func:`flask.has_request_context` +- Implemented ``has_request_context``. - Deprecated ``init_jinja_globals``. Override the - :meth:`~flask.Flask.create_jinja_environment` method instead to - achieve the same functionality. -- Added :func:`flask.safe_join` + ``Flask.create_jinja_environment`` method instead to achieve the + same functionality. +- Added ``safe_join``. - The automatic JSON request data unpacking now looks at the charset mimetype parameter. -- Don't modify the session on :func:`flask.get_flashed_messages` if - there are no messages in the session. +- Don't modify the session on ``get_flashed_messages`` if there are no + messages in the session. - ``before_request`` handlers are now able to abort requests with errors. - It is not possible to define user exception handlers. That way you @@ -1035,7 +1503,7 @@ Released 2010-12-31 - Fixed an issue where the default ``OPTIONS`` response was not exposing all valid methods in the ``Allow`` header. -- Jinja2 template loading syntax now allows "./" in front of a +- Jinja template loading syntax now allows "./" in front of a template load path. Previously this caused issues with module setups. - Fixed an issue where the subdomain setting for modules was ignored @@ -1059,29 +1527,25 @@ Released 2010-07-27, codename Whisky - Static rules are now even in place if there is no static folder for the module. This was implemented to aid GAE which will remove the static folder if it's part of a mapping in the .yml file. -- The :attr:`~flask.Flask.config` is now available in the templates as - ``config``. +- ``Flask.config`` is now available in the templates as ``config``. - Context processors will no longer override values passed directly to the render function. - Added the ability to limit the incoming request data with the new ``MAX_CONTENT_LENGTH`` configuration value. -- The endpoint for the :meth:`flask.Module.add_url_rule` method is now - optional to be consistent with the function of the same name on the +- The endpoint for the ``Module.add_url_rule`` method is now optional + to be consistent with the function of the same name on the application object. -- Added a :func:`flask.make_response` function that simplifies - creating response object instances in views. +- Added a ``make_response`` function that simplifies creating response + object instances in views. - Added signalling support based on blinker. This feature is currently optional and supposed to be used by extensions and applications. If - you want to use it, make sure to have `blinker`_ installed. + you want to use it, make sure to have ``blinker`` installed. - Refactored the way URL adapters are created. This process is now - fully customizable with the :meth:`~flask.Flask.create_url_adapter` - method. + fully customizable with the ``Flask.create_url_adapter`` method. - Modules can now register for a subdomain instead of just an URL prefix. This makes it possible to bind a whole module to a configurable subdomain. -.. _blinker: https://pypi.org/project/blinker/ - Version 0.5.2 ------------- @@ -1115,8 +1579,8 @@ Released 2010-07-06, codename Calvados templates this behavior can be changed with the ``autoescape`` tag. - Refactored Flask internally. It now consists of more than a single file. -- :func:`flask.send_file` now emits etags and has the ability to do - conditional responses builtin. +- ``send_file`` now emits etags and has the ability to do conditional + responses builtin. - (temporarily) dropped support for zipped applications. This was a rarely used feature and led to some confusing behavior. - Added support for per-package template and static-file directories. @@ -1132,9 +1596,8 @@ Released 2010-06-18, codename Rakia - Added the ability to register application wide error handlers from modules. -- :meth:`~flask.Flask.after_request` handlers are now also invoked if - the request dies with an exception and an error handling page kicks - in. +- ``Flask.after_request`` handlers are now also invoked if the request + dies with an exception and an error handling page kicks in. - Test client has not the ability to preserve the request context for a little longer. This can also be used to trigger custom requests that do not pop the request stack for testing. @@ -1149,8 +1612,8 @@ Version 0.3.1 Released 2010-05-28 -- Fixed a error reporting bug with :meth:`flask.Config.from_envvar` -- Removed some unused code from flask +- Fixed a error reporting bug with ``Config.from_envvar``. +- Removed some unused code. - Release does no longer include development leftover files (.git folder for themes, built documentation in zip and pdf file and some .pyc files) @@ -1162,9 +1625,9 @@ Version 0.3 Released 2010-05-28, codename Schnaps - Added support for categories for flashed messages. -- The application now configures a :class:`logging.Handler` and will - log request handling exceptions to that logger when not in debug - mode. This makes it possible to receive mails on server errors for +- The application now configures a ``logging.Handler`` and will log + request handling exceptions to that logger when not in debug mode. + This makes it possible to receive mails on server errors for example. - Added support for context binding that does not require the use of the with statement for playing in the console. @@ -1180,14 +1643,13 @@ Released 2010-05-12, codename J?germeister - Various bugfixes - Integrated JSON support -- Added :func:`~flask.get_template_attribute` helper function. -- :meth:`~flask.Flask.add_url_rule` can now also register a view - function. +- Added ``get_template_attribute`` helper function. +- ``Flask.add_url_rule`` can now also register a view function. - Refactored internal request dispatching. - Server listens on 127.0.0.1 by default now to fix issues with chrome. - Added external URL support. -- Added support for :func:`~flask.send_file` +- Added support for ``send_file``. - Module support and internal request handling refactoring to better support pluggable applications. - Sessions can be set to be permanent now on a per-session basis. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index f4ba197d..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at report@palletsprojects.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index a3c8b851..00000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,229 +0,0 @@ -How to contribute to Flask -========================== - -Thank you for considering contributing to Flask! - - -Support questions ------------------ - -Please don't use the issue tracker for this. The issue tracker is a tool -to address bugs and feature requests in Flask itself. Use one of the -following resources for questions about using Flask or issues with your -own code: - -- The ``#questions`` channel on our Discord chat: - https://discord.gg/pallets -- The mailing list flask@python.org for long term discussion or larger - issues. -- Ask on `Stack Overflow`_. Search with Google first using: - ``site:stackoverflow.com flask {search term, exception message, etc.}`` -- Ask on our `GitHub Discussions`_. - -.. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?tab=Frequent -.. _GitHub Discussions: https://github.com/pallets/flask/discussions - - -Reporting issues ----------------- - -Include the following information in your post: - -- Describe what you expected to happen. -- If possible, include a `minimal reproducible example`_ to help us - identify the issue. This also helps check that the issue is not with - your own code. -- Describe what actually happened. Include the full traceback if there - was an exception. -- List your Python and Flask versions. If possible, check if this - issue is already fixed in the latest releases or the latest code in - the repository. - -.. _minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example - - -Submitting patches ------------------- - -If there is not an open issue for what you want to submit, prefer -opening one for discussion before working on a PR. You can work on any -issue that doesn't have an open PR linked to it or a maintainer assigned -to it. These show up in the sidebar. No need to ask if you can work on -an issue that interests you. - -Include the following in your patch: - -- Use `Black`_ to format your code. This and other tools will run - automatically if you install `pre-commit`_ using the instructions - below. -- Include tests if your patch adds or changes code. Make sure the test - fails without your patch. -- Update any relevant docs pages and docstrings. Docs pages and - docstrings should be wrapped at 72 characters. -- Add an entry in ``CHANGES.rst``. Use the same style as other - entries. Also include ``.. versionchanged::`` inline changelogs in - relevant docstrings. - -.. _Black: https://black.readthedocs.io -.. _pre-commit: https://pre-commit.com - - -First time setup -~~~~~~~~~~~~~~~~ - -- Download and install the `latest version of git`_. -- Configure git with your `username`_ and `email`_. - - .. code-block:: text - - $ git config --global user.name 'your name' - $ git config --global user.email 'your email' - -- Make sure you have a `GitHub account`_. -- Fork Flask to your GitHub account by clicking the `Fork`_ button. -- `Clone`_ the main repository locally. - - .. code-block:: text - - $ git clone https://github.com/pallets/flask - $ cd flask - -- Add your fork as a remote to push your work to. Replace - ``{username}`` with your username. This names the remote "fork", the - default Pallets remote is "origin". - - .. code-block:: text - - $ git remote add fork https://github.com/{username}/flask - -- Create a virtualenv. - - .. tabs:: - - .. group-tab:: Linux/macOS - - .. code-block:: text - - $ python3 -m venv env - $ . env/bin/activate - - .. group-tab:: Windows - - .. code-block:: text - - > py -3 -m venv env - > env\Scripts\activate - -- Upgrade pip and setuptools. - - .. code-block:: text - - $ python -m pip install --upgrade pip setuptools - -- Install the development dependencies, then install Flask in editable - mode. - - .. code-block:: text - - $ pip install -r requirements/dev.txt && pip install -e . - -- Install the pre-commit hooks. - - .. code-block:: text - - $ pre-commit install - -.. _latest version of git: https://git-scm.com/downloads -.. _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/pallets/flask/fork -.. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork - - -Start coding -~~~~~~~~~~~~ - -- Create a branch to identify the issue you would like to work on. If - you're submitting a bug or documentation fix, branch off of the - latest ".x" branch. - - .. code-block:: text - - $ git fetch origin - $ git checkout -b your-branch-name origin/2.0.x - - If you're submitting a feature addition or change, branch off of the - "main" branch. - - .. code-block:: text - - $ git fetch origin - $ git checkout -b your-branch-name origin/main - -- Using your favorite editor, make your changes, - `committing as you go`_. -- Include tests that cover any code changes you make. Make sure the - test fails without your patch. Run the tests as described below. -- Push your commits to your fork on GitHub and - `create a pull request`_. Link to the issue being addressed with - ``fixes #123`` in the pull request. - - .. code-block:: text - - $ git push --set-upstream fork your-branch-name - -.. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes -.. _create a pull request: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request - - -Running the tests -~~~~~~~~~~~~~~~~~ - -Run the basic test suite with pytest. - -.. code-block:: text - - $ pytest - -This runs the tests for the current environment, which is usually -sufficient. CI will run the full suite when you submit your pull -request. You can run the full test suite with tox if you don't want to -wait. - -.. code-block:: text - - $ tox - - -Running test coverage -~~~~~~~~~~~~~~~~~~~~~ - -Generating a report of lines that do not have test coverage can indicate -where to start contributing. Run ``pytest`` using ``coverage`` and -generate a report. - -.. code-block:: text - - $ pip install coverage - $ coverage run -m pytest - $ coverage html - -Open ``htmlcov/index.html`` in your browser to explore the report. - -Read more about `coverage `__. - - -Building the docs -~~~~~~~~~~~~~~~~~ - -Build the docs in the ``docs`` directory using Sphinx. - -.. code-block:: text - - $ cd docs - $ make html - -Open ``_build/html/index.html`` in your browser to view the docs. - -Read more about `Sphinx `__. diff --git a/LICENSE.rst b/LICENSE.txt similarity index 100% rename from LICENSE.rst rename to LICENSE.txt diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 65a97749..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include CHANGES.rst -include CONTRIBUTING.rst -include tox.ini -include requirements/*.txt -graft artwork -graft docs -prune docs/_build -graft examples -graft tests -include src/flask/py.typed -global-exclude *.pyc diff --git a/README.md b/README.md new file mode 100644 index 00000000..64f56cac --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +
+ +# Flask + +Flask is a lightweight [WSGI] web application framework. It is designed +to make getting started quick and easy, with the ability to scale up to +complex applications. It began as a simple wrapper around [Werkzeug] +and [Jinja], and has become one of the most popular Python web +application frameworks. + +Flask offers suggestions, but doesn't enforce any dependencies or +project layout. It is up to the developer to choose the tools and +libraries they want to use. There are many extensions provided by the +community that make adding new functionality easy. + +[WSGI]: https://wsgi.readthedocs.io/ +[Werkzeug]: https://werkzeug.palletsprojects.com/ +[Jinja]: https://jinja.palletsprojects.com/ + +## A Simple Example + +```python +# save this as app.py +from flask import Flask + +app = Flask(__name__) + +@app.route("/") +def hello(): + return "Hello, World!" +``` + +``` +$ flask run + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) +``` + +## Donate + +The Pallets organization develops and supports Flask and the libraries +it uses. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, [please +donate today]. + +[please donate today]: https://palletsprojects.com/donate + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ diff --git a/README.rst b/README.rst deleted file mode 100644 index 3d1c3882..00000000 --- a/README.rst +++ /dev/null @@ -1,82 +0,0 @@ -Flask -===== - -Flask is a lightweight `WSGI`_ web application framework. It is designed -to make getting started quick and easy, with the ability to scale up to -complex applications. It began as a simple wrapper around `Werkzeug`_ -and `Jinja`_ and has become one of the most popular Python web -application frameworks. - -Flask offers suggestions, but doesn't enforce any dependencies or -project layout. It is up to the developer to choose the tools and -libraries they want to use. There are many extensions provided by the -community that make adding new functionality easy. - -.. _WSGI: https://wsgi.readthedocs.io/ -.. _Werkzeug: https://werkzeug.palletsprojects.com/ -.. _Jinja: https://jinja.palletsprojects.com/ - - -Installing ----------- - -Install and update using `pip`_: - -.. code-block:: text - - $ pip install -U Flask - -.. _pip: https://pip.pypa.io/en/stable/getting-started/ - - -A Simple Example ----------------- - -.. code-block:: python - - # save this as app.py - from flask import Flask - - app = Flask(__name__) - - @app.route("/") - def hello(): - return "Hello, World!" - -.. code-block:: text - - $ flask run - * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) - - -Contributing ------------- - -For guidance on setting up a development environment and how to make a -contribution to Flask, see the `contributing guidelines`_. - -.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst - - -Donate ------- - -The Pallets organization develops and supports Flask and the libraries -it uses. In order to grow the community of contributors and users, and -allow the maintainers to devote more time to the projects, `please -donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - - -Links ------ - -- Documentation: https://flask.palletsprojects.com/ -- Changes: https://flask.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/Flask/ -- Source Code: https://github.com/pallets/flask/ -- Issue Tracker: https://github.com/pallets/flask/issues/ -- Website: https://palletsprojects.com/p/flask/ -- Twitter: https://twitter.com/PalletsTeam -- Chat: https://discord.gg/pallets diff --git a/artwork/LICENSE.rst b/artwork/LICENSE.rst deleted file mode 100644 index 99c58a21..00000000 --- a/artwork/LICENSE.rst +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2010 Pallets - -This logo or a modified version may be used by anyone to refer to the -Flask project, but does not indicate endorsement by the project. - -Redistribution and use in source (SVG) and binary (renders in PNG, etc.) -forms, with or without modification, are permitted provided that the -following conditions are met: - -1. Redistributions of source code must retain the above copyright - notice and this list of conditions. - -2. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -We would appreciate that you make the image a link to -https://palletsprojects.com/p/flask/ if you use it in a medium that -supports links. diff --git a/artwork/logo-full.svg b/artwork/logo-full.svg deleted file mode 100644 index 8c0748a2..00000000 --- a/artwork/logo-full.svg +++ /dev/null @@ -1,290 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/artwork/logo-lineart.svg b/artwork/logo-lineart.svg deleted file mode 100644 index 615260dc..00000000 --- a/artwork/logo-lineart.svg +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/docs/_static/flask-icon.png b/docs/_static/flask-icon.png deleted file mode 100644 index 55cb8478..00000000 Binary files a/docs/_static/flask-icon.png and /dev/null differ diff --git a/docs/_static/flask-icon.svg b/docs/_static/flask-icon.svg new file mode 100644 index 00000000..c802da9a --- /dev/null +++ b/docs/_static/flask-icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/docs/_static/flask-logo.png b/docs/_static/flask-logo.png deleted file mode 100644 index ce236061..00000000 Binary files a/docs/_static/flask-logo.png and /dev/null differ diff --git a/docs/_static/flask-logo.svg b/docs/_static/flask-logo.svg new file mode 100644 index 00000000..c216b617 --- /dev/null +++ b/docs/_static/flask-logo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/_static/flask-name.svg b/docs/_static/flask-name.svg new file mode 100644 index 00000000..b46782d2 --- /dev/null +++ b/docs/_static/flask-name.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/docs/_static/no.png b/docs/_static/no.png deleted file mode 100644 index 644c3f70..00000000 Binary files a/docs/_static/no.png and /dev/null differ diff --git a/docs/_static/pycharm-run-config.png b/docs/_static/pycharm-run-config.png new file mode 100644 index 00000000..ad025545 Binary files /dev/null and b/docs/_static/pycharm-run-config.png differ diff --git a/docs/_static/pycharm-runconfig.png b/docs/_static/pycharm-runconfig.png deleted file mode 100644 index dff21fa0..00000000 Binary files a/docs/_static/pycharm-runconfig.png and /dev/null differ diff --git a/docs/_static/yes.png b/docs/_static/yes.png deleted file mode 100644 index 56917ab2..00000000 Binary files a/docs/_static/yes.png and /dev/null differ diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst deleted file mode 100644 index 9c36158a..00000000 --- a/docs/advanced_foreword.rst +++ /dev/null @@ -1,46 +0,0 @@ -Foreword for Experienced Programmers -==================================== - -Thread-Locals in Flask ----------------------- - -One of the design decisions in Flask was that simple tasks should be simple; -they should not take a lot of code and yet they should not limit you. Because -of that, Flask has a few design choices that some people might find -surprising or unorthodox. For example, Flask uses thread-local objects -internally so that you don’t have to pass objects around from -function to function within a request in order to stay threadsafe. -This approach is convenient, but requires a valid -request context for dependency injection or when attempting to reuse code which -uses a value pegged to the request. The Flask project is honest about -thread-locals, does not hide them, and calls out in the code and documentation -where they are used. - -Develop for the Web with Caution --------------------------------- - -Always keep security in mind when building web applications. - -If you write a web application, you are probably allowing users to register -and leave their data on your server. The users are entrusting you with data. -And even if you are the only user that might leave data in your application, -you still want that data to be stored securely. - -Unfortunately, there are many ways the security of a web application can be -compromised. Flask protects you against one of the most common security -problems of modern web applications: cross-site scripting (XSS). Unless you -deliberately mark insecure HTML as secure, Flask and the underlying Jinja2 -template engine have you covered. But there are many more ways to cause -security problems. - -The documentation will warn you about aspects of web development that require -attention to security. Some of these security concerns are far more complex -than one might think, and we all sometimes underestimate the likelihood that a -vulnerability will be exploited - until a clever attacker figures out a way to -exploit our applications. And don't think that your application is not -important enough to attract an attacker. Depending on the kind of attack, -chances are that automated bots are probing for ways to fill your database with -spam, links to malicious software, and the like. - -Flask is no different from any other framework in that you the developer must -build with caution, watching for exploits when building to your requirements. diff --git a/docs/api.rst b/docs/api.rst index 1a87f8d4..52b25376 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,7 +3,7 @@ API .. module:: flask -This part of the documentation covers all the interfaces of Flask. For +This part of the documentation covers all the interfaces of Flask. For parts where Flask depends on external libraries, we document the most important right here and provide links to the canonical documentation. @@ -31,17 +31,15 @@ Incoming Request Data :inherited-members: :exclude-members: json_module -.. attribute:: request +.. data:: request - To access incoming request data, you can use the global `request` - object. Flask parses incoming request data for you and gives you - access to it through that global object. Internally Flask makes - sure that you always get the correct data for the active thread if you - are in a multithreaded environment. + A proxy to the request data for the current request, an instance of + :class:`.Request`. - This is a proxy. See :ref:`notes-on-proxies` for more information. + This is only available when a :doc:`request context ` is + active. - The request object is an instance of a :class:`~flask.Request`. + This is a proxy. See :ref:`context-visibility` for more information. Response Objects @@ -62,40 +60,33 @@ does this is by using a signed cookie. The user can look at the session contents, but can't modify it unless they know the secret key, so make sure to set that to something complex and unguessable. -To access the current session you can use the :class:`session` object: +To access the current session you can use the :data:`.session` proxy. -.. class:: session +.. data:: session - The session object works pretty much like an ordinary dict, with the - difference that it keeps track of modifications. + A proxy to the session data for the current request, an instance of + :class:`.SessionMixin`. - This is a proxy. See :ref:`notes-on-proxies` for more information. + This is only available when a :doc:`request context ` is + active. - The following attributes are interesting: + This is a proxy. See :ref:`context-visibility` for more information. - .. attribute:: new + The session object works like a dict but tracks assignment and access to its + keys. It cannot track modifications to mutable values, you need to set + :attr:`~.SessionMixin.modified` manually when modifying a list, dict, etc. - ``True`` if the session is new, ``False`` otherwise. + .. code-block:: python - .. attribute:: modified - - ``True`` if the session object detected a modification. Be advised - that modifications on mutable structures are not picked up - automatically, in that situation you have to explicitly set the - attribute to ``True`` yourself. Here an example:: - - # this change is not picked up because a mutable object (here - # a list) is changed. - session['objects'].append(42) + # appending to a list is not detected + session["numbers"].append(42) # so mark it as modified yourself session.modified = True - .. attribute:: permanent - - If set to ``True`` the session lives for - :attr:`~flask.Flask.permanent_session_lifetime` seconds. The - default is 31 days. If set to ``False`` (which is the default) the - session will be deleted when the user closes the browser. + The session is persisted across requests using a cookie. By default the + users's browser will clear the cookie when it is closed. Set + :attr:`~.SessionMixin.permanent` to ``True`` to persist the cookie for + :data:`PERMANENT_SESSION_LIFETIME`. Session Interface @@ -125,10 +116,9 @@ implementation that Flask is using. .. admonition:: Notice - The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer - starting with Flask 0.8. Either catch this down yourself or use - the :attr:`~flask.Flask.permanent_session_lifetime` attribute on the - app which converts the result to an integer automatically. + The :data:`PERMANENT_SESSION_LIFETIME` config can be an integer or ``timedelta``. + The :attr:`~flask.Flask.permanent_session_lifetime` attribute is always a + ``timedelta``. Test Client @@ -156,23 +146,24 @@ Application Globals To share data that is valid for one request only from one function to another, a global variable is not good enough because it would break in -threaded environments. Flask provides you with a special object that +threaded environments. Flask provides you with a special object that ensures it is only valid for the active request and that will return -different values for each request. In a nutshell: it does the right -thing, like it does for :class:`request` and :class:`session`. +different values for each request. In a nutshell: it does the right +thing, like it does for :data:`.request` and :data:`.session`. .. data:: g - A namespace object that can store data during an - :doc:`application context `. This is an instance of - :attr:`Flask.app_ctx_globals_class`, which defaults to - :class:`ctx._AppCtxGlobals`. + A proxy to a namespace object used to store data during a single request or + app context. An instance of :attr:`.Flask.app_ctx_globals_class`, which + defaults to :class:`._AppCtxGlobals`. - This is a good place to store resources during a request. For - example, a ``before_request`` function could load a user object from - a session id, then set ``g.user`` to be used in the view function. + This is a good place to store resources during a request. For example, a + :meth:`~.Flask.before_request` function could load a user object from a + session id, then set ``g.user`` to be used in the view function. - This is a proxy. See :ref:`notes-on-proxies` for more information. + This is only available when an :doc:`app context ` is active. + + This is a proxy. See :ref:`context-visibility` for more information. .. versionchanged:: 0.10 Bound to the application context instead of the request context. @@ -186,17 +177,16 @@ Useful Functions and Classes .. data:: current_app - A proxy to the application handling the current request. This is - useful to access the application without needing to import it, or if - it can't be imported, such as when using the application factory - pattern or in blueprints and extensions. + A proxy to the :class:`.Flask` application handling the current request or + other activity. - This is only available when an - :doc:`application context ` is pushed. This happens - automatically during requests and CLI commands. It can be controlled - manually with :meth:`~flask.Flask.app_context`. + This is useful to access the application without needing to import it, or if + it can't be imported, such as when using the application factory pattern or + in blueprints and extensions. - This is a proxy. See :ref:`notes-on-proxies` for more information. + This is only available when an :doc:`app context ` is active. + + This is a proxy. See :ref:`context-visibility` for more information. .. autofunction:: has_request_context @@ -218,12 +208,6 @@ Useful Functions and Classes .. autofunction:: send_from_directory -.. autofunction:: safe_join - -.. autofunction:: escape - -.. autoclass:: Markup - :members: escape, unescape, striptags Message Flashing ---------------- @@ -238,26 +222,20 @@ JSON Support .. module:: flask.json -Flask uses the built-in :mod:`json` module for handling JSON. It will -use the current blueprint's or application's JSON encoder and decoder -for easier customization. By default it handles some extra data types: +Flask uses Python's built-in :mod:`json` module for handling JSON by +default. The JSON implementation can be changed by assigning a different +provider to :attr:`flask.Flask.json_provider_class` or +:attr:`flask.Flask.json`. The functions provided by ``flask.json`` will +use methods on ``app.json`` if an app context is active. -- :class:`datetime.datetime` and :class:`datetime.date` are serialized - to :rfc:`822` strings. This is the same as the HTTP date format. -- :class:`uuid.UUID` is serialized to a string. -- :class:`dataclasses.dataclass` is passed to - :func:`dataclasses.asdict`. -- :class:`~markupsafe.Markup` (or any object with a ``__html__`` - method) will call the ``__html__`` method to get a string. - -Jinja's ``|tojson`` filter is configured to use Flask's :func:`dumps` -function. The filter marks the output with ``|safe`` automatically. Use -the filter to render data inside `` @@ -271,11 +249,13 @@ the filter to render data inside `` + +A less common pattern is to add the data to a ``data-`` attribute on an +HTML tag. In this case, you must use single quotes around the value, not +double quotes, otherwise you will produce invalid or unsafe HTML. + +.. code-block:: jinja + +
+ + +Generating URLs +--------------- + +The other way to get data from the server to JavaScript is to make a +request for it. First, you need to know the URL to request. + +The simplest way to generate URLs is to continue to use +:func:`~flask.url_for` when rendering the template. For example: + +.. code-block:: javascript + + const user_url = {{ url_for("user", id=current_user.id)|tojson }} + fetch(user_url).then(...) + +However, you might need to generate a URL based on information you only +know in JavaScript. As discussed above, JavaScript runs in the user's +browser, not as part of the template rendering, so you can't use +``url_for`` at that point. + +In this case, you need to know the "root URL" under which your +application is served. In simple setups, this is ``/``, but it might +also be something else, like ``https://example.com/myapp/``. + +A simple way to tell your JavaScript code about this root is to set it +as a global variable when rendering the template. Then you can use it +when generating URLs from JavaScript. + +.. code-block:: javascript + + const SCRIPT_ROOT = {{ request.script_root|tojson }} + let user_id = ... // do something to get a user id from the page + let user_url = `${SCRIPT_ROOT}/user/${user_id}` + fetch(user_url).then(...) + + +Making a Request with ``fetch`` +------------------------------- + +|fetch|_ takes two arguments, a URL and an object with other options, +and returns a |Promise|_. We won't cover all the available options, and +will only use ``then()`` on the promise, not other callbacks or +``await`` syntax. Read the linked MDN docs for more information about +those features. + +By default, the GET method is used. If the response contains JSON, it +can be used with a ``then()`` callback chain. + +.. code-block:: javascript + + const room_url = {{ url_for("room_detail", id=room.id)|tojson }} + fetch(room_url) + .then(response => response.json()) + .then(data => { + // data is a parsed JSON object + }) + +To send data, use a data method such as POST, and pass the ``body`` +option. The most common types for data are form data or JSON data. + +To send form data, pass a populated |FormData|_ object. This uses the +same format as an HTML form, and would be accessed with ``request.form`` +in a Flask view. + +.. code-block:: javascript + + let data = new FormData() + data.append("name", "Flask Room") + data.append("description", "Talk about Flask here.") + fetch(room_url, { + "method": "POST", + "body": data, + }).then(...) + +In general, prefer sending request data as form data, as would be used +when submitting an HTML form. JSON can represent more complex data, but +unless you need that it's better to stick with the simpler format. When +sending JSON data, the ``Content-Type: application/json`` header must be +sent as well, otherwise Flask will return a 415 Unsupported Media Type +error. + +.. code-block:: javascript + + let data = { + "name": "Flask Room", + "description": "Talk about Flask here.", + } + fetch(room_url, { + "method": "POST", + "headers": {"Content-Type": "application/json"}, + "body": JSON.stringify(data), + }).then(...) + +.. |Promise| replace:: ``Promise`` +.. _Promise: https://developer.mozilla.org/Web/JavaScript/Reference/Global_Objects/Promise +.. |FormData| replace:: ``FormData`` +.. _FormData: https://developer.mozilla.org/en-US/docs/Web/API/FormData + + +Following Redirects +------------------- + +A response might be a redirect, for example if you logged in with +JavaScript instead of a traditional HTML form, and your view returned +a redirect instead of JSON. JavaScript requests do follow redirects, but +they don't change the page. If you want to make the page change you can +inspect the response and apply the redirect manually. + +.. code-block:: javascript + + fetch("/login", {"body": ...}).then( + response => { + if (response.redirected) { + window.location = response.url + } else { + showLoginError() + } + } + ) + + +Replacing Content +----------------- + +A response might be new HTML, either a new section of the page to add or +replace, or an entirely new page. In general, if you're returning the +entire page, it would be better to handle that with a redirect as shown +in the previous section. The following example shows how to replace a +``
`` with the HTML returned by a request. + +.. code-block:: html + +
+ {{ include "geology_fact.html" }} +
+ + + +Return JSON from Views +---------------------- + +To return a JSON object from your API view, you can directly return a +dict from the view. It will be serialized to JSON automatically. + +.. code-block:: python + + @app.route("/user/") + def user_detail(id): + user = User.query.get_or_404(id) + return { + "username": User.username, + "email": User.email, + "picture": url_for("static", filename=f"users/{id}/profile.png"), + } + +If you want to return another JSON type, use the +:func:`~flask.json.jsonify` function, which creates a response object +with the given data serialized to JSON. + +.. code-block:: python + + from flask import jsonify + + @app.route("/users") + def user_list(): + users = User.query.order_by(User.name).all() + return jsonify([u.to_json() for u in users]) + +It is usually not a good idea to return file data in a JSON response. +JSON cannot represent binary data directly, so it must be base64 +encoded, which can be slow, takes more bandwidth to send, and is not as +easy to cache. Instead, serve files using one view, and generate a URL +to the desired file to include in the JSON. Then the client can make a +separate request to get the linked resource after getting the JSON. + + +Receiving JSON in Views +----------------------- + +Use the :attr:`~flask.Request.json` property of the +:data:`~flask.request` object to decode the request's body as JSON. If +the body is not valid JSON, a 400 Bad Request error will be raised. If +the ``Content-Type`` header is not set to ``application/json``, a 415 +Unsupported Media Type error will be raised. + +.. code-block:: python + + from flask import request + + @app.post("/user/") + def user_update(id): + user = User.query.get_or_404(id) + user.update_from_json(request.json) + db.session.commit() + return user.to_json() diff --git a/docs/patterns/jquery.rst b/docs/patterns/jquery.rst index 0a75bb71..7ac6856e 100644 --- a/docs/patterns/jquery.rst +++ b/docs/patterns/jquery.rst @@ -1,148 +1,6 @@ +:orphan: + AJAX with jQuery ================ -`jQuery`_ is a small JavaScript library commonly used to simplify working -with the DOM and JavaScript in general. It is the perfect tool to make -web applications more dynamic by exchanging JSON between server and -client. - -JSON itself is a very lightweight transport format, very similar to how -Python primitives (numbers, strings, dicts and lists) look like which is -widely supported and very easy to parse. It became popular a few years -ago and quickly replaced XML as transport format in web applications. - -.. _jQuery: https://jquery.com/ - -Loading jQuery --------------- - -In order to use jQuery, you have to download it first and place it in the -static folder of your application and then ensure it's loaded. Ideally -you have a layout template that is used for all pages where you just have -to add a script statement to the bottom of your ```` to load jQuery: - -.. sourcecode:: html - - - -Another method is using Google's `AJAX Libraries API -`_ to load jQuery: - -.. sourcecode:: html - - - - -In this case you have to put jQuery into your static folder as a fallback, but it will -first try to load it directly from Google. This has the advantage that your -website will probably load faster for users if they went to at least one -other website before using the same jQuery version from Google because it -will already be in the browser cache. - -Where is My Site? ------------------ - -Do you know where your application is? If you are developing the answer -is quite simple: it's on localhost port something and directly on the root -of that server. But what if you later decide to move your application to -a different location? For example to ``http://example.com/myapp``? On -the server side this never was a problem because we were using the handy -:func:`~flask.url_for` function that could answer that question for -us, but if we are using jQuery we should not hardcode the path to -the application but make that dynamic, so how can we do that? - -A simple method would be to add a script tag to our page that sets a -global variable to the prefix to the root of the application. Something -like this: - -.. sourcecode:: html+jinja - - - - -JSON View Functions -------------------- - -Now let's create a server side function that accepts two URL arguments of -numbers which should be added together and then sent back to the -application in a JSON object. This is a really ridiculous example and is -something you usually would do on the client side alone, but a simple -example that shows how you would use jQuery and Flask nonetheless:: - - from flask import Flask, jsonify, render_template, request - app = Flask(__name__) - - @app.route('/_add_numbers') - def add_numbers(): - a = request.args.get('a', 0, type=int) - b = request.args.get('b', 0, type=int) - return jsonify(result=a + b) - - @app.route('/') - def index(): - return render_template('index.html') - -As you can see I also added an `index` method here that renders a -template. This template will load jQuery as above and have a little form where -we can add two numbers and a link to trigger the function on the server -side. - -Note that we are using the :meth:`~werkzeug.datastructures.MultiDict.get` method here -which will never fail. If the key is missing a default value (here ``0``) -is returned. Furthermore it can convert values to a specific type (like -in our case `int`). This is especially handy for code that is -triggered by a script (APIs, JavaScript etc.) because you don't need -special error reporting in that case. - -The HTML --------- - -Your index.html template either has to extend a :file:`layout.html` template with -jQuery loaded and the `$SCRIPT_ROOT` variable set, or do that on the top. -Here's the HTML code needed for our little application (:file:`index.html`). -Notice that we also drop the script directly into the HTML here. It is -usually a better idea to have that in a separate script file: - -.. sourcecode:: html - - -

jQuery Example

-

+ - = - ? -

calculate server side - -I won't go into detail here about how jQuery works, just a very quick -explanation of the little bit of code above: - -1. ``$(function() { ... })`` specifies code that should run once the - browser is done loading the basic parts of the page. -2. ``$('selector')`` selects an element and lets you operate on it. -3. ``element.bind('event', func)`` specifies a function that should run - when the user clicked on the element. If that function returns - `false`, the default behavior will not kick in (in this case, navigate - to the `#` URL). -4. ``$.getJSON(url, data, func)`` sends a ``GET`` request to `url` and will - send the contents of the `data` object as query parameters. Once the - data arrived, it will call the given function with the return value as - argument. Note that we can use the `$SCRIPT_ROOT` variable here that - we set earlier. - -Check out the :gh:`example source ` for a full -application demonstrating the code on this page, as well as the same -thing using ``XMLHttpRequest`` and ``fetch``. +Obsolete, see :doc:`/patterns/javascript` instead. diff --git a/docs/patterns/mongoengine.rst b/docs/patterns/mongoengine.rst index 015e7b61..8d49de7c 100644 --- a/docs/patterns/mongoengine.rst +++ b/docs/patterns/mongoengine.rst @@ -10,8 +10,7 @@ A running MongoDB server and `Flask-MongoEngine`_ are required. :: pip install flask-mongoengine .. _MongoEngine: http://mongoengine.org -.. _Flask-MongoEngine: https://flask-mongoengine.readthedocs.io - +.. _Flask-MongoEngine: https://docs.mongoengine.org/projects/flask-mongoengine/en/latest/ Configuration ------------- @@ -80,7 +79,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/patterns/packages.rst b/docs/patterns/packages.rst index 7c3a34cf..90fa8a8f 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -42,84 +42,34 @@ You should then end up with something like that:: But how do you run your application now? The naive ``python yourapplication/__init__.py`` will not work. Let's just say that Python does not want modules in packages to be the startup file. But that is not -a big problem, just add a new file called :file:`setup.py` next to the inner -:file:`yourapplication` folder with the following contents:: +a big problem, just add a new file called :file:`pyproject.toml` next to the inner +:file:`yourapplication` folder with the following contents: - from setuptools import setup +.. code-block:: toml - setup( - name='yourapplication', - packages=['yourapplication'], - include_package_data=True, - install_requires=[ - 'flask', - ], - ) + [project] + name = "yourapplication" + dependencies = [ + "flask", + ] -In order to run the application you need to export an environment variable -that tells Flask where to find the application instance: + [build-system] + requires = ["flit_core<4"] + build-backend = "flit_core.buildapi" -.. tabs:: +Install your application so it is importable: - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_APP=yourapplication - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_APP yourapplication - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_APP=yourapplication - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_APP = "yourapplication" - -If you are outside of the project directory make sure to provide the exact -path to your application directory. Similarly you can turn on the -development features like this: - -.. tabs:: - - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_ENV=development - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_ENV development - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_ENV=development - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_ENV = "development" - -In order to install and run the application you need to issue the following -commands:: +.. code-block:: text $ pip install -e . - $ flask run + +To use the ``flask`` command and run your application you need to set +the ``--app`` option that tells Flask where to find the application +instance: + +.. code-block:: text + + $ flask --app yourapplication run What did we gain from this? Now we can restructure the application a bit into multiple modules. The only thing you have to remember is the @@ -151,7 +101,7 @@ And this is what :file:`views.py` would look like:: You should then end up with something like that:: /yourapplication - setup.py + pyproject.toml /yourapplication __init__.py views.py @@ -173,10 +123,6 @@ You should then end up with something like that:: ensuring the module is imported and we are doing that at the bottom of the file. - There are still some problems with that approach but if you want to use - decorators there is no way around that. Check out the - :doc:`/becomingbig` section for some inspiration how to deal with that. - Working with Blueprints ----------------------- diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 734d550c..9e9afe48 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -34,8 +34,7 @@ official documentation on the `declarative`_ extension. Here's the example :file:`database.py` module for your application:: from sqlalchemy import create_engine - from sqlalchemy.orm import scoped_session, sessionmaker - from sqlalchemy.ext.declarative import declarative_base + from sqlalchemy.orm import scoped_session, sessionmaker, declarative_base engine = create_engine('sqlite:////tmp/test.db') db_session = scoped_session(sessionmaker(autocommit=False, @@ -132,9 +131,8 @@ Here is an example :file:`database.py` module for your application:: def init_db(): metadata.create_all(bind=engine) -As in the declarative approach, you need to close the session after -each request or application context shutdown. Put this into your -application module:: +As in the declarative approach, you need to close the session after each app +context. Put this into your application module:: from yourapplication.database import db_session diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 12336fb1..f42e0f8e 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -1,9 +1,9 @@ Using SQLite 3 with Flask ========================= -In Flask you can easily implement the opening of database connections on -demand and closing them when the context dies (usually at the end of the -request). +You can implement a few functions to work with a SQLite connection during a +request context. The connection is created the first time it's accessed, +reused on subsequent access, until it is closed when the request context ends. Here is a simple example of how you can use SQLite 3 with Flask:: @@ -30,10 +30,6 @@ or create an application context itself. At that point the ``get_db`` function can be used to get the current database connection. Whenever the context is destroyed the database connection will be terminated. -Note: if you use Flask 0.9 or older you need to use -``flask._app_ctx_stack.top`` instead of ``g`` as the :data:`flask.g` -object was bound to the request and not application context. - Example:: @app.route('/') diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst index e8571ffd..fc2f1739 100644 --- a/docs/patterns/streaming.rst +++ b/docs/patterns/streaming.rst @@ -8,6 +8,21 @@ roundtrip to the filesystem? The answer is by using generators and direct responses. +HTTP Response Behavior +---------------------- + +**Headers cannot be changed after the streaming response starts.** + +When using streaming, it's important to be aware of the order than an HTTP +response is sent. All headers must be sent first, then the body. More headers +cannot be sent after the body has begun. Therefore, you must make sure all +headers are set before starting the response, outside the generator. + +In particular, if the generator will access ``session``, be sure to do so in the +view as well so that the ``Vary: cookie`` header will be set. Do not modify the +session in the generator, as the ``Set-Cookie`` header will already be sent. + + Basic Usage ----------- @@ -20,7 +35,7 @@ data and to then invoke that function and pass it to a response object:: def generate(): for row in iter_all_rows(): yield f"{','.join(row)}\n" - return app.response_class(generate(), mimetype='text/csv') + return generate(), {"Content-Type": "text/csv"} Each ``yield`` expression is directly sent to the browser. Note though that some WSGI middlewares might break streaming, so be careful there in @@ -29,52 +44,57 @@ debug environments with profilers and other things you might have enabled. Streaming from Templates ------------------------ -The Jinja2 template engine also supports rendering templates piece by -piece. This functionality is not directly exposed by Flask because it is -quite uncommon, but you can easily do it yourself:: +The Jinja template engine supports rendering a template piece by +piece, returning an iterator of strings. Flask provides the +:func:`~flask.stream_template` and :func:`~flask.stream_template_string` +functions to make this easier to use. - def stream_template(template_name, **context): - app.update_template_context(context) - t = app.jinja_env.get_template(template_name) - rv = t.stream(context) - rv.enable_buffering(5) - return rv +.. code-block:: python - @app.route('/my-large-page.html') - def render_large_template(): - rows = iter_all_rows() - return app.response_class(stream_template('the_template.html', rows=rows)) + from flask import stream_template + + @app.get("/timeline") + def timeline(): + return stream_template("timeline.html") + +The parts yielded by the render stream tend to match statement blocks in +the template. -The trick here is to get the template object from the Jinja2 environment -on the application and to call :meth:`~jinja2.Template.stream` instead of -:meth:`~jinja2.Template.render` which returns a stream object instead of a -string. Since we're bypassing the Flask template render functions and -using the template object itself we have to make sure to update the render -context ourselves by calling :meth:`~flask.Flask.update_template_context`. -The template is then evaluated as the stream is iterated over. Since each -time you do a yield the server will flush the content to the client you -might want to buffer up a few items in the template which you can do with -``rv.enable_buffering(size)``. ``5`` is a sane default. Streaming with Context ---------------------- -.. versionadded:: 0.9 +The :data:`.request` proxy will not be active while the generator is +running, because the app has already returned control to the WSGI server at that +point. If you try to access ``request``, you'll get a ``RuntimeError``. -Note that when you stream data, the request context is already gone the -moment the function executes. Flask 0.9 provides you with a helper that -can keep the request context around during the execution of the -generator:: +If your generator function relies on data in ``request``, use the +:func:`.stream_with_context` wrapper. This will keep the request context active +during the generator. + +.. code-block:: python from flask import stream_with_context, request + from markupsafe import escape @app.route('/stream') def streamed_response(): def generate(): - yield 'Hello ' - yield request.args['name'] - yield '!' - return app.response_class(stream_with_context(generate())) + yield '

Hello ' + yield escape(request.args['name']) + yield '!

' + return stream_with_context(generate()) -Without the :func:`~flask.stream_with_context` function you would get a -:class:`RuntimeError` at that point. +It can also be used as a decorator. + +.. code-block:: python + + @stream_with_context + def generate(): + ... + + return generate() + +The :func:`~flask.stream_template` and +:func:`~flask.stream_template_string` functions automatically +use :func:`~flask.stream_with_context` if a request is active. diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 3d626f50..cb1208fe 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -99,7 +99,7 @@ WTForm's field function, which renders the field for us. The keyword arguments will be inserted as HTML attributes. So, for example, you can call ``render_field(form.username, class='username')`` to add a class to the input element. Note that WTForms returns standard Python strings, -so we have to tell Jinja2 that this data is already HTML-escaped with +so we have to tell Jinja that this data is already HTML-escaped with the ``|safe`` filter. Here is the :file:`register.html` template for the function we used above, which diff --git a/docs/quickstart.rst b/docs/quickstart.rst index a6956c32..712ba977 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -39,50 +39,20 @@ Save it as :file:`hello.py` or something similar. Make sure to not call your application :file:`flask.py` because this would conflict with Flask itself. -To run the application, use the :command:`flask` command or -:command:`python -m flask`. Before you can do that you need -to tell your terminal the application to work with by exporting the -``FLASK_APP`` environment variable: +To run the application, use the ``flask`` command or +``python -m flask``. You need to tell the Flask where your application +is with the ``--app`` option. -.. tabs:: +.. code-block:: text - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_APP=hello - $ flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_APP hello - $ flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_APP=hello - > flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_APP = "hello" - > flask run - * Running on http://127.0.0.1:5000/ + $ flask --app hello run + * Serving Flask app 'hello' + * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) .. admonition:: Application Discovery Behavior As a shortcut, if the file is named ``app.py`` or ``wsgi.py``, you - don't have to set the ``FLASK_APP`` environment variable. See - :doc:`/cli` for more details. + don't have to use ``--app``. See :doc:`/cli` for more details. This launches a very simple builtin server, which is good enough for testing but probably not what you want to use in production. For @@ -114,34 +84,6 @@ handle that. This tells your operating system to listen on all public IPs. -What to do if the Server does not Start ---------------------------------------- - -In case the :command:`python -m flask` fails or :command:`flask` -does not exist, there are multiple reasons this might be the case. -First of all you need to look at the error message. - -Old Version of Flask -```````````````````` - -Versions of Flask older than 0.11 used to have different ways to start the -application. In short, the :command:`flask` command did not exist, and -neither did :command:`python -m flask`. In that case you have two options: -either upgrade to newer Flask versions or have a look at :doc:`/server` -to see the alternative method for running a server. - -Invalid Import Name -``````````````````` - -The ``FLASK_APP`` environment variable is the name of the module to import at -:command:`flask run`. In case that module is incorrectly named you will get an -import error upon start (or if debug is enabled when you navigate to the -application). It will tell you what it tried to import and why it failed. - -The most common reason is a typo or because you did not actually create an -``app`` object. - - Debug Mode ---------- @@ -162,43 +104,21 @@ error occurs during a request. security risk. Do not run the development server or debugger in a production environment. -To enable all development features, set the ``FLASK_ENV`` environment -variable to ``development`` before calling ``flask run``. +To enable debug mode, use the ``--debug`` option. -.. tabs:: +.. code-block:: text - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_ENV=development - $ flask run - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_ENV development - $ flask run - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_ENV=development - > flask run - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_ENV = "development" - > flask run + $ flask --app hello run --debug + * Serving Flask app 'hello' + * Debug mode: on + * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + * Debugger PIN: nnn-nnn-nnn See also: -- :doc:`/server` and :doc:`/cli` for information about running in - development mode. +- :doc:`/server` and :doc:`/cli` for information about running in debug mode. - :doc:`/debugging` for information about using the built-in debugger and other debuggers. - :doc:`/logging` and :doc:`/errorhandling` to log errors and display @@ -219,18 +139,16 @@ how you're using untrusted data. .. code-block:: python + from flask import request from markupsafe import escape - @app.route("/") - def hello(name): + @app.route("/hello") + def hello(): + name = request.args.get("name", "Flask") return f"Hello, {escape(name)}!" -If a user managed to submit the name ````, -escaping causes it to be rendered as text, rather than running the -script in the user's browser. - -```` in the route captures a value from the URL and passes it to -the view function. These variable rules are explained below. +If a user submits ``/hello?name=``, escaping causes +it to be rendered as text, rather than running the script in the user's browser. Routing @@ -340,7 +258,7 @@ Why would you want to build URLs using the URL reversing function For example, here we use the :meth:`~flask.Flask.test_request_context` method to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context` tells Flask to behave as though it's handling a request even while we use a -Python shell. See :ref:`context-locals`. +Python shell. See :doc:`/appcontext`. .. code-block:: python @@ -390,6 +308,24 @@ of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods. else: return show_the_login_form() +The example above keeps all methods for the route within one function, +which can be useful if each part uses some common data. + +You can also separate views for different methods into different +functions. Flask provides a shortcut for decorating such routes with +:meth:`~flask.Flask.get`, :meth:`~flask.Flask.post`, etc. for each +common HTTP method. + +.. code-block:: python + + @app.get('/login') + def login_get(): + return show_the_login_form() + + @app.post('/login') + def login_post(): + return do_the_login() + If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method and handles ``HEAD`` requests according to the `HTTP RFC`_. Likewise, ``OPTIONS`` is automatically implemented for you. @@ -416,9 +352,17 @@ Rendering Templates Generating HTML from within Python is not fun, and actually pretty cumbersome because you have to do the HTML escaping on your own to keep -the application secure. Because of that Flask configures the `Jinja2 +the application secure. Because of that Flask configures the `Jinja `_ template engine for you automatically. +Templates can be used to generate any type of text file. For web applications, you'll +primarily be generating HTML pages, but you can also generate markdown, plain text for +emails, and anything else. + +For a reference to HTML, CSS, and other web APIs, use the `MDN Web Docs`_. + +.. _MDN Web Docs: https://developer.mozilla.org/ + To render a template you can use the :func:`~flask.render_template` method. All you have to do is provide the name of the template and the variables you want to pass to the template engine as keyword arguments. @@ -429,7 +373,7 @@ Here's a simple example of how to render a template:: @app.route('/hello/') @app.route('/hello/') def hello(name=None): - return render_template('hello.html', name=name) + return render_template('hello.html', person=name) Flask will look for templates in the :file:`templates` folder. So if your application is a module, this folder is next to that module, if it's a @@ -448,8 +392,8 @@ package it's actually inside your package: /templates /hello.html -For templates you can use the full power of Jinja2 templates. Head over -to the official `Jinja2 Template Documentation +For templates you can use the full power of Jinja templates. Head over +to the official `Jinja Template Documentation `_ for more information. Here is an example template: @@ -458,8 +402,8 @@ Here is an example template: Hello from Flask - {% if name %} -

Hello {{ name }}!

+ {% if person %} +

Hello {{ person }}!

{% else %}

Hello, World!

{% endif %} @@ -473,7 +417,7 @@ know how that works, see :doc:`patterns/templateinheritance`. Basically template inheritance makes it possible to keep certain elements on each page (like header, navigation and footer). -Automatic escaping is enabled, so if ``name`` contains HTML it will be escaped +Automatic escaping is enabled, so if ``person`` contains HTML it will be escaped automatically. If you can trust a variable and you know that it will be safe HTML (for example because it came from a module that converts wiki markup to HTML) you can mark it as safe by using the @@ -505,105 +449,58 @@ Here is a basic introduction to how the :class:`~markupsafe.Markup` class works: Accessing Request Data ---------------------- -For web applications it's crucial to react to the data a client sends to -the server. In Flask this information is provided by the global -:class:`~flask.request` object. If you have some experience with Python -you might be wondering how that object can be global and how Flask -manages to still be threadsafe. The answer is context locals: +For web applications it's crucial to react to the data a client sends to the +server. In Flask this information is provided by the global :data:`.request` +object, which is an instance of :class:`.Request`. This object has many +attributes and methods to work with the incoming request data, but here is a +broad overview. First it needs to be imported. - -.. _context-locals: - -Context Locals -`````````````` - -.. admonition:: Insider Information - - If you want to understand how that works and how you can implement - tests with context locals, read this section, otherwise just skip it. - -Certain objects in Flask are global objects, but not of the usual kind. -These objects are actually proxies to objects that are local to a specific -context. What a mouthful. But that is actually quite easy to understand. - -Imagine the context being the handling thread. A request comes in and the -web server decides to spawn a new thread (or something else, the -underlying object is capable of dealing with concurrency systems other -than threads). When Flask starts its internal request handling it -figures out that the current thread is the active context and binds the -current application and the WSGI environments to that context (thread). -It does that in an intelligent way so that one application can invoke another -application without breaking. - -So what does this mean to you? Basically you can completely ignore that -this is the case unless you are doing something like unit testing. You -will notice that code which depends on a request object will suddenly break -because there is no request object. The solution is creating a request -object yourself and binding it to the context. The easiest solution for -unit testing is to use the :meth:`~flask.Flask.test_request_context` -context manager. In combination with the ``with`` statement it will bind a -test request so that you can interact with it. Here is an example:: +.. code-block:: python from flask import request - with app.test_request_context('/hello', method='POST'): - # now you can do something with the request until the - # end of the with block, such as basic assertions: - assert request.path == '/hello' - assert request.method == 'POST' +If you have some experience with Python you might be wondering how that object +can be global when Flask handles multiple requests at a time. The answer is +that :data:`.request` is actually a proxy, pointing at whatever request is +currently being handled by a given worker, which is managed internally by Flask +and Python. See :doc:`/appcontext` for much more information. -The other possibility is passing a whole WSGI environment to the -:meth:`~flask.Flask.request_context` method:: +The current request method is available in the :attr:`~.Request.method` +attribute. To access form data (data transmitted in a ``POST`` or ``PUT`` +request), use the :attr:`~flask.Request.form` attribute, which behaves like a +dict. - with app.request_context(environ): - assert request.method == 'POST' +.. code-block:: python -The Request Object -`````````````````` - -The request object is documented in the API section and we will not cover -it here in detail (see :class:`~flask.Request`). Here is a broad overview of -some of the most common operations. First of all you have to import it from -the ``flask`` module:: - - from flask import request - -The current request method is available by using the -:attr:`~flask.Request.method` attribute. To access form data (data -transmitted in a ``POST`` or ``PUT`` request) you can use the -:attr:`~flask.Request.form` attribute. Here is a full example of the two -attributes mentioned above:: - - @app.route('/login', methods=['POST', 'GET']) + @app.route("/login", methods=["GET", "POST"]) def login(): error = None - if request.method == 'POST': - if valid_login(request.form['username'], - request.form['password']): - return log_the_user_in(request.form['username']) + + if request.method == "POST": + if valid_login(request.form["username"], request.form["password"]): + return store_login(request.form["username"]) else: - error = 'Invalid username/password' - # the code below is executed if the request method - # was GET or the credentials were invalid - return render_template('login.html', error=error) + error = "Invalid username or password" -What happens if the key does not exist in the ``form`` attribute? In that -case a special :exc:`KeyError` is raised. You can catch it like a -standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request -error page is shown instead. So for many situations you don't have to -deal with that problem. + # Executed if the request method was GET or the credentials were invalid. + return render_template("login.html", error=error) -To access parameters submitted in the URL (``?key=value``) you can use the -:attr:`~flask.Request.args` attribute:: +If the key does not exist in ``form``, a special :exc:`KeyError` is raised. You +can catch it like a normal ``KeyError``, otherwise it will return a HTTP 400 +Bad Request error page. You can also use the +:meth:`~werkzeug.datastructures.MultiDict.get` method to get a default +instead of an error. + +To access parameters submitted in the URL (``?key=value``), use the +:attr:`~.Request.args` attribute. Key errors behave the same as ``form``, +returning a 400 response if not caught. + +.. code-block:: python searchword = request.args.get('key', '') -We recommend accessing URL parameters with `get` or by catching the -:exc:`KeyError` because users might change the URL and presenting them a 400 -bad request page in that case is not user friendly. - -For a full list of methods and attributes of the request object, head over -to the :class:`~flask.Request` documentation. +For a full list of methods and attributes of the request object, see the +:class:`~.Request` documentation. File Uploads @@ -740,22 +637,25 @@ The return value from a view function is automatically converted into a response object for you. If the return value is a string it's converted into a response object with the string as response body, a ``200 OK`` status code and a :mimetype:`text/html` mimetype. If the -return value is a dict, :func:`jsonify` is called to produce a response. -The logic that Flask applies to converting return values into response -objects is as follows: +return value is a dict or list, :func:`jsonify` is called to produce a +response. The logic that Flask applies to converting return values into +response objects is as follows: 1. If a response object of the correct type is returned it's directly returned from the view. 2. If it's a string, a response object is created with that data and the default parameters. -3. If it's a dict, a response object is created using ``jsonify``. -4. If a tuple is returned the items in the tuple can provide extra +3. If it's an iterator or generator returning strings or bytes, it is + treated as a streaming response. +4. If it's a dict or list, a response object is created using + :func:`~flask.json.jsonify`. +5. If a tuple is returned the items in the tuple can provide extra information. Such tuples have to be in the form ``(response, status)``, ``(response, headers)``, or ``(response, status, headers)``. The ``status`` value will override the status code and ``headers`` can be a list or dictionary of additional header values. -5. If none of that works, Flask will assume the return value is a +6. If none of that works, Flask will assume the return value is a valid WSGI application and convert that into a response object. If you want to get hold of the resulting response object inside the view @@ -786,8 +686,8 @@ APIs with JSON `````````````` A common response format when writing an API is JSON. It's easy to get -started writing such an API with Flask. If you return a ``dict`` from a -view, it will be converted to a JSON response. +started writing such an API with Flask. If you return a ``dict`` or +``list`` from a view, it will be converted to a JSON response. .. code-block:: python @@ -800,20 +700,20 @@ view, it will be converted to a JSON response. "image": url_for("user_image", filename=user.image), } -Depending on your API design, you may want to create JSON responses for -types other than ``dict``. In that case, use the -:func:`~flask.json.jsonify` function, which will serialize any supported -JSON data type. Or look into Flask community extensions that support -more complex applications. - -.. code-block:: python - - from flask import jsonify - @app.route("/users") def users_api(): users = get_all_users() - return jsonify([user.to_json() for user in users]) + return [user.to_json() for user in users] + +This is a shortcut to passing the data to the +:func:`~flask.json.jsonify` function, which will serialize any supported +JSON data type. That means that all the data in the dict or list must be +JSON serializable. + +For complex types such as database models, you'll want to use a +serialization library to convert the data to valid JSON types first. +There are many serialization libraries and Flask API extensions +maintained by the community that support more complex applications. .. _sessions: diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index b67745ed..6660671e 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -1,265 +1,6 @@ -.. currentmodule:: flask +:orphan: The Request Context =================== -The request context keeps track of the request-level data during a -request. Rather than passing the request object to each function that -runs during a request, the :data:`request` and :data:`session` proxies -are accessed instead. - -This is similar to :doc:`/appcontext`, which keeps track of the -application-level data independent of a request. A corresponding -application context is pushed when a request context is pushed. - - -Purpose of the Context ----------------------- - -When the :class:`Flask` application handles a request, it creates a -:class:`Request` object based on the environment it received from the -WSGI server. Because a *worker* (thread, process, or coroutine depending -on the server) handles only one request at a time, the request data can -be considered global to that worker during that request. Flask uses the -term *context local* for this. - -Flask automatically *pushes* a request context when handling a request. -View functions, error handlers, and other functions that run during a -request will have access to the :data:`request` proxy, which points to -the request object for the current request. - - -Lifetime of the Context ------------------------ - -When a Flask application begins handling a request, it pushes a request -context, which also pushes an :doc:`app context `. When the -request ends it pops the request context then the application context. - -The context is unique to each thread (or other worker type). -:data:`request` cannot be passed to another thread, the other thread -will have a different context stack and will not know about the request -the parent thread was pointing to. - -Context locals are implemented in Werkzeug. See :doc:`werkzeug:local` -for more information on how this works internally. - - -Manually Push a Context ------------------------ - -If you try to access :data:`request`, or anything that uses it, outside -a request context, you'll get this error message: - -.. code-block:: pytb - - RuntimeError: Working outside of request context. - - This typically means that you attempted to use functionality that - needed an active HTTP request. Consult the documentation on testing - for information about how to avoid this problem. - -This should typically only happen when testing code that expects an -active request. One option is to use the -:meth:`test client ` to simulate a full request. Or -you can use :meth:`~Flask.test_request_context` in a ``with`` block, and -everything that runs in the block will have access to :data:`request`, -populated with your test data. :: - - def generate_report(year): - format = request.args.get('format') - ... - - with app.test_request_context( - '/make_report/2017', data={'format': 'short'}): - generate_report() - -If you see that error somewhere else in your code not related to -testing, it most likely indicates that you should move that code into a -view function. - -For information on how to use the request context from the interactive -Python shell, see :doc:`/shell`. - - -How the Context Works ---------------------- - -The :meth:`Flask.wsgi_app` method is called to handle each request. It -manages the contexts during the request. Internally, the request and -application contexts work as stacks, :data:`_request_ctx_stack` and -:data:`_app_ctx_stack`. When contexts are pushed onto the stack, the -proxies that depend on them are available and point at information from -the top context on the stack. - -When the request starts, a :class:`~ctx.RequestContext` is created and -pushed, which creates and pushes an :class:`~ctx.AppContext` first if -a context for that application is not already the top context. While -these contexts are pushed, the :data:`current_app`, :data:`g`, -:data:`request`, and :data:`session` proxies are available to the -original thread handling the request. - -Because the contexts are stacks, other contexts may be pushed to change -the proxies during a request. While this is not a common pattern, it -can be used in advanced applications to, for example, do internal -redirects or chain different applications together. - -After the request is dispatched and a response is generated and sent, -the request context is popped, which then pops the application context. -Immediately before they are popped, the :meth:`~Flask.teardown_request` -and :meth:`~Flask.teardown_appcontext` functions are executed. These -execute even if an unhandled exception occurred during dispatch. - - -.. _callbacks-and-errors: - -Callbacks and Errors --------------------- - -Flask dispatches a request in multiple stages which can affect the -request, response, and how errors are handled. The contexts are active -during all of these stages. - -A :class:`Blueprint` can add handlers for these events that are specific -to the blueprint. The handlers for a blueprint will run if the blueprint -owns the route that matches the request. - -#. Before each request, :meth:`~Flask.before_request` functions are - called. If one of these functions return a value, the other - functions are skipped. The return value is treated as the response - and the view function is not called. - -#. If the :meth:`~Flask.before_request` functions did not return a - response, the view function for the matched route is called and - returns a response. - -#. The return value of the view is converted into an actual response - object and passed to the :meth:`~Flask.after_request` - functions. Each function returns a modified or new response object. - -#. After the response is returned, the contexts are popped, which calls - the :meth:`~Flask.teardown_request` and - :meth:`~Flask.teardown_appcontext` functions. These functions are - called even if an unhandled exception was raised at any point above. - -If an exception is raised before the teardown functions, Flask tries to -match it with an :meth:`~Flask.errorhandler` function to handle the -exception and return a response. If no error handler is found, or the -handler itself raises an exception, Flask returns a generic -``500 Internal Server Error`` response. The teardown functions are still -called, and are passed the exception object. - -If debug mode is enabled, unhandled exceptions are not converted to a -``500`` response and instead are propagated to the WSGI server. This -allows the development server to present the interactive debugger with -the traceback. - - -Teardown Callbacks -~~~~~~~~~~~~~~~~~~ - -The teardown callbacks are independent of the request dispatch, and are -instead called by the contexts when they are popped. The functions are -called even if there is an unhandled exception during dispatch, and for -manually pushed contexts. This means there is no guarantee that any -other parts of the request dispatch have run first. Be sure to write -these functions in a way that does not depend on other callbacks and -will not fail. - -During testing, it can be useful to defer popping the contexts after the -request ends, so that their data can be accessed in the test function. -Use the :meth:`~Flask.test_client` as a ``with`` block to preserve the -contexts until the ``with`` block exits. - -.. code-block:: python - - from flask import Flask, request - - app = Flask(__name__) - - @app.route('/') - def hello(): - print('during view') - return 'Hello, World!' - - @app.teardown_request - def show_teardown(exception): - print('after with block') - - with app.test_request_context(): - print('during with block') - - # teardown functions are called after the context with block exits - - with app.test_client() as client: - client.get('/') - # the contexts are not popped even though the request ended - print(request.path) - - # the contexts are popped and teardown functions are called after - # the client with block exits - -Signals -~~~~~~~ - -If :data:`~signals.signals_available` is true, the following signals are -sent: - -#. :data:`request_started` is sent before the - :meth:`~Flask.before_request` functions are called. - -#. :data:`request_finished` is sent after the - :meth:`~Flask.after_request` functions are called. - -#. :data:`got_request_exception` is sent when an exception begins to - be handled, but before an :meth:`~Flask.errorhandler` is looked up or - called. - -#. :data:`request_tearing_down` is sent after the - :meth:`~Flask.teardown_request` functions are called. - - -Context Preservation on Error ------------------------------ - -At the end of a request, the request context is popped and all data -associated with it is destroyed. If an error occurs during development, -it is useful to delay destroying the data for debugging purposes. - -When the development server is running in development mode (the -``FLASK_ENV`` environment variable is set to ``'development'``), the -error and data will be preserved and shown in the interactive debugger. - -This behavior can be controlled with the -:data:`PRESERVE_CONTEXT_ON_EXCEPTION` config. As described above, it -defaults to ``True`` in the development environment. - -Do not enable :data:`PRESERVE_CONTEXT_ON_EXCEPTION` in production, as it -will cause your application to leak memory on exceptions. - - -.. _notes-on-proxies: - -Notes On Proxies ----------------- - -Some of the objects provided by Flask are proxies to other objects. The -proxies are accessed in the same way for each worker thread, but -point to the unique object bound to each worker behind the scenes as -described on this page. - -Most of the time you don't have to care about that, but there are some -exceptions where it is good to know that this object is actually a proxy: - -- The proxy objects cannot fake their type as the actual object types. - If you want to perform instance checks, you have to do that on the - object being proxied. -- The reference to the proxied object is needed in some situations, - such as sending :doc:`signals` or passing data to a background - thread. - -If you need to access the underlying object that is proxied, use the -:meth:`~werkzeug.local.LocalProxy._get_current_object` method:: - - app = current_app._get_current_object() - my_signal.send(app) +Obsolete, see :doc:`/appcontext` instead. diff --git a/docs/server.rst b/docs/server.rst index f674bcd7..d6beb1d8 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -3,9 +3,9 @@ Development Server ================== -Flask provides a ``run`` command to run the application with a -development server. In development mode, this server provides an -interactive debugger and will reload when code is changed. +Flask provides a ``run`` command to run the application with a development server. In +debug mode, this server provides an interactive debugger and will reload when code is +changed. .. warning:: @@ -18,58 +18,18 @@ interactive debugger and will reload when code is changed. Command Line ------------ -The ``flask run`` command line script is the recommended way to run the -development server. It requires setting the ``FLASK_APP`` environment -variable to point to your application, and ``FLASK_ENV=development`` to -fully enable development mode. +The ``flask run`` CLI command is the recommended way to run the development server. Use +the ``--app`` option to point to your application, and the ``--debug`` option to enable +debug mode. -.. tabs:: +.. code-block:: text - .. group-tab:: Bash + $ flask --app hello run --debug - .. code-block:: text - - $ export FLASK_APP=hello - $ export FLASK_ENV=development - $ flask run - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_APP hello - $ export FLASK_ENV=development - $ flask run - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_APP=hello - > set FLASK_ENV=development - > flask run - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_APP = "hello" - > $env:FLASK_ENV = "development" - > flask run - -This enables the development environment, including the interactive -debugger and reloader, and then starts the server on -http://localhost:5000/. Use ``flask run --help`` to see the available -options, and :doc:`/cli` for detailed instructions about configuring -and using the CLI. - -.. note:: - - Prior to Flask 1.0 the ``FLASK_ENV`` environment variable was not - supported and you needed to enable debug mode by exporting - ``FLASK_DEBUG=1``. This can still be used to control debug mode, but - you should prefer setting the development environment as shown - above. +This enables debug mode, including the interactive debugger and reloader, and then +starts the server on http://localhost:5000/. Use ``flask run --help`` to see the +available options, and :doc:`/cli` for detailed instructions about configuring and using +the CLI. .. _address-already-in-use: @@ -116,44 +76,34 @@ following example shows that process id 6847 is using port 5000. TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING 6847 macOS Monterey and later automatically starts a service that uses port -5000. To disable the service, go to System Preferences, Sharing, and -disable "AirPlay Receiver". +5000. You can choose to disable this service instead of using a different port by +searching for "AirPlay Receiver" in System Settings and toggling it off. -Lazy or Eager Loading -~~~~~~~~~~~~~~~~~~~~~ +Deferred Errors on Reload +~~~~~~~~~~~~~~~~~~~~~~~~~ When using the ``flask run`` command with the reloader, the server will continue to run even if you introduce syntax errors or other initialization errors into the code. Accessing the site will show the interactive debugger for the error, rather than crashing the server. -This feature is called "lazy loading". If a syntax error is already present when calling ``flask run``, it will fail immediately and show the traceback rather than waiting until the site is accessed. This is intended to make errors more visible initially while still allowing the server to handle errors on reload. -To override this behavior and always fail immediately, even on reload, -pass the ``--eager-loading`` option. To always keep the server running, -even on the initial call, pass ``--lazy-loading``. - In Code ------- -As an alternative to the ``flask run`` command, the development server -can also be started from Python with the :meth:`Flask.run` method. This -method takes arguments similar to the CLI options to control the server. -The main difference from the CLI command is that the server will crash -if there are errors when reloading. +The development server can also be started from Python with the :meth:`Flask.run` +method. This method takes arguments similar to the CLI options to control the server. +The main difference from the CLI command is that the server will crash if there are +errors when reloading. ``debug=True`` can be passed to enable debug mode. -``debug=True`` can be passed to enable the debugger and reloader, but -the ``FLASK_ENV=development`` environment variable is still required to -fully enable development mode. - -Place the call in a main block, otherwise it will interfere when trying -to import and run the application with a production server later. +Place the call in a main block, otherwise it will interfere when trying to import and +run the application with a production server later. .. code-block:: python diff --git a/docs/shell.rst b/docs/shell.rst index 7e42e285..d8821e23 100644 --- a/docs/shell.rst +++ b/docs/shell.rst @@ -1,56 +1,37 @@ Working with the Shell ====================== -.. versionadded:: 0.3 +One of the reasons everybody loves Python is the interactive shell. It allows +you to play around with code in real time and immediately get results back. +Flask provides the ``flask shell`` CLI command to start an interactive Python +shell with some setup done to make working with the Flask app easier. -One of the reasons everybody loves Python is the interactive shell. It -basically allows you to execute Python commands in real time and -immediately get results back. Flask itself does not come with an -interactive shell, because it does not require any specific setup upfront, -just import your application and start playing around. +.. code-block:: text -There are however some handy helpers to make playing around in the shell a -more pleasant experience. The main issue with interactive console -sessions is that you're not triggering a request like a browser does which -means that :data:`~flask.g`, :data:`~flask.request` and others are not -available. But the code you want to test might depend on them, so what -can you do? - -This is where some helper functions come in handy. Keep in mind however -that these functions are not only there for interactive shell usage, but -also for unit testing and other situations that require a faked request -context. - -Generally it's recommended that you read :doc:`reqcontext` first. - -Command Line Interface ----------------------- - -Starting with Flask 0.11 the recommended way to work with the shell is the -``flask shell`` command which does a lot of this automatically for you. -For instance the shell is automatically initialized with a loaded -application context. - -For more information see :doc:`/cli`. + $ flask shell Creating a Request Context -------------------------- +``flask shell`` pushes an app context automatically, so :data:`.current_app` and +:data:`.g` are already available. However, there is no HTTP request being +handled in the shell, so :data:`.request` and :data:`.session` are not yet +available. + The easiest way to create a proper request context from the shell is by using the :attr:`~flask.Flask.test_request_context` method which creates us a :class:`~flask.ctx.RequestContext`: >>> ctx = app.test_request_context() -Normally you would use the ``with`` statement to make this request object -active, but in the shell it's easier to use the -:meth:`~flask.ctx.RequestContext.push` and -:meth:`~flask.ctx.RequestContext.pop` methods by hand: +Normally you would use the ``with`` statement to make this context active, but +in the shell it's easier to call :meth:`~.RequestContext.push` and +:meth:`~.RequestContext.pop` manually: >>> ctx.push() -From that point onwards you can work with the request object until you -call `pop`: +From that point onwards you can work with the request object until you call +``pop``: >>> ctx.pop() diff --git a/docs/signals.rst b/docs/signals.rst index 27630de6..7ca81a9d 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -1,33 +1,28 @@ Signals ======= -.. versionadded:: 0.6 +Signals are a lightweight way to notify subscribers of certain events during the +lifecycle of the application and each request. When an event occurs, it emits the +signal, which calls each subscriber. -Starting with Flask 0.6, there is integrated support for signalling in -Flask. This support is provided by the excellent `blinker`_ library and -will gracefully fall back if it is not available. +Signals are implemented by the `Blinker`_ library. See its documentation for detailed +information. Flask provides some built-in signals. Extensions may provide their own. -What are signals? Signals help you decouple applications by sending -notifications when actions occur elsewhere in the core framework or -another Flask extensions. In short, signals allow certain senders to -notify subscribers that something happened. +Many signals mirror Flask's decorator-based callbacks with similar names. For example, +the :data:`.request_started` signal is similar to the :meth:`~.Flask.before_request` +decorator. The advantage of signals over handlers is that they can be subscribed to +temporarily, and can't directly affect the application. This is useful for testing, +metrics, auditing, and more. For example, if you want to know what templates were +rendered at what parts of what requests, there is a signal that will notify you of that +information. -Flask comes with a couple of signals and other extensions might provide -more. Also keep in mind that signals are intended to notify subscribers -and should not encourage subscribers to modify data. You will notice that -there are signals that appear to do the same thing like some of the -builtin decorators do (eg: :data:`~flask.request_started` is very similar -to :meth:`~flask.Flask.before_request`). However, there are differences in -how they work. The core :meth:`~flask.Flask.before_request` handler, for -example, is executed in a specific order and is able to abort the request -early by returning a response. In contrast all signal handlers are -executed in undefined order and do not modify any data. -The big advantage of signals over handlers is that you can safely -subscribe to them for just a split second. These temporary -subscriptions are helpful for unit testing for example. Say you want to -know what templates were rendered as part of a request: signals allow you -to do exactly that. +Core Signals +------------ + +See :ref:`core-signals-list` for a list of all built-in signals. The :doc:`lifecycle` +page also describes the order that signals and decorators execute. + Subscribing to Signals ---------------------- @@ -99,17 +94,12 @@ The example above would then look like this:: ... template, context = templates[0] -.. admonition:: Blinker API Changes - - The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker - with version 1.1. - Creating Signals ---------------- If you want to use signals in your own application, you can use the blinker library directly. The most common use case are named signals in a -custom :class:`~blinker.base.Namespace`.. This is what is recommended +custom :class:`~blinker.base.Namespace`. This is what is recommended most of the time:: from blinker import Namespace @@ -123,12 +113,6 @@ The name for the signal here makes it unique and also simplifies debugging. You can access the name of the signal with the :attr:`~blinker.base.NamedSignal.name` attribute. -.. admonition:: For Extension Developers - - If you are writing a Flask extension and you want to gracefully degrade for - missing blinker installations, you can do so by using the - :class:`flask.signals.Namespace` class. - .. _signals-sending: Sending Signals @@ -160,17 +144,16 @@ function, you can pass ``current_app._get_current_object()`` as sender. Signals and Flask's Request Context ----------------------------------- -Signals fully support :doc:`reqcontext` when receiving signals. -Context-local variables are consistently available between -:data:`~flask.request_started` and :data:`~flask.request_finished`, so you can -rely on :class:`flask.g` and others as needed. Note the limitations described -in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal. +Context-local proxies are available between :data:`~flask.request_started` and +:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others +as needed. Note the limitations described in :ref:`signals-sending` and the +:data:`~flask.request_tearing_down` signal. Decorator Based Signal Subscriptions ------------------------------------ -With Blinker 1.1 you can also easily subscribe to signals by using the new +You can also easily subscribe to signals by using the :meth:`~blinker.base.NamedSignal.connect_via` decorator:: from flask import template_rendered @@ -179,10 +162,5 @@ With Blinker 1.1 you can also easily subscribe to signals by using the new def when_template_rendered(sender, template, context, **extra): print(f'Template {template.name} is rendered with {context}') -Core Signals ------------- - -Take a look at :ref:`core-signals-list` for a list of all builtin signals. - .. _blinker: https://pypi.org/project/blinker/ diff --git a/docs/templating.rst b/docs/templating.rst index dcc757c3..ed4a52ee 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -1,37 +1,37 @@ Templates ========= -Flask leverages Jinja2 as its template engine. You are obviously free to use -a different template engine, but you still have to install Jinja2 to run +Flask leverages Jinja as its template engine. You are obviously free to use +a different template engine, but you still have to install Jinja to run Flask itself. This requirement is necessary to enable rich extensions. -An extension can depend on Jinja2 being present. +An extension can depend on Jinja being present. -This section only gives a very quick introduction into how Jinja2 +This section only gives a very quick introduction into how Jinja is integrated into Flask. If you want information on the template -engine's syntax itself, head over to the official `Jinja2 Template +engine's syntax itself, head over to the official `Jinja Template Documentation `_ for more information. Jinja Setup ----------- -Unless customized, Jinja2 is configured by Flask as follows: +Unless customized, Jinja is configured by Flask as follows: - autoescaping is enabled for all templates ending in ``.html``, - ``.htm``, ``.xml`` as well as ``.xhtml`` when using + ``.htm``, ``.xml``, ``.xhtml``, as well as ``.svg`` when using :func:`~flask.templating.render_template`. - autoescaping is enabled for all strings when using :func:`~flask.templating.render_template_string`. - a template has the ability to opt in/out autoescaping with the ``{% autoescape %}`` tag. - Flask inserts a couple of global functions and helpers into the - Jinja2 context, additionally to the values that are present by + Jinja context, additionally to the values that are present by default. Standard Context ---------------- -The following global variables are available within Jinja2 templates +The following global variables are available within Jinja templates by default: .. data:: config @@ -115,7 +115,7 @@ markdown to HTML converter. There are three ways to accomplish that: -- In the Python code, wrap the HTML string in a :class:`~flask.Markup` +- In the Python code, wrap the HTML string in a :class:`~markupsafe.Markup` object before passing it to the template. This is in general the recommended way. - Inside the template, use the ``|safe`` filter to explicitly mark a @@ -137,32 +137,58 @@ using in this block. .. _registering-filters: -Registering Filters -------------------- +Registering Filters, Tests, and Globals +--------------------------------------- -If you want to register your own filters in Jinja2 you have two ways to do -that. You can either put them by hand into the -:attr:`~flask.Flask.jinja_env` of the application or use the -:meth:`~flask.Flask.template_filter` decorator. +The Flask app and blueprints provide decorators and methods to register your own +filters, tests, and global functions for use in Jinja templates. They all follow +the same pattern, so the following examples only discuss filters. -The two following examples work the same and both reverse an object:: +Decorate a function with :meth:`~.Flask.template_filter` to register it as a +template filter. - @app.template_filter('reverse') - def reverse_filter(s): - return s[::-1] +.. code-block:: python - def reverse_filter(s): - return s[::-1] - app.jinja_env.filters['reverse'] = reverse_filter + @app.template_filter + def reverse(s): + return reversed(s) -In case of the decorator the argument is optional if you want to use the -function name as name of the filter. Once registered, you can use the filter -in your templates in the same way as Jinja2's builtin filters, for example if -you have a Python list in context called `mylist`:: +.. code-block:: jinja - {% for x in mylist | reverse %} + {% for item in data | reverse %} {% endfor %} +By default it will use the name of the function as the name of the filter, but +that can be changed by passing a name to the decorator. + +.. code-block:: python + + @app.template_filter("reverse") + def reverse_filter(s): + return reversed(s) + +A filter can be registered separately using :meth:`~.Flask.add_template_filter`. +The name is optional and will use the function name if not given. + +.. code-block:: python + + def reverse_filter(s): + return reversed(s) + + app.add_template_filter(reverse_filter, "reverse") + +For template tests, use the :meth:`~.Flask.template_test` decorator or +:meth:`~.Flask.add_template_test` method. For template global functions, use the +:meth:`~.Flask.template_global` decorator or :meth:`~.Flask.add_template_global` +method. + +The same methods also exist on :class:`.Blueprint`, prefixed with ``app_`` to +indicate that the registered functions will be available to all templates, not +only when rendering from within the blueprint. + +The Jinja environment is also available as :attr:`~.Flask.jinja_env`. It may be +modified directly, as you would when using Jinja outside Flask. + Context Processors ------------------ @@ -201,3 +227,35 @@ templates:: You could also build `format_price` as a template filter (see :ref:`registering-filters`), but this demonstrates how to pass functions in a context processor. + +Streaming +--------- + +It can be useful to not render the whole template as one complete +string, instead render it as a stream, yielding smaller incremental +strings. This can be used for streaming HTML in chunks to speed up +initial page load, or to save memory when rendering a very large +template. + +The Jinja template engine supports rendering a template piece +by piece, returning an iterator of strings. Flask provides the +:func:`~flask.stream_template` and :func:`~flask.stream_template_string` +functions to make this easier to use. + +.. code-block:: python + + from flask import stream_template + + @app.get("/timeline") + def timeline(): + return stream_template("timeline.html") + +These functions automatically apply the +:func:`~flask.stream_with_context` wrapper if a request is active, so that +:data:`.request`, :data:`.session`, and :data:`.g` remain available in the +template. + +More headers cannot be sent after the body has begun. Therefore, you must +make sure all headers are set before starting the response. In particular, +if the template will access ``session``, be sure to do so in the view as +well so that the ``Vary: cookie`` header will be set. diff --git a/docs/testing.rst b/docs/testing.rst index eaf0d920..c171abd6 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -109,10 +109,8 @@ provides ``response.text``, or use ``response.get_data(as_text=True)``. Pass a dict ``query_string={"key": "value", ...}`` to set arguments in -the query string (after the ``?`` in the URL). You can also pass a -string if you want to set a specific value directly. - -Pass a dict to ``headers={}`` to set request headers. +the query string (after the ``?`` in the URL). Pass a dict +``headers={}`` to set request headers. To send a request body in a POST or PUT request, pass a value to ``data``. If raw bytes are passed, that exact body is used. Usually, @@ -194,7 +192,7 @@ which records the request that produced that response. .. code-block:: python def test_logout_redirect(client): - response = client.get("/logout") + response = client.get("/logout", follow_redirects=True) # Check that there was one redirect response. assert len(response.history) == 1 # Check that the second request was to the index page. @@ -267,21 +265,20 @@ command from the command line. click.echo(f"Hello, {name}!") def test_hello_command(runner): - result = runner.invoke(["hello"]) + result = runner.invoke(args="hello") assert "World" in result.output - result = runner.invoke(["hello", "--name", "Flask"]) + result = runner.invoke(args=["hello", "--name", "Flask"]) assert "Flask" in result.output Tests that depend on an Active Context -------------------------------------- -You may have functions that are called from views or commands, that -expect an active :doc:`application context ` or -:doc:`request context ` because they access ``request``, -``session``, or ``current_app``. Rather than testing them by making a -request or invoking the command, you can create and activate a context +You may have functions that are called from views or commands, that expect an +active :doc:`app context ` because they access :data:`.request`, +:data:`.session`, :data:`.g`, or :data:`.current_app`. Rather than testing them by +making a request or invoking the command, you can create and activate a context directly. Use ``with app.app_context()`` to push an application context. For diff --git a/docs/tutorial/blog.rst b/docs/tutorial/blog.rst index b06329ea..6418f5ff 100644 --- a/docs/tutorial/blog.rst +++ b/docs/tutorial/blog.rst @@ -305,7 +305,7 @@ The pattern ``{{ request.form['title'] or post['title'] }}`` is used to choose what data appears in the form. When the form hasn't been submitted, the original ``post`` data appears, but if invalid form data was posted you want to display that so the user can fix the error, so -``request.form`` is used instead. :data:`request` is another variable +``request.form`` is used instead. :data:`.request` is another variable that's automatically available in templates. diff --git a/docs/tutorial/database.rst b/docs/tutorial/database.rst index b094909e..cf132603 100644 --- a/docs/tutorial/database.rst +++ b/docs/tutorial/database.rst @@ -37,10 +37,10 @@ response is sent. :caption: ``flaskr/db.py`` import sqlite3 + from datetime import datetime import click from flask import current_app, g - from flask.cli import with_appcontext def get_db(): @@ -60,17 +60,17 @@ response is sent. if db is not None: db.close() -:data:`g` is a special object that is unique for each request. It is +:data:`.g` is a special object that is unique for each request. It is used to store data that might be accessed by multiple functions during the request. The connection is stored and reused instead of creating a new connection if ``get_db`` is called a second time in the same request. -:data:`current_app` is another special object that points to the Flask +:data:`.current_app` is another special object that points to the Flask application handling the request. Since you used an application factory, there is no application object when writing the rest of your code. ``get_db`` will be called when the application has been created and is -handling a request, so :data:`current_app` can be used. +handling a request, so :data:`.current_app` can be used. :func:`sqlite3.connect` establishes a connection to the file pointed at by the ``DATABASE`` configuration key. This file doesn't have to exist @@ -128,12 +128,16 @@ Add the Python functions that will run these SQL commands to the @click.command('init-db') - @with_appcontext def init_db_command(): """Clear the existing data and create new tables.""" init_db() click.echo('Initialized the database.') + + sqlite3.register_converter( + "timestamp", lambda v: datetime.fromisoformat(v.decode()) + ) + :meth:`open_resource() ` opens a file relative to the ``flaskr`` package, which is useful since you won't necessarily know where that location is when deploying the application later. ``get_db`` @@ -144,6 +148,10 @@ read from the file. that calls the ``init_db`` function and shows a success message to the user. You can read :doc:`/cli` to learn more about writing commands. +The call to :func:`sqlite3.register_converter` tells Python how to +interpret timestamp values in the database. We convert the value to a +:class:`datetime.datetime`. + Register with the Application ----------------------------- @@ -196,15 +204,13 @@ previous page. If you're still running the server from the previous page, you can either stop the server, or run this command in a new terminal. If you use a new terminal, remember to change to your project directory - and activate the env as described in :doc:`/installation`. You'll - also need to set ``FLASK_APP`` and ``FLASK_ENV`` as shown on the - previous page. + and activate the env as described in :doc:`/installation`. Run the ``init-db`` command: .. code-block:: none - $ flask init-db + $ flask --app flaskr init-db Initialized the database. There will now be a ``flaskr.sqlite`` file in the ``instance`` folder in diff --git a/docs/tutorial/deploy.rst b/docs/tutorial/deploy.rst index 26940240..eb3a53ac 100644 --- a/docs/tutorial/deploy.rst +++ b/docs/tutorial/deploy.rst @@ -14,22 +14,13 @@ application. Build and Install ----------------- -When you want to deploy your application elsewhere, you build a -distribution file. The current standard for Python distribution is the -*wheel* format, with the ``.whl`` extension. Make sure the wheel library -is installed first: +When you want to deploy your application elsewhere, you build a *wheel* +(``.whl``) file. Install and use the ``build`` tool to do this. .. code-block:: none - $ pip install wheel - -Running ``setup.py`` with Python gives you a command line tool to issue -build-related commands. The ``bdist_wheel`` command will build a wheel -distribution file. - -.. code-block:: none - - $ python setup.py bdist_wheel + $ pip install build + $ python -m build --wheel You can find the file in ``dist/flaskr-1.0.0-py3-none-any.whl``. The file name is in the format of {project name}-{version}-{python tag} @@ -48,39 +39,13 @@ Pip will install your project along with its dependencies. Since this is a different machine, you need to run ``init-db`` again to create the database in the instance folder. -.. tabs:: + .. code-block:: text - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_APP=flaskr - $ flask init-db - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_APP flaskr - $ flask init-db - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_APP=flaskr - > flask init-db - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_APP = "flaskr" - > flask init-db + $ flask --app flaskr init-db When Flask detects that it's installed (not in editable mode), it uses a different directory for the instance folder. You can find it at -``venv/var/flaskr-instance`` instead. +``.venv/var/flaskr-instance`` instead. Configure the Secret Key @@ -103,7 +68,7 @@ Create the ``config.py`` file in the instance folder, which the factory will read from if it exists. Copy the generated value into it. .. code-block:: python - :caption: ``venv/var/flaskr-instance/config.py`` + :caption: ``.venv/var/flaskr-instance/config.py`` SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' @@ -127,7 +92,7 @@ first install it in the virtual environment: $ pip install waitress You need to tell Waitress about your application, but it doesn't use -``FLASK_APP`` like ``flask run`` does. You need to tell it to import and +``--app`` like ``flask run`` does. You need to tell it to import and call the application factory to get an application object. .. code-block:: none diff --git a/docs/tutorial/factory.rst b/docs/tutorial/factory.rst index 73081874..381477f9 100644 --- a/docs/tutorial/factory.rst +++ b/docs/tutorial/factory.rst @@ -56,10 +56,7 @@ directory should be treated as a package. app.config.from_mapping(test_config) # ensure the instance folder exists - try: - os.makedirs(app.instance_path) - except OSError: - pass + os.makedirs(app.instance_path, exist_ok=True) # a simple page that says hello @app.route('/hello') @@ -127,59 +124,28 @@ Run The Application Now you can run your application using the ``flask`` command. From the terminal, tell Flask where to find your application, then run it in -development mode. Remember, you should still be in the top-level +debug mode. Remember, you should still be in the top-level ``flask-tutorial`` directory, not the ``flaskr`` package. -Development mode shows an interactive debugger whenever a page raises an +Debug mode shows an interactive debugger whenever a page raises an exception, and restarts the server whenever you make changes to the code. You can leave it running and just reload the browser page as you follow the tutorial. -.. tabs:: +.. code-block:: text - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_APP=flaskr - $ export FLASK_ENV=development - $ flask run - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_APP flaskr - $ set -x FLASK_ENV development - $ flask run - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_APP=flaskr - > set FLASK_ENV=development - > flask run - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_APP = "flaskr" - > $env:FLASK_ENV = "development" - > flask run + $ flask --app flaskr run --debug You'll see output similar to this: -.. code-block:: none +.. code-block:: text * Serving Flask app "flaskr" - * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! - * Debugger PIN: 855-212-761 + * Debugger PIN: nnn-nnn-nnn Visit http://127.0.0.1:5000/hello in a browser and you should see the "Hello, World!" message. Congratulations, you're now running your Flask diff --git a/docs/tutorial/install.rst b/docs/tutorial/install.rst index 3d7d7c60..db83e106 100644 --- a/docs/tutorial/install.rst +++ b/docs/tutorial/install.rst @@ -1,11 +1,10 @@ Make the Project Installable ============================ -Making your project installable means that you can build a -*distribution* file and install that in another environment, just like -you installed Flask in your project's environment. This makes deploying -your project the same as installing any other library, so you're using -all the standard Python tools to manage everything. +Making your project installable means that you can build a *wheel* file and install that +in another environment, just like you installed Flask in your project's environment. +This makes deploying your project the same as installing any other library, so you're +using all the standard Python tools to manage everything. Installing also comes with other benefits that might not be obvious from the tutorial or as a new Python user, including: @@ -28,49 +27,27 @@ the tutorial or as a new Python user, including: Describe the Project -------------------- -The ``setup.py`` file describes your project and the files that belong -to it. +The ``pyproject.toml`` file describes your project and how to build it. -.. code-block:: python - :caption: ``setup.py`` +.. code-block:: toml + :caption: ``pyproject.toml`` - from setuptools import find_packages, setup + [project] + name = "flaskr" + version = "1.0.0" + description = "The basic blog app built in the Flask tutorial." + dependencies = [ + "flask", + ] - setup( - name='flaskr', - version='1.0.0', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=[ - 'flask', - ], - ) + [build-system] + requires = ["flit_core<4"] + build-backend = "flit_core.buildapi" +See the official `Packaging tutorial `_ for more +explanation of the files and options used. -``packages`` tells Python what package directories (and the Python files -they contain) to include. ``find_packages()`` finds these directories -automatically so you don't have to type them out. To include other -files, such as the static and templates directories, -``include_package_data`` is set. Python needs another file named -``MANIFEST.in`` to tell what this other data is. - -.. code-block:: none - :caption: ``MANIFEST.in`` - - include flaskr/schema.sql - graft flaskr/static - graft flaskr/templates - global-exclude *.pyc - -This tells Python to copy everything in the ``static`` and ``templates`` -directories, and the ``schema.sql`` file, but to exclude all bytecode -files. - -See the `official packaging guide`_ for another explanation of the files -and options used. - -.. _official packaging guide: https://packaging.python.org/tutorials/packaging-projects/ +.. _packaging tutorial: https://packaging.python.org/tutorials/packaging-projects/ Install the Project @@ -82,10 +59,10 @@ Use ``pip`` to install your project in the virtual environment. $ pip install -e . -This tells pip to find ``setup.py`` in the current directory and install -it in *editable* or *development* mode. Editable mode means that as you -make changes to your local code, you'll only need to re-install if you -change the metadata about the project, such as its dependencies. +This tells pip to find ``pyproject.toml`` in the current directory and install the +project in *editable* or *development* mode. Editable mode means that as you make +changes to your local code, you'll only need to re-install if you change the metadata +about the project, such as its dependencies. You can observe that the project is now installed with ``pip list``. @@ -102,12 +79,10 @@ You can observe that the project is now installed with ``pip list``. Jinja2 2.10 MarkupSafe 1.0 pip 9.0.3 - setuptools 39.0.1 Werkzeug 0.14.1 - wheel 0.30.0 Nothing changes from how you've been running your project so far. -``FLASK_APP`` is still set to ``flaskr`` and ``flask run`` still runs +``--app`` is still set to ``flaskr`` and ``flask run`` still runs the application, but you can call it from anywhere, not just the ``flask-tutorial`` directory. diff --git a/docs/tutorial/layout.rst b/docs/tutorial/layout.rst index b6a09f03..9f510927 100644 --- a/docs/tutorial/layout.rst +++ b/docs/tutorial/layout.rst @@ -41,7 +41,7 @@ The project directory will contain: * ``flaskr/``, a Python package containing your application code and files. * ``tests/``, a directory containing test modules. -* ``venv/``, a Python virtual environment where Flask and other +* ``.venv/``, a Python virtual environment where Flask and other dependencies are installed. * Installation files telling Python how to install your project. * Version control config, such as `git`_. You should make a habit of @@ -80,9 +80,8 @@ By the end, your project layout will look like this: │ ├── test_db.py │ ├── test_auth.py │ └── test_blog.py - ├── venv/ - ├── setup.py - └── MANIFEST.in + ├── .venv/ + └── pyproject.toml If you're using version control, the following files that are generated while running your project should be ignored. There may be other files @@ -92,7 +91,7 @@ write. For example, with git: .. code-block:: none :caption: ``.gitignore`` - venv/ + .venv/ *.pyc __pycache__/ @@ -103,8 +102,4 @@ write. For example, with git: .coverage htmlcov/ - dist/ - build/ - *.egg-info/ - Continue to :doc:`factory`. diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 1a5535cc..ca9d4b32 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -71,7 +71,7 @@ specific sections. {% block content %}{% endblock %} -:data:`g` is automatically available in templates. Based on if +:data:`.g` is automatically available in templates. Based on if ``g.user`` is set (from ``load_logged_in_user``), either the username and a log out link are displayed, or links to register and log in are displayed. :func:`url_for` is also automatically available, and is diff --git a/docs/tutorial/tests.rst b/docs/tutorial/tests.rst index f97d19df..8958e773 100644 --- a/docs/tutorial/tests.rst +++ b/docs/tutorial/tests.rst @@ -266,7 +266,7 @@ messages. response = client.post( '/auth/register', data={'username': 'a', 'password': 'a'} ) - assert 'http://localhost/auth/login' == response.headers['Location'] + assert response.headers["Location"] == "/auth/login" with app.app_context(): assert get_db().execute( @@ -311,7 +311,7 @@ input and error messages without writing the same code three times. The tests for the ``login`` view are very similar to those for ``register``. Rather than testing the data in the database, -:data:`session` should have ``user_id`` set after logging in. +:data:`.session` should have ``user_id`` set after logging in. .. code-block:: python :caption: ``tests/test_auth.py`` @@ -319,7 +319,7 @@ The tests for the ``login`` view are very similar to those for def test_login(client, auth): assert client.get('/auth/login').status_code == 200 response = auth.login() - assert response.headers['Location'] == 'http://localhost/' + assert response.headers["Location"] == "/" with client: client.get('/') @@ -336,10 +336,10 @@ The tests for the ``login`` view are very similar to those for assert message in response.data Using ``client`` in a ``with`` block allows accessing context variables -such as :data:`session` after the response is returned. Normally, +such as :data:`.session` after the response is returned. Normally, accessing ``session`` outside of a request would raise an error. -Testing ``logout`` is the opposite of ``login``. :data:`session` should +Testing ``logout`` is the opposite of ``login``. :data:`.session` should not contain ``user_id`` after logging out. .. code-block:: python @@ -404,7 +404,7 @@ is returned. If a ``post`` with the given ``id`` doesn't exist, )) def test_login_required(client, path): response = client.post(path) - assert response.headers['Location'] == 'http://localhost/auth/login' + assert response.headers["Location"] == "/auth/login" def test_author_required(app, client, auth): @@ -479,7 +479,7 @@ no longer exist in the database. def test_delete(client, auth, app): auth.login() response = client.post('/1/delete') - assert response.headers['Location'] == 'http://localhost/' + assert response.headers["Location"] == "/" with app.app_context(): db = get_db() @@ -490,20 +490,18 @@ no longer exist in the database. Running the Tests ----------------- -Some extra configuration, which is not required but makes running -tests with coverage less verbose, can be added to the project's -``setup.cfg`` file. +Some extra configuration, which is not required but makes running tests with coverage +less verbose, can be added to the project's ``pyproject.toml`` file. -.. code-block:: none - :caption: ``setup.cfg`` +.. code-block:: toml + :caption: ``pyproject.toml`` - [tool:pytest] - testpaths = tests + [tool.pytest.ini_options] + testpaths = ["tests"] - [coverage:run] - branch = True - source = - flaskr + [tool.coverage.run] + branch = true + source = ["flaskr"] To run the tests, use the ``pytest`` command. It will find and run all the test functions you've written. @@ -514,7 +512,7 @@ the test functions you've written. ========================= test session starts ========================== platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0 - rootdir: /home/user/Projects/flask-tutorial, inifile: setup.cfg + rootdir: /home/user/Projects/flask-tutorial collected 23 items tests/test_auth.py ........ [ 34%] diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 7092dbc2..6626628a 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -208,13 +208,13 @@ There are a few differences from the ``register`` view: password in the same way as the stored hash and securely compares them. If they match, the password is valid. -#. :data:`session` is a :class:`dict` that stores data across requests. +#. :data:`.session` is a :class:`dict` that stores data across requests. When validation succeeds, the user's ``id`` is stored in a new session. The data is stored in a *cookie* that is sent to the browser, and the browser then sends it back with subsequent requests. Flask securely *signs* the data so that it can't be tampered with. -Now that the user's ``id`` is stored in the :data:`session`, it will be +Now that the user's ``id`` is stored in the :data:`.session`, it will be available on subsequent requests. At the beginning of each request, if a user is logged in their information should be loaded and made available to other views. @@ -236,7 +236,7 @@ available to other views. :meth:`bp.before_app_request() ` registers a function that runs before the view function, no matter what URL is requested. ``load_logged_in_user`` checks if a user id is stored in the -:data:`session` and gets that user's data from the database, storing it +:data:`.session` and gets that user's data from the database, storing it on :data:`g.user `, which lasts for the length of the request. If there is no user id, or if the id doesn't exist, ``g.user`` will be ``None``. @@ -245,7 +245,7 @@ there is no user id, or if the id doesn't exist, ``g.user`` will be Logout ------ -To log out, you need to remove the user id from the :data:`session`. +To log out, you need to remove the user id from the :data:`.session`. Then ``load_logged_in_user`` won't load a user on subsequent requests. .. code-block:: python diff --git a/docs/views.rst b/docs/views.rst index 63d26c5c..f2210270 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -1,235 +1,324 @@ -Pluggable Views -=============== +Class-based Views +================= -.. versionadded:: 0.7 +.. currentmodule:: flask.views -Flask 0.7 introduces pluggable views inspired by the generic views from -Django which are based on classes instead of functions. The main -intention is that you can replace parts of the implementations and this -way have customizable pluggable views. +This page introduces using the :class:`View` and :class:`MethodView` +classes to write class-based views. -Basic Principle ---------------- +A class-based view is a class that acts as a view function. Because it +is a class, different instances of the class can be created with +different arguments, to change the behavior of the view. This is also +known as generic, reusable, or pluggable views. -Consider you have a function that loads a list of objects from the -database and renders into a template:: +An example of where this is useful is defining a class that creates an +API based on the database model it is initialized with. - @app.route('/users/') - def show_users(page): +For more complex API behavior and customization, look into the various +API extensions for Flask. + + +Basic Reusable View +------------------- + +Let's walk through an example converting a view function to a view +class. We start with a view function that queries a list of users then +renders a template to show the list. + +.. code-block:: python + + @app.route("/users/") + def user_list(): users = User.query.all() - return render_template('users.html', users=users) + return render_template("users.html", users=users) -This is simple and flexible, but if you want to provide this view in a -generic fashion that can be adapted to other models and templates as well -you might want more flexibility. This is where pluggable class-based -views come into place. As the first step to convert this into a class -based view you would do this:: +This works for the user model, but let's say you also had more models +that needed list pages. You'd need to write another view function for +each model, even though the only thing that would change is the model +and template name. +Instead, you can write a :class:`View` subclass that will query a model +and render a template. As the first step, we'll convert the view to a +class without any customization. + +.. code-block:: python from flask.views import View - class ShowUsers(View): - + class UserList(View): def dispatch_request(self): users = User.query.all() - return render_template('users.html', objects=users) + return render_template("users.html", objects=users) - app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users')) + app.add_url_rule("/users/", view_func=UserList.as_view("user_list")) -As you can see what you have to do is to create a subclass of -:class:`flask.views.View` and implement -:meth:`~flask.views.View.dispatch_request`. Then we have to convert that -class into an actual view function by using the -:meth:`~flask.views.View.as_view` class method. The string you pass to -that function is the name of the endpoint that view will then have. But -this by itself is not helpful, so let's refactor the code a bit:: +The :meth:`View.dispatch_request` method is the equivalent of the view +function. Calling :meth:`View.as_view` method will create a view +function that can be registered on the app with its +:meth:`~flask.Flask.add_url_rule` method. The first argument to +``as_view`` is the name to use to refer to the view with +:func:`~flask.url_for`. +.. note:: - from flask.views import View + You can't decorate the class with ``@app.route()`` the way you'd + do with a basic view function. + +Next, we need to be able to register the same view class for different +models and templates, to make it more useful than the original function. +The class will take two arguments, the model and template, and store +them on ``self``. Then ``dispatch_request`` can reference these instead +of hard-coded values. + +.. code-block:: python class ListView(View): - - def get_template_name(self): - raise NotImplementedError() - - def render_template(self, context): - return render_template(self.get_template_name(), **context) + def __init__(self, model, template): + self.model = model + self.template = template def dispatch_request(self): - context = {'objects': self.get_objects()} - return self.render_template(context) + items = self.model.query.all() + return render_template(self.template, items=items) - class UserView(ListView): +Remember, we create the view function with ``View.as_view()`` instead of +creating the class directly. Any extra arguments passed to ``as_view`` +are then passed when creating the class. Now we can register the same +view to handle multiple models. - def get_template_name(self): - return 'users.html' +.. code-block:: python - def get_objects(self): - return User.query.all() + app.add_url_rule( + "/users/", + view_func=ListView.as_view("user_list", User, "users.html"), + ) + app.add_url_rule( + "/stories/", + view_func=ListView.as_view("story_list", Story, "stories.html"), + ) -This of course is not that helpful for such a small example, but it's good -enough to explain the basic principle. When you have a class-based view -the question comes up what ``self`` points to. The way this works is that -whenever the request is dispatched a new instance of the class is created -and the :meth:`~flask.views.View.dispatch_request` method is called with -the parameters from the URL rule. The class itself is instantiated with -the parameters passed to the :meth:`~flask.views.View.as_view` function. -For instance you can write a class like this:: - class RenderTemplateView(View): - def __init__(self, template_name): - self.template_name = template_name +URL Variables +------------- + +Any variables captured by the URL are passed as keyword arguments to the +``dispatch_request`` method, as they would be for a regular view +function. + +.. code-block:: python + + class DetailView(View): + def __init__(self, model): + self.model = model + self.template = f"{model.__name__.lower()}/detail.html" + + def dispatch_request(self, id) + item = self.model.query.get_or_404(id) + return render_template(self.template, item=item) + + app.add_url_rule( + "/users/", + view_func=DetailView.as_view("user_detail", User) + ) + + +View Lifetime and ``self`` +-------------------------- + +By default, a new instance of the view class is created every time a +request is handled. This means that it is safe to write other data to +``self`` during the request, since the next request will not see it, +unlike other forms of global state. + +However, if your view class needs to do a lot of complex initialization, +doing it for every request is unnecessary and can be inefficient. To +avoid this, set :attr:`View.init_every_request` to ``False``, which will +only create one instance of the class and use it for every request. In +this case, writing to ``self`` is not safe. If you need to store data +during the request, use :data:`~flask.g` instead. + +In the ``ListView`` example, nothing writes to ``self`` during the +request, so it is more efficient to create a single instance. + +.. code-block:: python + + class ListView(View): + init_every_request = False + + def __init__(self, model, template): + self.model = model + self.template = template + def dispatch_request(self): - return render_template(self.template_name) + items = self.model.query.all() + return render_template(self.template, items=items) -And then you can register it like this:: +Different instances will still be created each for each ``as_view`` +call, but not for each request to those views. + + +View Decorators +--------------- + +The view class itself is not the view function. View decorators need to +be applied to the view function returned by ``as_view``, not the class +itself. Set :attr:`View.decorators` to a list of decorators to apply. + +.. code-block:: python + + class UserList(View): + decorators = [cache(minutes=2), login_required] + + app.add_url_rule('/users/', view_func=UserList.as_view()) + +If you didn't set ``decorators``, you could apply them manually instead. +This is equivalent to: + +.. code-block:: python + + view = UserList.as_view("users_list") + view = cache(minutes=2)(view) + view = login_required(view) + app.add_url_rule('/users/', view_func=view) + +Keep in mind that order matters. If you're used to ``@decorator`` style, +this is equivalent to: + +.. code-block:: python + + @app.route("/users/") + @login_required + @cache(minutes=2) + def user_list(): + ... - app.add_url_rule('/about', view_func=RenderTemplateView.as_view( - 'about_page', template_name='about.html')) Method Hints ------------ -Pluggable views are attached to the application like a regular function by -either using :func:`~flask.Flask.route` or better -:meth:`~flask.Flask.add_url_rule`. That however also means that you would -have to provide the names of the HTTP methods the view supports when you -attach this. In order to move that information to the class you can -provide a :attr:`~flask.views.View.methods` attribute that has this -information:: +A common pattern is to register a view with ``methods=["GET", "POST"]``, +then check ``request.method == "POST"`` to decide what to do. Setting +:attr:`View.methods` is equivalent to passing the list of methods to +``add_url_rule`` or ``route``. + +.. code-block:: python class MyView(View): - methods = ['GET', 'POST'] + methods = ["GET", "POST"] def dispatch_request(self): - if request.method == 'POST': + if request.method == "POST": ... ... - app.add_url_rule('/myview', view_func=MyView.as_view('myview')) + app.add_url_rule('/my-view', view_func=MyView.as_view('my-view')) -Method Based Dispatching ------------------------- +This is equivalent to the following, except further subclasses can +inherit or change the methods. -For RESTful APIs it's especially helpful to execute a different function -for each HTTP method. With the :class:`flask.views.MethodView` you can -easily do that. Each HTTP method maps to a method of the class with the -same name (just in lowercase):: +.. code-block:: python + + app.add_url_rule( + "/my-view", + view_func=MyView.as_view("my-view"), + methods=["GET", "POST"], + ) + + +Method Dispatching and APIs +--------------------------- + +For APIs it can be helpful to use a different function for each HTTP +method. :class:`MethodView` extends the basic :class:`View` to dispatch +to different methods of the class based on the request method. Each HTTP +method maps to a method of the class with the same (lowercase) name. + +:class:`MethodView` automatically sets :attr:`View.methods` based on the +methods defined by the class. It even knows how to handle subclasses +that override or define other methods. + +We can make a generic ``ItemAPI`` class that provides get (detail), +patch (edit), and delete methods for a given model. A ``GroupAPI`` can +provide get (list) and post (create) methods. + +.. code-block:: python from flask.views import MethodView - class UserAPI(MethodView): + class ItemAPI(MethodView): + init_every_request = False + + def __init__(self, model): + self.model = model + self.validator = generate_validator(model) + + def _get_item(self, id): + return self.model.query.get_or_404(id) + + def get(self, id): + item = self._get_item(id) + return jsonify(item.to_json()) + + def patch(self, id): + item = self._get_item(id) + errors = self.validator.validate(item, request.json) + + if errors: + return jsonify(errors), 400 + + item.update_from_json(request.json) + db.session.commit() + return jsonify(item.to_json()) + + def delete(self, id): + item = self._get_item(id) + db.session.delete(item) + db.session.commit() + return "", 204 + + class GroupAPI(MethodView): + init_every_request = False + + def __init__(self, model): + self.model = model + self.validator = generate_validator(model, create=True) def get(self): - users = User.query.all() - ... + items = self.model.query.all() + return jsonify([item.to_json() for item in items]) def post(self): - user = User.from_form_data(request.form) - ... + errors = self.validator.validate(request.json) - app.add_url_rule('/users/', view_func=UserAPI.as_view('users')) + if errors: + return jsonify(errors), 400 -That way you also don't have to provide the -:attr:`~flask.views.View.methods` attribute. It's automatically set based -on the methods defined in the class. + db.session.add(self.model.from_json(request.json)) + db.session.commit() + return jsonify(item.to_json()) -Decorating Views ----------------- + def register_api(app, model, name): + item = ItemAPI.as_view(f"{name}-item", model) + group = GroupAPI.as_view(f"{name}-group", model) + app.add_url_rule(f"/{name}/", view_func=item) + app.add_url_rule(f"/{name}/", view_func=group) -Since the view class itself is not the view function that is added to the -routing system it does not make much sense to decorate the class itself. -Instead you either have to decorate the return value of -:meth:`~flask.views.View.as_view` by hand:: + register_api(app, User, "users") + register_api(app, Story, "stories") - def user_required(f): - """Checks whether user is logged in or raises error 401.""" - def decorator(*args, **kwargs): - if not g.user: - abort(401) - return f(*args, **kwargs) - return decorator +This produces the following views, a standard REST API! - view = user_required(UserAPI.as_view('users')) - app.add_url_rule('/users/', view_func=view) - -Starting with Flask 0.8 there is also an alternative way where you can -specify a list of decorators to apply in the class declaration:: - - class UserAPI(MethodView): - decorators = [user_required] - -Due to the implicit self from the caller's perspective you cannot use -regular view decorators on the individual methods of the view however, -keep this in mind. - -Method Views for APIs ---------------------- - -Web APIs are often working very closely with HTTP verbs so it makes a lot -of sense to implement such an API based on the -:class:`~flask.views.MethodView`. That said, you will notice that the API -will require different URL rules that go to the same method view most of -the time. For instance consider that you are exposing a user object on -the web: - -=============== =============== ====================================== -URL Method Description ---------------- --------------- -------------------------------------- -``/users/`` ``GET`` Gives a list of all users -``/users/`` ``POST`` Creates a new user -``/users/`` ``GET`` Shows a single user -``/users/`` ``PUT`` Updates a single user -``/users/`` ``DELETE`` Deletes a single user -=============== =============== ====================================== - -So how would you go about doing that with the -:class:`~flask.views.MethodView`? The trick is to take advantage of the -fact that you can provide multiple rules to the same view. - -Let's assume for the moment the view would look like this:: - - class UserAPI(MethodView): - - def get(self, user_id): - if user_id is None: - # return a list of users - pass - else: - # expose a single user - pass - - def post(self): - # create a new user - pass - - def delete(self, user_id): - # delete a single user - pass - - def put(self, user_id): - # update a single user - pass - -So how do we hook this up with the routing system? By adding two rules -and explicitly mentioning the methods for each:: - - user_view = UserAPI.as_view('user_api') - app.add_url_rule('/users/', defaults={'user_id': None}, - view_func=user_view, methods=['GET',]) - app.add_url_rule('/users/', view_func=user_view, methods=['POST',]) - app.add_url_rule('/users/', view_func=user_view, - methods=['GET', 'PUT', 'DELETE']) - -If you have a lot of APIs that look similar you can refactor that -registration code:: - - def register_api(view, endpoint, url, pk='id', pk_type='int'): - view_func = view.as_view(endpoint) - app.add_url_rule(url, defaults={pk: None}, - view_func=view_func, methods=['GET',]) - app.add_url_rule(url, view_func=view_func, methods=['POST',]) - app.add_url_rule(f'{url}<{pk_type}:{pk}>', view_func=view_func, - methods=['GET', 'PUT', 'DELETE']) - - register_api(UserAPI, 'user_api', '/users/', pk='user_id') +================= ========== =================== +URL Method Description +----------------- ---------- ------------------- +``/users/`` ``GET`` List all users +``/users/`` ``POST`` Create a new user +``/users/`` ``GET`` Show a single user +``/users/`` ``PATCH`` Update a user +``/users/`` ``DELETE`` Delete a user +``/stories/`` ``GET`` List all stories +``/stories/`` ``POST`` Create a new story +``/stories/`` ``GET`` Show a single story +``/stories/`` ``PATCH`` Update a story +``/stories/`` ``DELETE`` Delete a story +================= ========== =================== diff --git a/docs/security.rst b/docs/web-security.rst similarity index 74% rename from docs/security.rst rename to docs/web-security.rst index 777e5112..4118b5ec 100644 --- a/docs/security.rst +++ b/docs/web-security.rst @@ -1,9 +1,43 @@ Security Considerations ======================= -Web applications usually face all kinds of security problems and it's very -hard to get everything right. Flask tries to solve a few of these things -for you, but there are a couple more you have to take care of yourself. +Web applications face many types of potential security problems, and it can be +hard to get everything right, or even to know what "right" is in general. Flask +tries to solve a few of these things by default, but there are other parts you +may have to take care of yourself. Many of these solutions are tradeoffs, and +will depend on each application's specific needs and threat model. Many hosting +platforms may take care of certain types of problems without the need for the +Flask application to handle them. + +Resource Use +------------ + +A common category of attacks is "Denial of Service" (DoS or DDoS). This is a +very broad category, and different variants target different layers in a +deployed application. In general, something is done to increase how much +processing time or memory is used to handle each request, to the point where +there are not enough resources to handle legitimate requests. + +Flask provides a few configuration options to handle resource use. They can +also be set on individual requests to customize only that request. The +documentation for each goes into more detail. + +- :data:`MAX_CONTENT_LENGTH` or :attr:`.Request.max_content_length` controls + how much data will be read from a request. It is not set by default, + although it will still block truly unlimited streams unless the WSGI server + indicates support. +- :data:`MAX_FORM_MEMORY_SIZE` or :attr:`.Request.max_form_memory_size` + controls how large any non-file ``multipart/form-data`` field can be. It is + set to 500kB by default. +- :data:`MAX_FORM_PARTS` or :attr:`.Request.max_form_parts` controls how many + ``multipart/form-data`` fields can be parsed. It is set to 1000 by default. + Combined with the default `max_form_memory_size`, this means that a form + will occupy at most 500MB of memory. + +Regardless of these settings, you should also review what settings are available +from your operating system, container deployment (Docker etc), WSGI server, HTTP +server, and hosting platform. They typically have ways to set process resource +limits, timeouts, and other checks regardless of how Flask is configured. .. _security-xss: @@ -17,13 +51,13 @@ tags. For more information on that have a look at the Wikipedia article on `Cross-Site Scripting `_. -Flask configures Jinja2 to automatically escape all values unless +Flask configures Jinja to automatically escape all values unless explicitly told otherwise. This should rule out all XSS problems caused in templates, but there are still other places where you have to be careful: -- generating HTML without the help of Jinja2 -- calling :class:`~flask.Markup` on data submitted by users +- generating HTML without the help of Jinja +- calling :class:`~markupsafe.Markup` on data submitted by users - sending out HTML from uploaded files, never do that, use the ``Content-Disposition: attachment`` header to prevent that problem. - sending out textfiles from uploaded files. Some browsers are using @@ -31,7 +65,7 @@ careful: trick a browser to execute HTML. Another thing that is very important are unquoted attributes. While -Jinja2 can protect you from XSS issues by escaping HTML, there is one +Jinja can protect you from XSS issues by escaping HTML, there is one thing it cannot protect you from: XSS by attribute injection. To counter this possible attack vector, be sure to always quote your attributes with either double or single quotes when using Jinja expressions in them: @@ -124,7 +158,7 @@ recommend reviewing each of the headers below for use in your application. The `Flask-Talisman`_ extension can be used to manage HTTPS and the security headers for you. -.. _Flask-Talisman: https://github.com/GoogleCloudPlatform/flask-talisman +.. _Flask-Talisman: https://github.com/wntrblm/flask-talisman HTTP Strict Transport Security (HSTS) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -235,17 +269,25 @@ values (or any values that need secure signatures). .. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute -HTTP Public Key Pinning (HPKP) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Host Header Validation +---------------------- -This tells the browser to authenticate with the server using only the specific -certificate key to prevent MITM attacks. +The ``Host`` header is used by the client to indicate what host name the request +was made to. This is used, for example, by ``url_for(..., _external=True)`` to +generate full URLs, for use in email or other messages outside the browser +window. -.. warning:: - Be careful when enabling this, as it is very difficult to undo if you set up - or upgrade your key incorrectly. +By default the app doesn't know what host(s) it is allowed to be accessed +through, and assumes any host is valid. Although browsers do not allow setting +the ``Host`` header, requests made by attackers in other scenarios could set +the ``Host`` header to a value they want. -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning +When deploying your application, set :data:`TRUSTED_HOSTS` to restrict what +values the ``Host`` header may be. + +The ``Host`` header may be modified by proxies in between the client and your +application. See :doc:`deploying/proxy_fix` to tell your app which proxy values +to trust. Copy/Paste to Terminal diff --git a/examples/celery/README.md b/examples/celery/README.md new file mode 100644 index 00000000..038eb51e --- /dev/null +++ b/examples/celery/README.md @@ -0,0 +1,27 @@ +Background Tasks with Celery +============================ + +This example shows how to configure Celery with Flask, how to set up an API for +submitting tasks and polling results, and how to use that API with JavaScript. See +[Flask's documentation about Celery](https://flask.palletsprojects.com/patterns/celery/). + +From this directory, create a virtualenv and install the application into it. Then run a +Celery worker. + +```shell +$ python3 -m venv .venv +$ . ./.venv/bin/activate +$ pip install -r requirements.txt && pip install -e . +$ celery -A make_celery worker --loglevel INFO +``` + +In a separate terminal, activate the virtualenv and run the Flask development server. + +```shell +$ . ./.venv/bin/activate +$ flask -A task_app run --debug +``` + +Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling +requests in the browser dev tools and the Flask logs. You can see the tasks submitting +and completing in the Celery logs. diff --git a/examples/celery/make_celery.py b/examples/celery/make_celery.py new file mode 100644 index 00000000..f7d138e6 --- /dev/null +++ b/examples/celery/make_celery.py @@ -0,0 +1,4 @@ +from task_app import create_app + +flask_app = create_app() +celery_app = flask_app.extensions["celery"] diff --git a/examples/celery/pyproject.toml b/examples/celery/pyproject.toml new file mode 100644 index 00000000..cca36d8c --- /dev/null +++ b/examples/celery/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "flask-example-celery" +version = "1.0.0" +description = "Example Flask application with Celery background tasks." +readme = "README.md" +classifiers = ["Private :: Do Not Upload"] +dependencies = ["flask", "celery[redis]"] + +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.module] +name = "task_app" + +[tool.ruff] +src = ["src"] diff --git a/examples/celery/requirements.txt b/examples/celery/requirements.txt new file mode 100644 index 00000000..29075ab5 --- /dev/null +++ b/examples/celery/requirements.txt @@ -0,0 +1,58 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --resolver=backtracking pyproject.toml +# +amqp==5.1.1 + # via kombu +async-timeout==4.0.2 + # via redis +billiard==3.6.4.0 + # via celery +blinker==1.6.2 + # via flask +celery[redis]==5.2.7 + # via flask-example-celery (pyproject.toml) +click==8.1.3 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl + # flask +click-didyoumean==0.3.0 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.2.0 + # via celery +flask==2.3.2 + # via flask-example-celery (pyproject.toml) +itsdangerous==2.1.2 + # via flask +jinja2==3.1.2 + # via flask +kombu==5.2.4 + # via celery +markupsafe==2.1.2 + # via + # jinja2 + # werkzeug +prompt-toolkit==3.0.38 + # via click-repl +pytz==2023.3 + # via celery +redis==4.5.4 + # via celery +six==1.16.0 + # via click-repl +vine==5.0.0 + # via + # amqp + # celery + # kombu +wcwidth==0.2.6 + # via prompt-toolkit +werkzeug==2.3.3 + # via flask diff --git a/examples/celery/src/task_app/__init__.py b/examples/celery/src/task_app/__init__.py new file mode 100644 index 00000000..dafff8aa --- /dev/null +++ b/examples/celery/src/task_app/__init__.py @@ -0,0 +1,39 @@ +from celery import Celery +from celery import Task +from flask import Flask +from flask import render_template + + +def create_app() -> Flask: + app = Flask(__name__) + app.config.from_mapping( + CELERY=dict( + broker_url="redis://localhost", + result_backend="redis://localhost", + task_ignore_result=True, + ), + ) + app.config.from_prefixed_env() + celery_init_app(app) + + @app.route("/") + def index() -> str: + return render_template("index.html") + + from . import views + + app.register_blueprint(views.bp) + return app + + +def celery_init_app(app: Flask) -> Celery: + class FlaskTask(Task): + def __call__(self, *args: object, **kwargs: object) -> object: + with app.app_context(): + return self.run(*args, **kwargs) + + celery_app = Celery(app.name, task_cls=FlaskTask) + celery_app.config_from_object(app.config["CELERY"]) + celery_app.set_default() + app.extensions["celery"] = celery_app + return celery_app diff --git a/examples/celery/src/task_app/tasks.py b/examples/celery/src/task_app/tasks.py new file mode 100644 index 00000000..b6b3595d --- /dev/null +++ b/examples/celery/src/task_app/tasks.py @@ -0,0 +1,23 @@ +import time + +from celery import shared_task +from celery import Task + + +@shared_task(ignore_result=False) +def add(a: int, b: int) -> int: + return a + b + + +@shared_task() +def block() -> None: + time.sleep(5) + + +@shared_task(bind=True, ignore_result=False) +def process(self: Task, total: int) -> object: + for i in range(total): + self.update_state(state="PROGRESS", meta={"current": i + 1, "total": total}) + time.sleep(1) + + return {"current": total, "total": total} diff --git a/examples/celery/src/task_app/templates/index.html b/examples/celery/src/task_app/templates/index.html new file mode 100644 index 00000000..4e1145cb --- /dev/null +++ b/examples/celery/src/task_app/templates/index.html @@ -0,0 +1,108 @@ + + + + + Celery Example + + +

Celery Example

+Execute background tasks with Celery. Submits tasks and shows results using JavaScript. + +
+

Add

+

Start a task to add two numbers, then poll for the result. +

+
+
+ +
+

Result:

+ +
+

Block

+

Start a task that takes 5 seconds. However, the response will return immediately. +

+ +
+

+ +
+

Process

+

Start a task that counts, waiting one second each time, showing progress. +

+
+ +
+

+ + + + diff --git a/examples/celery/src/task_app/views.py b/examples/celery/src/task_app/views.py new file mode 100644 index 00000000..99cf92dc --- /dev/null +++ b/examples/celery/src/task_app/views.py @@ -0,0 +1,38 @@ +from celery.result import AsyncResult +from flask import Blueprint +from flask import request + +from . import tasks + +bp = Blueprint("tasks", __name__, url_prefix="/tasks") + + +@bp.get("/result/") +def result(id: str) -> dict[str, object]: + result = AsyncResult(id) + ready = result.ready() + return { + "ready": ready, + "successful": result.successful() if ready else None, + "value": result.get() if ready else result.result, + } + + +@bp.post("/add") +def add() -> dict[str, object]: + a = request.form.get("a", type=int) + b = request.form.get("b", type=int) + result = tasks.add.delay(a, b) + return {"result_id": result.id} + + +@bp.post("/block") +def block() -> dict[str, object]: + result = tasks.block.delay() + return {"result_id": result.id} + + +@bp.post("/process") +def process() -> dict[str, object]: + result = tasks.process.delay(total=request.form.get("total", type=int)) + return {"result_id": result.id} diff --git a/examples/javascript/.gitignore b/examples/javascript/.gitignore index 85a35845..a306afbc 100644 --- a/examples/javascript/.gitignore +++ b/examples/javascript/.gitignore @@ -1,4 +1,4 @@ -venv/ +.venv/ *.pyc __pycache__/ instance/ diff --git a/examples/javascript/LICENSE.rst b/examples/javascript/LICENSE.txt similarity index 100% rename from examples/javascript/LICENSE.rst rename to examples/javascript/LICENSE.txt diff --git a/examples/javascript/MANIFEST.in b/examples/javascript/MANIFEST.in deleted file mode 100644 index c730a34e..00000000 --- a/examples/javascript/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include LICENSE.rst -graft js_example/templates -graft tests -global-exclude *.pyc diff --git a/examples/javascript/README.rst b/examples/javascript/README.rst index b25bdb4e..f5f66912 100644 --- a/examples/javascript/README.rst +++ b/examples/javascript/README.rst @@ -3,38 +3,37 @@ JavaScript Ajax Example Demonstrates how to post form data and process a JSON response using JavaScript. This allows making requests without navigating away from the -page. Demonstrates using |XMLHttpRequest|_, |fetch|_, and -|jQuery.ajax|_. See the `Flask docs`_ about jQuery and Ajax. - -.. |XMLHttpRequest| replace:: ``XMLHttpRequest`` -.. _XMLHttpRequest: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest +page. Demonstrates using |fetch|_, |XMLHttpRequest|_, and +|jQuery.ajax|_. See the `Flask docs`_ about JavaScript and Ajax. .. |fetch| replace:: ``fetch`` .. _fetch: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch +.. |XMLHttpRequest| replace:: ``XMLHttpRequest`` +.. _XMLHttpRequest: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest + .. |jQuery.ajax| replace:: ``jQuery.ajax`` .. _jQuery.ajax: https://api.jquery.com/jQuery.ajax/ -.. _Flask docs: https://flask.palletsprojects.com/patterns/jquery/ +.. _Flask docs: https://flask.palletsprojects.com/patterns/javascript/ Install ------- -:: +.. code-block:: text - $ python3 -m venv venv - $ . venv/bin/activate + $ python3 -m venv .venv + $ . .venv/bin/activate $ pip install -e . Run --- -:: +.. code-block:: text - $ export FLASK_APP=js_example - $ flask run + $ flask --app js_example run Open http://127.0.0.1:5000 in a browser. @@ -42,7 +41,7 @@ Open http://127.0.0.1:5000 in a browser. Test ---- -:: +.. code-block:: text $ pip install -e '.[test]' $ coverage run -m pytest diff --git a/examples/javascript/js_example/__init__.py b/examples/javascript/js_example/__init__.py index 068b2d98..0ec3ca21 100644 --- a/examples/javascript/js_example/__init__.py +++ b/examples/javascript/js_example/__init__.py @@ -2,4 +2,4 @@ from flask import Flask app = Flask(__name__) -from js_example import views # noqa: F401 +from js_example import views # noqa: E402, F401 diff --git a/examples/javascript/js_example/templates/base.html b/examples/javascript/js_example/templates/base.html index 50ce0e9c..a4d35bd7 100644 --- a/examples/javascript/js_example/templates/base.html +++ b/examples/javascript/js_example/templates/base.html @@ -1,7 +1,7 @@ JavaScript Example - - + + diff --git a/examples/javascript/js_example/templates/fetch.html b/examples/javascript/js_example/templates/fetch.html index 780ecec5..e2944b85 100644 --- a/examples/javascript/js_example/templates/fetch.html +++ b/examples/javascript/js_example/templates/fetch.html @@ -2,14 +2,11 @@ {% block intro %} fetch - is the new plain JavaScript way to make requests. It's - supported in all modern browsers except IE, which requires a - polyfill. + is the modern plain JavaScript way to make requests. It's + supported in all modern browsers. {% endblock %} {% block script %} - -