├── .devcontainer ├── devcontainer.json └── on-create-command.sh ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ └── feature-request.md ├── pull_request_template.md └── workflows │ ├── lock.yaml │ ├── pre-commit.yaml │ ├── publish.yaml │ └── tests.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGES.rst ├── LICENSE.txt ├── README.md ├── docs ├── Makefile ├── _static │ ├── debugger.png │ ├── flask-horizontal.png │ ├── flask-vertical.png │ ├── pycharm-run-config.png │ └── shortcut-icon.png ├── api.rst ├── appcontext.rst ├── async-await.rst ├── blueprints.rst ├── changes.rst ├── cli.rst ├── conf.py ├── config.rst ├── contributing.rst ├── debugging.rst ├── deploying │ ├── apache-httpd.rst │ ├── asgi.rst │ ├── eventlet.rst │ ├── gevent.rst │ ├── gunicorn.rst │ ├── index.rst │ ├── mod_wsgi.rst │ ├── nginx.rst │ ├── proxy_fix.rst │ ├── uwsgi.rst │ └── waitress.rst ├── design.rst ├── errorhandling.rst ├── extensiondev.rst ├── extensions.rst ├── index.rst ├── installation.rst ├── license.rst ├── lifecycle.rst ├── logging.rst ├── make.bat ├── patterns │ ├── appdispatch.rst │ ├── appfactories.rst │ ├── caching.rst │ ├── celery.rst │ ├── deferredcallbacks.rst │ ├── favicon.rst │ ├── fileuploads.rst │ ├── flashing.rst │ ├── index.rst │ ├── javascript.rst │ ├── jquery.rst │ ├── lazyloading.rst │ ├── methodoverrides.rst │ ├── mongoengine.rst │ ├── packages.rst │ ├── requestchecksum.rst │ ├── singlepageapplications.rst │ ├── sqlalchemy.rst │ ├── sqlite3.rst │ ├── streaming.rst │ ├── subclassing.rst │ ├── templateinheritance.rst │ ├── urlprocessors.rst │ ├── viewdecorators.rst │ └── wtforms.rst ├── quickstart.rst ├── reqcontext.rst ├── server.rst ├── shell.rst ├── signals.rst ├── templating.rst ├── testing.rst ├── tutorial │ ├── blog.rst │ ├── database.rst │ ├── deploy.rst │ ├── factory.rst │ ├── flaskr_edit.png │ ├── flaskr_index.png │ ├── flaskr_login.png │ ├── index.rst │ ├── install.rst │ ├── layout.rst │ ├── next.rst │ ├── static.rst │ ├── templates.rst │ ├── tests.rst │ └── views.rst ├── views.rst └── web-security.rst ├── examples ├── celery │ ├── README.md │ ├── make_celery.py │ ├── pyproject.toml │ ├── requirements.txt │ └── src │ │ └── task_app │ │ ├── __init__.py │ │ ├── tasks.py │ │ ├── templates │ │ └── index.html │ │ └── views.py ├── javascript │ ├── .gitignore │ ├── LICENSE.txt │ ├── README.rst │ ├── js_example │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── base.html │ │ │ ├── fetch.html │ │ │ ├── jquery.html │ │ │ └── xhr.html │ │ └── views.py │ ├── pyproject.toml │ └── tests │ │ ├── conftest.py │ │ └── test_js_example.py └── tutorial │ ├── .gitignore │ ├── LICENSE.txt │ ├── README.rst │ ├── flaskr │ ├── __init__.py │ ├── auth.py │ ├── blog.py │ ├── db.py │ ├── schema.sql │ ├── static │ │ └── style.css │ └── templates │ │ ├── auth │ │ ├── login.html │ │ └── register.html │ │ ├── base.html │ │ └── blog │ │ ├── create.html │ │ ├── index.html │ │ └── update.html │ ├── pyproject.toml │ └── tests │ ├── conftest.py │ ├── data.sql │ ├── test_auth.py │ ├── test_blog.py │ ├── test_db.py │ └── test_factory.py ├── pyproject.toml ├── src └── flask │ ├── __init__.py │ ├── __main__.py │ ├── app.py │ ├── blueprints.py │ ├── cli.py │ ├── config.py │ ├── ctx.py │ ├── debughelpers.py │ ├── globals.py │ ├── helpers.py │ ├── json │ ├── __init__.py │ ├── provider.py │ └── tag.py │ ├── logging.py │ ├── py.typed │ ├── sansio │ ├── README.md │ ├── app.py │ ├── blueprints.py │ └── scaffold.py │ ├── sessions.py │ ├── signals.py │ ├── templating.py │ ├── testing.py │ ├── typing.py │ ├── views.py │ └── wrappers.py ├── tests ├── conftest.py ├── static │ ├── config.json │ ├── config.toml │ └── index.html ├── templates │ ├── _macro.html │ ├── context_template.html │ ├── escaping_template.html │ ├── mail.txt │ ├── nested │ │ └── nested.txt │ ├── non_escaping_template.txt │ ├── simple_template.html │ ├── template_filter.html │ └── template_test.html ├── test_appctx.py ├── test_apps │ ├── .env │ ├── .flaskenv │ ├── blueprintapp │ │ ├── __init__.py │ │ └── apps │ │ │ ├── __init__.py │ │ │ ├── admin │ │ │ ├── __init__.py │ │ │ ├── static │ │ │ │ ├── css │ │ │ │ │ └── test.css │ │ │ │ └── test.txt │ │ │ └── templates │ │ │ │ └── admin │ │ │ │ └── index.html │ │ │ └── frontend │ │ │ ├── __init__.py │ │ │ └── templates │ │ │ └── frontend │ │ │ └── index.html │ ├── cliapp │ │ ├── __init__.py │ │ ├── app.py │ │ ├── factory.py │ │ ├── importerrorapp.py │ │ ├── inner1 │ │ │ ├── __init__.py │ │ │ └── inner2 │ │ │ │ ├── __init__.py │ │ │ │ └── flask.py │ │ ├── message.txt │ │ └── multiapp.py │ ├── helloworld │ │ ├── hello.py │ │ └── wsgi.py │ └── subdomaintestmodule │ │ ├── __init__.py │ │ └── static │ │ └── hello.txt ├── test_async.py ├── test_basic.py ├── test_blueprints.py ├── test_cli.py ├── test_config.py ├── test_converters.py ├── test_helpers.py ├── test_instance_config.py ├── test_json.py ├── test_json_tag.py ├── test_logging.py ├── test_regression.py ├── test_reqctx.py ├── test_request.py ├── test_session_interface.py ├── test_signals.py ├── test_subclassing.py ├── test_templating.py ├── test_testing.py ├── test_user_error_handler.py ├── test_views.py └── type_check │ ├── typing_app_decorators.py │ ├── typing_error_handler.py │ └── typing_route.py └── uv.lock /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pallets/flask", 3 | "image": "mcr.microsoft.com/devcontainers/python:3", 4 | "customizations": { 5 | "vscode": { 6 | "settings": { 7 | "python.defaultInterpreterPath": "${workspaceFolder}/.venv", 8 | "python.terminal.activateEnvInCurrentTerminal": true, 9 | "python.terminal.launchArgs": [ 10 | "-X", 11 | "dev" 12 | ] 13 | } 14 | } 15 | }, 16 | "onCreateCommand": ".devcontainer/on-create-command.sh" 17 | } 18 | -------------------------------------------------------------------------------- /.devcontainer/on-create-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | python3 -m venv --upgrade-deps .venv 4 | . .venv/bin/activate 5 | pip install -r requirements/dev.txt 6 | pip install -e . 7 | pre-commit install --install-hooks 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | charset = utf-8 10 | max_line_length = 88 11 | 12 | [*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in Flask (not other projects which depend on Flask) 4 | --- 5 | 6 | 12 | 13 | 19 | 20 | 23 | 24 | Environment: 25 | 26 | - Python version: 27 | - Flask version: 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Security issue 4 | url: https://github.com/pallets/flask/security/advisories/new 5 | about: Do not report security issues publicly. Create a private advisory. 6 | - name: Questions on GitHub Discussions 7 | url: https://github.com/pallets/flask/discussions/ 8 | about: Ask questions about your own code on the Discussions tab. 9 | - name: Questions on Discord 10 | url: https://discord.gg/pallets 11 | about: Ask questions about your own code on our Discord chat. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for Flask 4 | --- 5 | 6 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /.github/workflows/lock.yaml: -------------------------------------------------------------------------------- 1 | name: Lock inactive closed issues 2 | # Lock closed issues that have not received any further activity for two weeks. 3 | # This does not close open issues, only humans may do that. It is easier to 4 | # respond to new issues with fresh examples rather than continuing discussions 5 | # on old issues. 6 | 7 | on: 8 | schedule: 9 | - cron: '0 0 * * *' 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | discussions: write 14 | concurrency: 15 | group: lock 16 | jobs: 17 | lock: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 21 | with: 22 | issue-inactive-days: 14 23 | pr-inactive-days: 14 24 | discussion-inactive-days: 14 25 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | on: 3 | pull_request: 4 | push: 5 | branches: [main, stable] 6 | jobs: 7 | main: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 11 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 12 | with: 13 | enable-cache: true 14 | prune-cache: false 15 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 16 | id: setup-python 17 | with: 18 | python-version-file: pyproject.toml 19 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 20 | with: 21 | path: ~/.cache/pre-commit 22 | key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }} 23 | - run: uv run --locked --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files 24 | - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 25 | if: ${{ !cancelled() }} 26 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | tags: ['*'] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | outputs: 9 | hash: ${{ steps.hash.outputs.hash }} 10 | steps: 11 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 12 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 13 | with: 14 | enable-cache: true 15 | prune-cache: false 16 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 17 | with: 18 | python-version-file: pyproject.toml 19 | - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV 20 | - run: uv build 21 | - name: generate hash 22 | id: hash 23 | run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT 24 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 25 | with: 26 | path: ./dist 27 | provenance: 28 | needs: [build] 29 | permissions: 30 | actions: read 31 | id-token: write 32 | contents: write 33 | # Can't pin with hash due to how this workflow works. 34 | uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 35 | with: 36 | base64-subjects: ${{ needs.build.outputs.hash }} 37 | create-release: 38 | needs: [provenance] 39 | runs-on: ubuntu-latest 40 | permissions: 41 | contents: write 42 | steps: 43 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 44 | - name: create release 45 | run: > 46 | gh release create --draft --repo ${{ github.repository }} 47 | ${{ github.ref_name }} 48 | *.intoto.jsonl/* artifact/* 49 | env: 50 | GH_TOKEN: ${{ github.token }} 51 | publish-pypi: 52 | needs: [provenance] 53 | environment: 54 | name: publish 55 | url: https://pypi.org/project/Flask/${{ github.ref_name }} 56 | runs-on: ubuntu-latest 57 | permissions: 58 | id-token: write 59 | steps: 60 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 61 | - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 62 | with: 63 | packages-dir: artifact/ 64 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | paths-ignore: ['docs/**', 'README.md'] 5 | push: 6 | branches: [main, stable] 7 | paths-ignore: ['docs/**', 'README.md'] 8 | jobs: 9 | tests: 10 | name: ${{ matrix.name || matrix.python }} 11 | runs-on: ${{ matrix.os || 'ubuntu-latest' }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - {python: '3.13'} 17 | - {name: Windows, python: '3.13', os: windows-latest} 18 | - {name: Mac, python: '3.13', os: macos-latest} 19 | - {python: '3.12'} 20 | - {python: '3.11'} 21 | - {python: '3.10'} 22 | - {name: PyPy, python: 'pypy-3.11', tox: pypy3.11} 23 | - {name: Minimum Versions, python: '3.13', tox: tests-min} 24 | - {name: Development Versions, python: '3.10', tox: tests-dev} 25 | steps: 26 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 27 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 28 | with: 29 | enable-cache: true 30 | prune-cache: false 31 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 32 | with: 33 | python-version: ${{ matrix.python }} 34 | - run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }} 35 | typing: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 39 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 40 | with: 41 | enable-cache: true 42 | prune-cache: false 43 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 44 | with: 45 | python-version-file: pyproject.toml 46 | - name: cache mypy 47 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 48 | with: 49 | path: ./.mypy_cache 50 | key: mypy|${{ hashFiles('pyproject.toml') }} 51 | - run: uv run --locked tox run -e typing 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | __pycache__/ 4 | dist/ 5 | .coverage* 6 | htmlcov/ 7 | .tox/ 8 | docs/_build/ 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: 24e02b24b8ab2b7c76225602d13fa60e12d114e6 # frozen: v0.11.9 4 | hooks: 5 | - id: ruff 6 | - id: ruff-format 7 | - repo: https://github.com/astral-sh/uv-pre-commit 8 | rev: 14ac15b122e538e407d036ff45e3895b7cf4a2bf # frozen: 0.7.3 9 | hooks: 10 | - id: uv-lock 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0 13 | hooks: 14 | - id: check-merge-conflict 15 | - id: debug-statements 16 | - id: fix-byte-order-marker 17 | - id: trailing-whitespace 18 | - id: end-of-file-fixer 19 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-24.04 4 | tools: 5 | python: '3.13' 6 | commands: 7 | - asdf plugin add uv 8 | - asdf install uv latest 9 | - asdf global uv latest 10 | - uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask 2 | 3 | Flask is a lightweight [WSGI] web application framework. It is designed 4 | to make getting started quick and easy, with the ability to scale up to 5 | complex applications. It began as a simple wrapper around [Werkzeug] 6 | and [Jinja], and has become one of the most popular Python web 7 | application frameworks. 8 | 9 | Flask offers suggestions, but doesn't enforce any dependencies or 10 | project layout. It is up to the developer to choose the tools and 11 | libraries they want to use. There are many extensions provided by the 12 | community that make adding new functionality easy. 13 | 14 | [WSGI]: https://wsgi.readthedocs.io/ 15 | [Werkzeug]: https://werkzeug.palletsprojects.com/ 16 | [Jinja]: https://jinja.palletsprojects.com/ 17 | 18 | ## A Simple Example 19 | 20 | ```python 21 | # save this as app.py 22 | from flask import Flask 23 | 24 | app = Flask(__name__) 25 | 26 | @app.route("/") 27 | def hello(): 28 | return "Hello, World!" 29 | ``` 30 | 31 | ``` 32 | $ flask run 33 | * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 34 | ``` 35 | 36 | ## Donate 37 | 38 | The Pallets organization develops and supports Flask and the libraries 39 | it uses. In order to grow the community of contributors and users, and 40 | allow the maintainers to devote more time to the projects, [please 41 | donate today]. 42 | 43 | [please donate today]: https://palletsprojects.com/donate 44 | 45 | ## Contributing 46 | 47 | See our [detailed contributing documentation][contrib] for many ways to 48 | contribute, including reporting issues, requesting features, asking or answering 49 | questions, and making PRs. 50 | 51 | [contrib]: https://palletsprojects.com/contributing/ 52 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/_static/debugger.png -------------------------------------------------------------------------------- /docs/_static/flask-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/_static/flask-horizontal.png -------------------------------------------------------------------------------- /docs/_static/flask-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/_static/flask-vertical.png -------------------------------------------------------------------------------- /docs/_static/pycharm-run-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/_static/pycharm-run-config.png -------------------------------------------------------------------------------- /docs/_static/shortcut-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/_static/shortcut-icon.png -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | .. include:: ../CHANGES.rst 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import packaging.version 2 | from pallets_sphinx_themes import get_version 3 | from pallets_sphinx_themes import ProjectLink 4 | 5 | # Project -------------------------------------------------------------- 6 | 7 | project = "Flask" 8 | copyright = "2010 Pallets" 9 | author = "Pallets" 10 | release, version = get_version("Flask") 11 | 12 | # General -------------------------------------------------------------- 13 | 14 | default_role = "code" 15 | extensions = [ 16 | "sphinx.ext.autodoc", 17 | "sphinx.ext.extlinks", 18 | "sphinx.ext.intersphinx", 19 | "sphinxcontrib.log_cabinet", 20 | "sphinx_tabs.tabs", 21 | "pallets_sphinx_themes", 22 | ] 23 | autodoc_member_order = "bysource" 24 | autodoc_typehints = "description" 25 | autodoc_preserve_defaults = True 26 | extlinks = { 27 | "issue": ("https://github.com/pallets/flask/issues/%s", "#%s"), 28 | "pr": ("https://github.com/pallets/flask/pull/%s", "#%s"), 29 | "ghsa": ("https://github.com/pallets/flask/security/advisories/GHSA-%s", "GHSA-%s"), 30 | } 31 | intersphinx_mapping = { 32 | "python": ("https://docs.python.org/3/", None), 33 | "werkzeug": ("https://werkzeug.palletsprojects.com/", None), 34 | "click": ("https://click.palletsprojects.com/", None), 35 | "jinja": ("https://jinja.palletsprojects.com/", None), 36 | "itsdangerous": ("https://itsdangerous.palletsprojects.com/", None), 37 | "sqlalchemy": ("https://docs.sqlalchemy.org/", None), 38 | "wtforms": ("https://wtforms.readthedocs.io/", None), 39 | "blinker": ("https://blinker.readthedocs.io/", None), 40 | } 41 | 42 | # HTML ----------------------------------------------------------------- 43 | 44 | html_theme = "flask" 45 | html_theme_options = {"index_sidebar_logo": False} 46 | html_context = { 47 | "project_links": [ 48 | ProjectLink("Donate", "https://palletsprojects.com/donate"), 49 | ProjectLink("PyPI Releases", "https://pypi.org/project/Flask/"), 50 | ProjectLink("Source Code", "https://github.com/pallets/flask/"), 51 | ProjectLink("Issue Tracker", "https://github.com/pallets/flask/issues/"), 52 | ProjectLink("Chat", "https://discord.gg/pallets"), 53 | ] 54 | } 55 | html_sidebars = { 56 | "index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"], 57 | "**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"], 58 | } 59 | singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]} 60 | html_static_path = ["_static"] 61 | html_favicon = "_static/shortcut-icon.png" 62 | html_logo = "_static/flask-vertical.png" 63 | html_title = f"Flask Documentation ({version})" 64 | html_show_sourcelink = False 65 | 66 | gettext_uuid = True 67 | gettext_compact = False 68 | 69 | # Local Extensions ----------------------------------------------------- 70 | 71 | 72 | def github_link(name, rawtext, text, lineno, inliner, options=None, content=None): 73 | app = inliner.document.settings.env.app 74 | release = app.config.release 75 | base_url = "https://github.com/pallets/flask/tree/" 76 | 77 | if text.endswith(">"): 78 | words, text = text[:-1].rsplit("<", 1) 79 | words = words.strip() 80 | else: 81 | words = None 82 | 83 | if packaging.version.parse(release).is_devrelease: 84 | url = f"{base_url}main/{text}" 85 | else: 86 | url = f"{base_url}{release}/{text}" 87 | 88 | if words is None: 89 | words = url 90 | 91 | from docutils.nodes import reference 92 | from docutils.parsers.rst.roles import set_classes 93 | 94 | options = options or {} 95 | set_classes(options) 96 | node = reference(rawtext, words, refuri=url, **options) 97 | return [node], [] 98 | 99 | 100 | def setup(app): 101 | app.add_role("gh", github_link) 102 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | See the Pallets `detailed contributing documentation <_contrib>`_ for many ways 5 | to contribute, including reporting issues, requesting features, asking or 6 | answering questions, and making PRs. 7 | 8 | .. _contrib: https://palletsprojects.com/contributing/ 9 | -------------------------------------------------------------------------------- /docs/debugging.rst: -------------------------------------------------------------------------------- 1 | Debugging Application Errors 2 | ============================ 3 | 4 | 5 | In Production 6 | ------------- 7 | 8 | **Do not run the development server, or enable the built-in debugger, in 9 | a production environment.** The debugger allows executing arbitrary 10 | Python code from the browser. It's protected by a pin, but that should 11 | not be relied on for security. 12 | 13 | Use an error logging tool, such as Sentry, as described in 14 | :ref:`error-logging-tools`, or enable logging and notifications as 15 | described in :doc:`/logging`. 16 | 17 | If you have access to the server, you could add some code to start an 18 | external debugger if ``request.remote_addr`` matches your IP. Some IDE 19 | debuggers also have a remote mode so breakpoints on the server can be 20 | interacted with locally. Only enable a debugger temporarily. 21 | 22 | 23 | The Built-In Debugger 24 | --------------------- 25 | 26 | The built-in Werkzeug development server provides a debugger which shows 27 | an interactive traceback in the browser when an unhandled error occurs 28 | during a request. This debugger should only be used during development. 29 | 30 | .. image:: _static/debugger.png 31 | :align: center 32 | :class: screenshot 33 | :alt: screenshot of debugger in action 34 | 35 | .. warning:: 36 | 37 | The debugger allows executing arbitrary Python code from the 38 | browser. It is protected by a pin, but still represents a major 39 | security risk. Do not run the development server or debugger in a 40 | production environment. 41 | 42 | The debugger is enabled by default when the development server is run in debug mode. 43 | 44 | .. code-block:: text 45 | 46 | $ flask --app hello run --debug 47 | 48 | When running from Python code, passing ``debug=True`` enables debug mode, which is 49 | mostly equivalent. 50 | 51 | .. code-block:: python 52 | 53 | app.run(debug=True) 54 | 55 | :doc:`/server` and :doc:`/cli` have more information about running the debugger and 56 | debug mode. More information about the debugger can be found in the `Werkzeug 57 | documentation `__. 58 | 59 | 60 | External Debuggers 61 | ------------------ 62 | 63 | External debuggers, such as those provided by IDEs, can offer a more 64 | powerful debugging experience than the built-in debugger. They can also 65 | be used to step through code during a request before an error is raised, 66 | or if no error is raised. Some even have a remote mode so you can debug 67 | code running on another machine. 68 | 69 | When using an external debugger, the app should still be in debug mode, otherwise Flask 70 | turns unhandled errors into generic 500 error pages. However, the built-in debugger and 71 | reloader should be disabled so they don't interfere with the external debugger. 72 | 73 | .. code-block:: text 74 | 75 | $ flask --app hello run --debug --no-debugger --no-reload 76 | 77 | When running from Python: 78 | 79 | .. code-block:: python 80 | 81 | app.run(debug=True, use_debugger=False, use_reloader=False) 82 | 83 | Disabling these isn't required, an external debugger will continue to work with the 84 | following caveats. 85 | 86 | - If the built-in debugger is not disabled, it will catch unhandled exceptions before 87 | the external debugger can. 88 | - If the reloader is not disabled, it could cause an unexpected reload if code changes 89 | during a breakpoint. 90 | - The development server will still catch unhandled exceptions if the built-in 91 | debugger is disabled, otherwise it would crash on any error. If you want that (and 92 | usually you don't) pass ``passthrough_errors=True`` to ``app.run``. 93 | 94 | .. code-block:: python 95 | 96 | app.run( 97 | debug=True, passthrough_errors=True, 98 | use_debugger=False, use_reloader=False 99 | ) 100 | -------------------------------------------------------------------------------- /docs/deploying/apache-httpd.rst: -------------------------------------------------------------------------------- 1 | Apache httpd 2 | ============ 3 | 4 | `Apache httpd`_ is a fast, production level HTTP server. When serving 5 | your application with one of the WSGI servers listed in :doc:`index`, it 6 | is often good or necessary to put a dedicated HTTP server in front of 7 | it. This "reverse proxy" can handle incoming requests, TLS, and other 8 | security and performance concerns better than the WSGI server. 9 | 10 | httpd can be installed using your system package manager, or a pre-built 11 | executable for Windows. Installing and running httpd itself is outside 12 | the scope of this doc. This page outlines the basics of configuring 13 | httpd to proxy your application. Be sure to read its documentation to 14 | understand what features are available. 15 | 16 | .. _Apache httpd: https://httpd.apache.org/ 17 | 18 | 19 | Domain Name 20 | ----------- 21 | 22 | Acquiring and configuring a domain name is outside the scope of this 23 | doc. In general, you will buy a domain name from a registrar, pay for 24 | server space with a hosting provider, and then point your registrar 25 | at the hosting provider's name servers. 26 | 27 | To simulate this, you can also edit your ``hosts`` file, located at 28 | ``/etc/hosts`` on Linux. Add a line that associates a name with the 29 | local IP. 30 | 31 | Modern Linux systems may be configured to treat any domain name that 32 | ends with ``.localhost`` like this without adding it to the ``hosts`` 33 | file. 34 | 35 | .. code-block:: python 36 | :caption: ``/etc/hosts`` 37 | 38 | 127.0.0.1 hello.localhost 39 | 40 | 41 | Configuration 42 | ------------- 43 | 44 | The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on 45 | Linux. It may be different depending on your operating system. Check the 46 | docs and look for ``httpd.conf``. 47 | 48 | Remove or comment out any existing ``DocumentRoot`` directive. Add the 49 | config lines below. We'll assume the WSGI server is listening locally at 50 | ``http://127.0.0.1:8000``. 51 | 52 | .. code-block:: apache 53 | :caption: ``/etc/httpd/conf/httpd.conf`` 54 | 55 | LoadModule proxy_module modules/mod_proxy.so 56 | LoadModule proxy_http_module modules/mod_proxy_http.so 57 | ProxyPass / http://127.0.0.1:8000/ 58 | RequestHeader set X-Forwarded-Proto http 59 | RequestHeader set X-Forwarded-Prefix / 60 | 61 | The ``LoadModule`` lines might already exist. If so, make sure they are 62 | uncommented instead of adding them manually. 63 | 64 | Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded`` 65 | headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically 66 | set by ``ProxyPass``. 67 | -------------------------------------------------------------------------------- /docs/deploying/asgi.rst: -------------------------------------------------------------------------------- 1 | ASGI 2 | ==== 3 | 4 | If you'd like to use an ASGI server you will need to utilise WSGI to 5 | ASGI middleware. The asgiref 6 | `WsgiToAsgi `_ 7 | adapter is recommended as it integrates with the event loop used for 8 | Flask's :ref:`async_await` support. You can use the adapter by 9 | wrapping the Flask app, 10 | 11 | .. code-block:: python 12 | 13 | from asgiref.wsgi import WsgiToAsgi 14 | from flask import Flask 15 | 16 | app = Flask(__name__) 17 | 18 | ... 19 | 20 | asgi_app = WsgiToAsgi(app) 21 | 22 | and then serving the ``asgi_app`` with the ASGI server, e.g. using 23 | `Hypercorn `_, 24 | 25 | .. sourcecode:: text 26 | 27 | $ hypercorn module:asgi_app 28 | -------------------------------------------------------------------------------- /docs/deploying/eventlet.rst: -------------------------------------------------------------------------------- 1 | eventlet 2 | ======== 3 | 4 | Prefer using :doc:`gunicorn` with eventlet workers rather than using 5 | `eventlet`_ directly. Gunicorn provides a much more configurable and 6 | production-tested server. 7 | 8 | `eventlet`_ allows writing asynchronous, coroutine-based code that looks 9 | like standard synchronous Python. It uses `greenlet`_ to enable task 10 | switching without writing ``async/await`` or using ``asyncio``. 11 | 12 | :doc:`gevent` is another library that does the same thing. Certain 13 | dependencies you have, or other considerations, may affect which of the 14 | two you choose to use. 15 | 16 | eventlet provides a WSGI server that can handle many connections at once 17 | instead of one per worker process. You must actually use eventlet in 18 | your own code to see any benefit to using the server. 19 | 20 | .. _eventlet: https://eventlet.net/ 21 | .. _greenlet: https://greenlet.readthedocs.io/en/latest/ 22 | 23 | 24 | Installing 25 | ---------- 26 | 27 | When using eventlet, greenlet>=1.0 is required, otherwise context locals 28 | such as ``request`` will not work as expected. When using PyPy, 29 | PyPy>=7.3.7 is required. 30 | 31 | Create a virtualenv, install your application, then install 32 | ``eventlet``. 33 | 34 | .. code-block:: text 35 | 36 | $ cd hello-app 37 | $ python -m venv .venv 38 | $ . .venv/bin/activate 39 | $ pip install . # install your application 40 | $ pip install eventlet 41 | 42 | 43 | Running 44 | ------- 45 | 46 | To use eventlet to serve your application, write a script that imports 47 | its ``wsgi.server``, as well as your app or app factory. 48 | 49 | .. code-block:: python 50 | :caption: ``wsgi.py`` 51 | 52 | import eventlet 53 | from eventlet import wsgi 54 | from hello import create_app 55 | 56 | app = create_app() 57 | wsgi.server(eventlet.listen(("127.0.0.1", 8000)), app) 58 | 59 | .. code-block:: text 60 | 61 | $ python wsgi.py 62 | (x) wsgi starting up on http://127.0.0.1:8000 63 | 64 | 65 | Binding Externally 66 | ------------------ 67 | 68 | eventlet should not be run as root because it would cause your 69 | application code to run as root, which is not secure. However, this 70 | means it will not be possible to bind to port 80 or 443. Instead, a 71 | reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used 72 | in front of eventlet. 73 | 74 | You can bind to all external IPs on a non-privileged port by using 75 | ``0.0.0.0`` in the server arguments shown in the previous section. 76 | Don't do this when using a reverse proxy setup, otherwise it will be 77 | possible to bypass the proxy. 78 | 79 | ``0.0.0.0`` is not a valid address to navigate to, you'd use a specific 80 | IP address in your browser. 81 | -------------------------------------------------------------------------------- /docs/deploying/gevent.rst: -------------------------------------------------------------------------------- 1 | gevent 2 | ====== 3 | 4 | Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather 5 | than using `gevent`_ directly. Gunicorn and uWSGI provide much more 6 | configurable and production-tested servers. 7 | 8 | `gevent`_ allows writing asynchronous, coroutine-based code that looks 9 | like standard synchronous Python. It uses `greenlet`_ to enable task 10 | switching without writing ``async/await`` or using ``asyncio``. 11 | 12 | :doc:`eventlet` is another library that does the same thing. Certain 13 | dependencies you have, or other considerations, may affect which of the 14 | two you choose to use. 15 | 16 | gevent provides a WSGI server that can handle many connections at once 17 | instead of one per worker process. You must actually use gevent in your 18 | own code to see any benefit to using the server. 19 | 20 | .. _gevent: https://www.gevent.org/ 21 | .. _greenlet: https://greenlet.readthedocs.io/en/latest/ 22 | 23 | 24 | Installing 25 | ---------- 26 | 27 | When using gevent, greenlet>=1.0 is required, otherwise context locals 28 | such as ``request`` will not work as expected. When using PyPy, 29 | PyPy>=7.3.7 is required. 30 | 31 | Create a virtualenv, install your application, then install ``gevent``. 32 | 33 | .. code-block:: text 34 | 35 | $ cd hello-app 36 | $ python -m venv .venv 37 | $ . .venv/bin/activate 38 | $ pip install . # install your application 39 | $ pip install gevent 40 | 41 | 42 | Running 43 | ------- 44 | 45 | To use gevent to serve your application, write a script that imports its 46 | ``WSGIServer``, as well as your app or app factory. 47 | 48 | .. code-block:: python 49 | :caption: ``wsgi.py`` 50 | 51 | from gevent.pywsgi import WSGIServer 52 | from hello import create_app 53 | 54 | app = create_app() 55 | http_server = WSGIServer(("127.0.0.1", 8000), app) 56 | http_server.serve_forever() 57 | 58 | .. code-block:: text 59 | 60 | $ python wsgi.py 61 | 62 | No output is shown when the server starts. 63 | 64 | 65 | Binding Externally 66 | ------------------ 67 | 68 | gevent should not be run as root because it would cause your 69 | application code to run as root, which is not secure. However, this 70 | means it will not be possible to bind to port 80 or 443. Instead, a 71 | reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used 72 | in front of gevent. 73 | 74 | You can bind to all external IPs on a non-privileged port by using 75 | ``0.0.0.0`` in the server arguments shown in the previous section. Don't 76 | do this when using a reverse proxy setup, otherwise it will be possible 77 | to bypass the proxy. 78 | 79 | ``0.0.0.0`` is not a valid address to navigate to, you'd use a specific 80 | IP address in your browser. 81 | -------------------------------------------------------------------------------- /docs/deploying/gunicorn.rst: -------------------------------------------------------------------------------- 1 | Gunicorn 2 | ======== 3 | 4 | `Gunicorn`_ is a pure Python WSGI server with simple configuration and 5 | multiple worker implementations for performance tuning. 6 | 7 | * It tends to integrate easily with hosting platforms. 8 | * It does not support Windows (but does run on WSL). 9 | * It is easy to install as it does not require additional dependencies 10 | or compilation. 11 | * It has built-in async worker support using gevent or eventlet. 12 | 13 | This page outlines the basics of running Gunicorn. Be sure to read its 14 | `documentation`_ and use ``gunicorn --help`` to understand what features 15 | are available. 16 | 17 | .. _Gunicorn: https://gunicorn.org/ 18 | .. _documentation: https://docs.gunicorn.org/ 19 | 20 | 21 | Installing 22 | ---------- 23 | 24 | Gunicorn is easy to install, as it does not require external 25 | dependencies or compilation. It runs on Windows only under WSL. 26 | 27 | Create a virtualenv, install your application, then install 28 | ``gunicorn``. 29 | 30 | .. code-block:: text 31 | 32 | $ cd hello-app 33 | $ python -m venv .venv 34 | $ . .venv/bin/activate 35 | $ pip install . # install your application 36 | $ pip install gunicorn 37 | 38 | 39 | Running 40 | ------- 41 | 42 | The only required argument to Gunicorn tells it how to load your Flask 43 | application. The syntax is ``{module_import}:{app_variable}``. 44 | ``module_import`` is the dotted import name to the module with your 45 | application. ``app_variable`` is the variable with the application. It 46 | can also be a function call (with any arguments) if you're using the 47 | app factory pattern. 48 | 49 | .. code-block:: text 50 | 51 | # equivalent to 'from hello import app' 52 | $ gunicorn -w 4 'hello:app' 53 | 54 | # equivalent to 'from hello import create_app; create_app()' 55 | $ gunicorn -w 4 'hello:create_app()' 56 | 57 | Starting gunicorn 20.1.0 58 | Listening at: http://127.0.0.1:8000 (x) 59 | Using worker: sync 60 | Booting worker with pid: x 61 | Booting worker with pid: x 62 | Booting worker with pid: x 63 | Booting worker with pid: x 64 | 65 | The ``-w`` option specifies the number of processes to run; a starting 66 | value could be ``CPU * 2``. The default is only 1 worker, which is 67 | probably not what you want for the default worker type. 68 | 69 | Logs for each request aren't shown by default, only worker info and 70 | errors are shown. To show access logs on stdout, use the 71 | ``--access-logfile=-`` option. 72 | 73 | 74 | Binding Externally 75 | ------------------ 76 | 77 | Gunicorn should not be run as root because it would cause your 78 | application code to run as root, which is not secure. However, this 79 | means it will not be possible to bind to port 80 or 443. Instead, a 80 | reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used 81 | in front of Gunicorn. 82 | 83 | You can bind to all external IPs on a non-privileged port using the 84 | ``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup, 85 | otherwise it will be possible to bypass the proxy. 86 | 87 | .. code-block:: text 88 | 89 | $ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()' 90 | Listening at: http://0.0.0.0:8000 (x) 91 | 92 | ``0.0.0.0`` is not a valid address to navigate to, you'd use a specific 93 | IP address in your browser. 94 | 95 | 96 | Async with gevent or eventlet 97 | ----------------------------- 98 | 99 | The default sync worker is appropriate for many use cases. If you need 100 | asynchronous support, Gunicorn provides workers using either `gevent`_ 101 | or `eventlet`_. This is not the same as Python's ``async/await``, or the 102 | ASGI server spec. You must actually use gevent/eventlet in your own code 103 | to see any benefit to using the workers. 104 | 105 | When using either gevent or eventlet, greenlet>=1.0 is required, 106 | otherwise context locals such as ``request`` will not work as expected. 107 | When using PyPy, PyPy>=7.3.7 is required. 108 | 109 | To use gevent: 110 | 111 | .. code-block:: text 112 | 113 | $ gunicorn -k gevent 'hello:create_app()' 114 | Starting gunicorn 20.1.0 115 | Listening at: http://127.0.0.1:8000 (x) 116 | Using worker: gevent 117 | Booting worker with pid: x 118 | 119 | To use eventlet: 120 | 121 | .. code-block:: text 122 | 123 | $ gunicorn -k eventlet 'hello:create_app()' 124 | Starting gunicorn 20.1.0 125 | Listening at: http://127.0.0.1:8000 (x) 126 | Using worker: eventlet 127 | Booting worker with pid: x 128 | 129 | .. _gevent: https://www.gevent.org/ 130 | .. _eventlet: https://eventlet.net/ 131 | -------------------------------------------------------------------------------- /docs/deploying/index.rst: -------------------------------------------------------------------------------- 1 | Deploying to Production 2 | ======================= 3 | 4 | After developing your application, you'll want to make it available 5 | publicly to other users. When you're developing locally, you're probably 6 | using the built-in development server, debugger, and reloader. These 7 | should not be used in production. Instead, you should use a dedicated 8 | WSGI server or hosting platform, some of which will be described here. 9 | 10 | "Production" means "not development", which applies whether you're 11 | serving your application publicly to millions of users or privately / 12 | locally to a single user. **Do not use the development server when 13 | deploying to production. It is intended for use only during local 14 | development. It is not designed to be particularly secure, stable, or 15 | efficient.** 16 | 17 | Self-Hosted Options 18 | ------------------- 19 | 20 | Flask is a WSGI *application*. A WSGI *server* is used to run the 21 | application, converting incoming HTTP requests to the standard WSGI 22 | environ, and converting outgoing WSGI responses to HTTP responses. 23 | 24 | The primary goal of these docs is to familiarize you with the concepts 25 | involved in running a WSGI application using a production WSGI server 26 | and HTTP server. There are many WSGI servers and HTTP servers, with many 27 | configuration possibilities. The pages below discuss the most common 28 | servers, and show the basics of running each one. The next section 29 | discusses platforms that can manage this for you. 30 | 31 | .. toctree:: 32 | :maxdepth: 1 33 | 34 | gunicorn 35 | waitress 36 | mod_wsgi 37 | uwsgi 38 | gevent 39 | eventlet 40 | asgi 41 | 42 | WSGI servers have HTTP servers built-in. However, a dedicated HTTP 43 | server may be safer, more efficient, or more capable. Putting an HTTP 44 | server in front of the WSGI server is called a "reverse proxy." 45 | 46 | .. toctree:: 47 | :maxdepth: 1 48 | 49 | proxy_fix 50 | nginx 51 | apache-httpd 52 | 53 | This list is not exhaustive, and you should evaluate these and other 54 | servers based on your application's needs. Different servers will have 55 | different capabilities, configuration, and support. 56 | 57 | 58 | Hosting Platforms 59 | ----------------- 60 | 61 | There are many services available for hosting web applications without 62 | needing to maintain your own server, networking, domain, etc. Some 63 | services may have a free tier up to a certain time or bandwidth. Many of 64 | these services use one of the WSGI servers described above, or a similar 65 | interface. The links below are for some of the most common platforms, 66 | which have instructions for Flask, WSGI, or Python. 67 | 68 | - `PythonAnywhere `_ 69 | - `Google App Engine `_ 70 | - `Google Cloud Run `_ 71 | - `AWS Elastic Beanstalk `_ 72 | - `Microsoft Azure `_ 73 | 74 | This list is not exhaustive, and you should evaluate these and other 75 | services based on your application's needs. Different services will have 76 | different capabilities, configuration, pricing, and support. 77 | 78 | You'll probably need to :doc:`proxy_fix` when using most hosting 79 | platforms. 80 | -------------------------------------------------------------------------------- /docs/deploying/mod_wsgi.rst: -------------------------------------------------------------------------------- 1 | mod_wsgi 2 | ======== 3 | 4 | `mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server. 5 | The modern `mod_wsgi-express`_ command makes it easy to configure and 6 | start the server without needing to write Apache httpd configuration. 7 | 8 | * Tightly integrated with Apache httpd. 9 | * Supports Windows directly. 10 | * Requires a compiler and the Apache development headers to install. 11 | * Does not require a reverse proxy setup. 12 | 13 | This page outlines the basics of running mod_wsgi-express, not the more 14 | complex installation and configuration with httpd. Be sure to read the 15 | `mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to 16 | understand what features are available. 17 | 18 | .. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/ 19 | .. _mod_wsgi: https://modwsgi.readthedocs.io/ 20 | .. _Apache httpd: https://httpd.apache.org/ 21 | 22 | 23 | Installing 24 | ---------- 25 | 26 | Installing mod_wsgi requires a compiler and the Apache server and 27 | development headers installed. You will get an error if they are not. 28 | How to install them depends on the OS and package manager that you use. 29 | 30 | Create a virtualenv, install your application, then install 31 | ``mod_wsgi``. 32 | 33 | .. code-block:: text 34 | 35 | $ cd hello-app 36 | $ python -m venv .venv 37 | $ . .venv/bin/activate 38 | $ pip install . # install your application 39 | $ pip install mod_wsgi 40 | 41 | 42 | Running 43 | ------- 44 | 45 | The only argument to ``mod_wsgi-express`` specifies a script containing 46 | your Flask application, which must be called ``application``. You can 47 | write a small script to import your app with this name, or to create it 48 | if using the app factory pattern. 49 | 50 | .. code-block:: python 51 | :caption: ``wsgi.py`` 52 | 53 | from hello import app 54 | 55 | application = app 56 | 57 | .. code-block:: python 58 | :caption: ``wsgi.py`` 59 | 60 | from hello import create_app 61 | 62 | application = create_app() 63 | 64 | Now run the ``mod_wsgi-express start-server`` command. 65 | 66 | .. code-block:: text 67 | 68 | $ mod_wsgi-express start-server wsgi.py --processes 4 69 | 70 | The ``--processes`` option specifies the number of worker processes to 71 | run; a starting value could be ``CPU * 2``. 72 | 73 | Logs for each request aren't show in the terminal. If an error occurs, 74 | its information is written to the error log file shown when starting the 75 | server. 76 | 77 | 78 | Binding Externally 79 | ------------------ 80 | 81 | Unlike the other WSGI servers in these docs, mod_wsgi can be run as 82 | root to bind to privileged ports like 80 and 443. However, it must be 83 | configured to drop permissions to a different user and group for the 84 | worker processes. 85 | 86 | For example, if you created a ``hello`` user and group, you should 87 | install your virtualenv and application as that user, then tell 88 | mod_wsgi to drop to that user after starting. 89 | 90 | .. code-block:: text 91 | 92 | $ sudo /home/hello/.venv/bin/mod_wsgi-express start-server \ 93 | /home/hello/wsgi.py \ 94 | --user hello --group hello --port 80 --processes 4 95 | -------------------------------------------------------------------------------- /docs/deploying/nginx.rst: -------------------------------------------------------------------------------- 1 | nginx 2 | ===== 3 | 4 | `nginx`_ is a fast, production level HTTP server. When serving your 5 | application with one of the WSGI servers listed in :doc:`index`, it is 6 | often good or necessary to put a dedicated HTTP server in front of it. 7 | This "reverse proxy" can handle incoming requests, TLS, and other 8 | security and performance concerns better than the WSGI server. 9 | 10 | Nginx can be installed using your system package manager, or a pre-built 11 | executable for Windows. Installing and running Nginx itself is outside 12 | the scope of this doc. This page outlines the basics of configuring 13 | Nginx to proxy your application. Be sure to read its documentation to 14 | understand what features are available. 15 | 16 | .. _nginx: https://nginx.org/ 17 | 18 | 19 | Domain Name 20 | ----------- 21 | 22 | Acquiring and configuring a domain name is outside the scope of this 23 | doc. In general, you will buy a domain name from a registrar, pay for 24 | server space with a hosting provider, and then point your registrar 25 | at the hosting provider's name servers. 26 | 27 | To simulate this, you can also edit your ``hosts`` file, located at 28 | ``/etc/hosts`` on Linux. Add a line that associates a name with the 29 | local IP. 30 | 31 | Modern Linux systems may be configured to treat any domain name that 32 | ends with ``.localhost`` like this without adding it to the ``hosts`` 33 | file. 34 | 35 | .. code-block:: python 36 | :caption: ``/etc/hosts`` 37 | 38 | 127.0.0.1 hello.localhost 39 | 40 | 41 | Configuration 42 | ------------- 43 | 44 | The nginx configuration is located at ``/etc/nginx/nginx.conf`` on 45 | Linux. It may be different depending on your operating system. Check the 46 | docs and look for ``nginx.conf``. 47 | 48 | Remove or comment out any existing ``server`` section. Add a ``server`` 49 | section and use the ``proxy_pass`` directive to point to the address the 50 | WSGI server is listening on. We'll assume the WSGI server is listening 51 | locally at ``http://127.0.0.1:8000``. 52 | 53 | .. code-block:: nginx 54 | :caption: ``/etc/nginx.conf`` 55 | 56 | server { 57 | listen 80; 58 | server_name _; 59 | 60 | location / { 61 | proxy_pass http://127.0.0.1:8000/; 62 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 63 | proxy_set_header X-Forwarded-Proto $scheme; 64 | proxy_set_header X-Forwarded-Host $host; 65 | proxy_set_header X-Forwarded-Prefix /; 66 | } 67 | } 68 | 69 | Then :doc:`proxy_fix` so that your application uses these headers. 70 | -------------------------------------------------------------------------------- /docs/deploying/proxy_fix.rst: -------------------------------------------------------------------------------- 1 | Tell Flask it is Behind a Proxy 2 | =============================== 3 | 4 | When using a reverse proxy, or many Python hosting platforms, the proxy 5 | will intercept and forward all external requests to the local WSGI 6 | server. 7 | 8 | From the WSGI server and Flask application's perspectives, requests are 9 | now coming from the HTTP server to the local address, rather than from 10 | the remote address to the external server address. 11 | 12 | HTTP servers should set ``X-Forwarded-`` headers to pass on the real 13 | values to the application. The application can then be told to trust and 14 | use those values by wrapping it with the 15 | :doc:`werkzeug:middleware/proxy_fix` middleware provided by Werkzeug. 16 | 17 | This middleware should only be used if the application is actually 18 | behind a proxy, and should be configured with the number of proxies that 19 | are chained in front of it. Not all proxies set all the headers. Since 20 | incoming headers can be faked, you must set how many proxies are setting 21 | each header so the middleware knows what to trust. 22 | 23 | .. code-block:: python 24 | 25 | from werkzeug.middleware.proxy_fix import ProxyFix 26 | 27 | app.wsgi_app = ProxyFix( 28 | app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 29 | ) 30 | 31 | Remember, only apply this middleware if you are behind a proxy, and set 32 | the correct number of proxies that set each header. It can be a security 33 | issue if you get this configuration wrong. 34 | -------------------------------------------------------------------------------- /docs/deploying/waitress.rst: -------------------------------------------------------------------------------- 1 | Waitress 2 | ======== 3 | 4 | `Waitress`_ is a pure Python WSGI server. 5 | 6 | * It is easy to configure. 7 | * It supports Windows directly. 8 | * It is easy to install as it does not require additional dependencies 9 | or compilation. 10 | * It does not support streaming requests, full request data is always 11 | buffered. 12 | * It uses a single process with multiple thread workers. 13 | 14 | This page outlines the basics of running Waitress. Be sure to read its 15 | documentation and ``waitress-serve --help`` to understand what features 16 | are available. 17 | 18 | .. _Waitress: https://docs.pylonsproject.org/projects/waitress/ 19 | 20 | 21 | Installing 22 | ---------- 23 | 24 | Create a virtualenv, install your application, then install 25 | ``waitress``. 26 | 27 | .. code-block:: text 28 | 29 | $ cd hello-app 30 | $ python -m venv .venv 31 | $ . .venv/bin/activate 32 | $ pip install . # install your application 33 | $ pip install waitress 34 | 35 | 36 | Running 37 | ------- 38 | 39 | The only required argument to ``waitress-serve`` tells it how to load 40 | your Flask application. The syntax is ``{module}:{app}``. ``module`` is 41 | the dotted import name to the module with your application. ``app`` is 42 | the variable with the application. If you're using the app factory 43 | pattern, use ``--call {module}:{factory}`` instead. 44 | 45 | .. code-block:: text 46 | 47 | # equivalent to 'from hello import app' 48 | $ waitress-serve --host 127.0.0.1 hello:app 49 | 50 | # equivalent to 'from hello import create_app; create_app()' 51 | $ waitress-serve --host 127.0.0.1 --call hello:create_app 52 | 53 | Serving on http://127.0.0.1:8080 54 | 55 | The ``--host`` option binds the server to local ``127.0.0.1`` only. 56 | 57 | Logs for each request aren't shown, only errors are shown. Logging can 58 | be configured through the Python interface instead of the command line. 59 | 60 | 61 | Binding Externally 62 | ------------------ 63 | 64 | Waitress should not be run as root because it would cause your 65 | application code to run as root, which is not secure. However, this 66 | means it will not be possible to bind to port 80 or 443. Instead, a 67 | reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used 68 | in front of Waitress. 69 | 70 | You can bind to all external IPs on a non-privileged port by not 71 | specifying the ``--host`` option. Don't do this when using a reverse 72 | proxy setup, otherwise it will be possible to bypass the proxy. 73 | 74 | ``0.0.0.0`` is not a valid address to navigate to, you'd use a specific 75 | IP address in your browser. 76 | -------------------------------------------------------------------------------- /docs/extensions.rst: -------------------------------------------------------------------------------- 1 | Extensions 2 | ========== 3 | 4 | Extensions are extra packages that add functionality to a Flask 5 | application. For example, an extension might add support for sending 6 | email or connecting to a database. Some extensions add entire new 7 | frameworks to help build certain types of applications, like a REST API. 8 | 9 | 10 | Finding Extensions 11 | ------------------ 12 | 13 | Flask extensions are usually named "Flask-Foo" or "Foo-Flask". You can 14 | search PyPI for packages tagged with `Framework :: Flask `_. 15 | 16 | 17 | Using Extensions 18 | ---------------- 19 | 20 | Consult each extension's documentation for installation, configuration, 21 | and usage instructions. Generally, extensions pull their own 22 | configuration from :attr:`app.config ` and are 23 | passed an application instance during initialization. For example, 24 | an extension called "Flask-Foo" might be used like this:: 25 | 26 | from flask_foo import Foo 27 | 28 | foo = Foo() 29 | 30 | app = Flask(__name__) 31 | app.config.update( 32 | FOO_BAR='baz', 33 | FOO_SPAM='eggs', 34 | ) 35 | 36 | foo.init_app(app) 37 | 38 | 39 | Building Extensions 40 | ------------------- 41 | 42 | While `PyPI `_ contains many Flask extensions, you may not find 43 | an extension that fits your need. If this is the case, you can create 44 | your own, and publish it for others to use as well. Read 45 | :doc:`extensiondev` to develop your own Flask extension. 46 | 47 | 48 | .. _pypi: https://pypi.org/search/?c=Framework+%3A%3A+Flask 49 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. rst-class:: hide-header 2 | 3 | Welcome to Flask 4 | ================ 5 | 6 | .. image:: _static/flask-horizontal.png 7 | :align: center 8 | 9 | Welcome to Flask's documentation. Flask is a lightweight WSGI web application framework. 10 | It is designed to make getting started quick and easy, with the ability to scale up to 11 | complex applications. 12 | 13 | Get started with :doc:`installation` 14 | and then get an overview with the :doc:`quickstart`. There is also a 15 | more detailed :doc:`tutorial/index` that shows how to create a small but 16 | complete application with Flask. Common patterns are described in the 17 | :doc:`patterns/index` section. The rest of the docs describe each 18 | component of Flask in detail, with a full reference in the :doc:`api` 19 | section. 20 | 21 | Flask depends on the `Werkzeug`_ WSGI toolkit, the `Jinja`_ template engine, and the 22 | `Click`_ CLI toolkit. Be sure to check their documentation as well as Flask's when 23 | looking for information. 24 | 25 | .. _Werkzeug: https://werkzeug.palletsprojects.com 26 | .. _Jinja: https://jinja.palletsprojects.com 27 | .. _Click: https://click.palletsprojects.com 28 | 29 | 30 | User's Guide 31 | ------------ 32 | 33 | Flask provides configuration and conventions, with sensible defaults, to get started. 34 | This section of the documentation explains the different parts of the Flask framework 35 | and how they can be used, customized, and extended. Beyond Flask itself, look for 36 | community-maintained extensions to add even more functionality. 37 | 38 | .. toctree:: 39 | :maxdepth: 2 40 | 41 | installation 42 | quickstart 43 | tutorial/index 44 | templating 45 | testing 46 | errorhandling 47 | debugging 48 | logging 49 | config 50 | signals 51 | views 52 | lifecycle 53 | appcontext 54 | reqcontext 55 | blueprints 56 | extensions 57 | cli 58 | server 59 | shell 60 | patterns/index 61 | web-security 62 | deploying/index 63 | async-await 64 | 65 | 66 | API Reference 67 | ------------- 68 | 69 | If you are looking for information on a specific function, class or 70 | method, this part of the documentation is for you. 71 | 72 | .. toctree:: 73 | :maxdepth: 2 74 | 75 | api 76 | 77 | 78 | Additional Notes 79 | ---------------- 80 | 81 | .. toctree:: 82 | :maxdepth: 2 83 | 84 | design 85 | extensiondev 86 | contributing 87 | license 88 | changes 89 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | 5 | Python Version 6 | -------------- 7 | 8 | We recommend using the latest version of Python. Flask supports Python 3.10 and newer. 9 | 10 | 11 | Dependencies 12 | ------------ 13 | 14 | These distributions will be installed automatically when installing Flask. 15 | 16 | * `Werkzeug`_ implements WSGI, the standard Python interface between 17 | applications and servers. 18 | * `Jinja`_ is a template language that renders the pages your application 19 | serves. 20 | * `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering 21 | templates to avoid injection attacks. 22 | * `ItsDangerous`_ securely signs data to ensure its integrity. This is used 23 | to protect Flask's session cookie. 24 | * `Click`_ is a framework for writing command line applications. It provides 25 | the ``flask`` command and allows adding custom management commands. 26 | * `Blinker`_ provides support for :doc:`signals`. 27 | 28 | .. _Werkzeug: https://palletsprojects.com/p/werkzeug/ 29 | .. _Jinja: https://palletsprojects.com/p/jinja/ 30 | .. _MarkupSafe: https://palletsprojects.com/p/markupsafe/ 31 | .. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ 32 | .. _Click: https://palletsprojects.com/p/click/ 33 | .. _Blinker: https://blinker.readthedocs.io/ 34 | 35 | 36 | Optional dependencies 37 | ~~~~~~~~~~~~~~~~~~~~~ 38 | 39 | These distributions will not be installed automatically. Flask will detect and 40 | use them if you install them. 41 | 42 | * `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask`` 43 | commands. 44 | * `Watchdog`_ provides a faster, more efficient reloader for the development 45 | server. 46 | 47 | .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme 48 | .. _watchdog: https://pythonhosted.org/watchdog/ 49 | 50 | 51 | greenlet 52 | ~~~~~~~~ 53 | 54 | You may choose to use gevent or eventlet with your application. In this 55 | case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is 56 | required. 57 | 58 | These are not minimum supported versions, they only indicate the first 59 | versions that added necessary features. You should use the latest 60 | versions of each. 61 | 62 | 63 | Virtual environments 64 | -------------------- 65 | 66 | Use a virtual environment to manage the dependencies for your project, both in 67 | development and in production. 68 | 69 | What problem does a virtual environment solve? The more Python projects you 70 | have, the more likely it is that you need to work with different versions of 71 | Python libraries, or even Python itself. Newer versions of libraries for one 72 | project can break compatibility in another project. 73 | 74 | Virtual environments are independent groups of Python libraries, one for each 75 | project. Packages installed for one project will not affect other projects or 76 | the operating system's packages. 77 | 78 | Python comes bundled with the :mod:`venv` module to create virtual 79 | environments. 80 | 81 | 82 | .. _install-create-env: 83 | 84 | Create an environment 85 | ~~~~~~~~~~~~~~~~~~~~~ 86 | 87 | Create a project folder and a :file:`.venv` folder within: 88 | 89 | .. tabs:: 90 | 91 | .. group-tab:: macOS/Linux 92 | 93 | .. code-block:: text 94 | 95 | $ mkdir myproject 96 | $ cd myproject 97 | $ python3 -m venv .venv 98 | 99 | .. group-tab:: Windows 100 | 101 | .. code-block:: text 102 | 103 | > mkdir myproject 104 | > cd myproject 105 | > py -3 -m venv .venv 106 | 107 | 108 | .. _install-activate-env: 109 | 110 | Activate the environment 111 | ~~~~~~~~~~~~~~~~~~~~~~~~ 112 | 113 | Before you work on your project, activate the corresponding environment: 114 | 115 | .. tabs:: 116 | 117 | .. group-tab:: macOS/Linux 118 | 119 | .. code-block:: text 120 | 121 | $ . .venv/bin/activate 122 | 123 | .. group-tab:: Windows 124 | 125 | .. code-block:: text 126 | 127 | > .venv\Scripts\activate 128 | 129 | Your shell prompt will change to show the name of the activated 130 | environment. 131 | 132 | 133 | Install Flask 134 | ------------- 135 | 136 | Within the activated environment, use the following command to install 137 | Flask: 138 | 139 | .. code-block:: sh 140 | 141 | $ pip install Flask 142 | 143 | Flask is now installed. Check out the :doc:`/quickstart` or go to the 144 | :doc:`Documentation Overview `. 145 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | BSD-3-Clause License 2 | ==================== 3 | 4 | .. literalinclude:: ../LICENSE.txt 5 | :language: text 6 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/patterns/appfactories.rst: -------------------------------------------------------------------------------- 1 | Application Factories 2 | ===================== 3 | 4 | If you are already using packages and blueprints for your application 5 | (:doc:`/blueprints`) there are a couple of really nice ways to further improve 6 | the experience. A common pattern is creating the application object when 7 | the blueprint is imported. But if you move the creation of this object 8 | into a function, you can then create multiple instances of this app later. 9 | 10 | So why would you want to do this? 11 | 12 | 1. Testing. You can have instances of the application with different 13 | settings to test every case. 14 | 2. Multiple instances. Imagine you want to run different versions of the 15 | same application. Of course you could have multiple instances with 16 | different configs set up in your webserver, but if you use factories, 17 | you can have multiple instances of the same application running in the 18 | same application process which can be handy. 19 | 20 | So how would you then actually implement that? 21 | 22 | Basic Factories 23 | --------------- 24 | 25 | The idea is to set up the application in a function. Like this:: 26 | 27 | def create_app(config_filename): 28 | app = Flask(__name__) 29 | app.config.from_pyfile(config_filename) 30 | 31 | from yourapplication.model import db 32 | db.init_app(app) 33 | 34 | from yourapplication.views.admin import admin 35 | from yourapplication.views.frontend import frontend 36 | app.register_blueprint(admin) 37 | app.register_blueprint(frontend) 38 | 39 | return app 40 | 41 | The downside is that you cannot use the application object in the blueprints 42 | at import time. You can however use it from within a request. How do you 43 | get access to the application with the config? Use 44 | :data:`~flask.current_app`:: 45 | 46 | from flask import current_app, Blueprint, render_template 47 | admin = Blueprint('admin', __name__, url_prefix='/admin') 48 | 49 | @admin.route('/') 50 | def index(): 51 | return render_template(current_app.config['INDEX_TEMPLATE']) 52 | 53 | Here we look up the name of a template in the config. 54 | 55 | Factories & Extensions 56 | ---------------------- 57 | 58 | It's preferable to create your extensions and app factories so that the 59 | extension object does not initially get bound to the application. 60 | 61 | Using `Flask-SQLAlchemy `_, 62 | as an example, you should not do something along those lines:: 63 | 64 | def create_app(config_filename): 65 | app = Flask(__name__) 66 | app.config.from_pyfile(config_filename) 67 | 68 | db = SQLAlchemy(app) 69 | 70 | But, rather, in model.py (or equivalent):: 71 | 72 | db = SQLAlchemy() 73 | 74 | and in your application.py (or equivalent):: 75 | 76 | def create_app(config_filename): 77 | app = Flask(__name__) 78 | app.config.from_pyfile(config_filename) 79 | 80 | from yourapplication.model import db 81 | db.init_app(app) 82 | 83 | Using this design pattern, no application-specific state is stored on the 84 | extension object, so one extension object can be used for multiple apps. 85 | For more information about the design of extensions refer to :doc:`/extensiondev`. 86 | 87 | Using Applications 88 | ------------------ 89 | 90 | To run such an application, you can use the :command:`flask` command: 91 | 92 | .. code-block:: text 93 | 94 | $ flask --app hello run 95 | 96 | Flask will automatically detect the factory if it is named 97 | ``create_app`` or ``make_app`` in ``hello``. You can also pass arguments 98 | to the factory like this: 99 | 100 | .. code-block:: text 101 | 102 | $ flask --app 'hello:create_app(local_auth=True)' run 103 | 104 | Then the ``create_app`` factory in ``hello`` is called with the keyword 105 | argument ``local_auth=True``. See :doc:`/cli` for more detail. 106 | 107 | Factory Improvements 108 | -------------------- 109 | 110 | The factory function above is not very clever, but you can improve it. 111 | The following changes are straightforward to implement: 112 | 113 | 1. Make it possible to pass in configuration values for unit tests so that 114 | you don't have to create config files on the filesystem. 115 | 2. Call a function from a blueprint when the application is setting up so 116 | that you have a place to modify attributes of the application (like 117 | hooking in before/after request handlers etc.) 118 | 3. Add in WSGI middlewares when the application is being created if necessary. 119 | -------------------------------------------------------------------------------- /docs/patterns/caching.rst: -------------------------------------------------------------------------------- 1 | Caching 2 | ======= 3 | 4 | When your application runs slow, throw some caches in. Well, at least 5 | it's the easiest way to speed up things. What does a cache do? Say you 6 | have a function that takes some time to complete but the results would 7 | still be good enough if they were 5 minutes old. So then the idea is that 8 | you actually put the result of that calculation into a cache for some 9 | time. 10 | 11 | Flask itself does not provide caching for you, but `Flask-Caching`_, an 12 | extension for Flask does. Flask-Caching supports various backends, and it is 13 | even possible to develop your own caching backend. 14 | 15 | 16 | .. _Flask-Caching: https://flask-caching.readthedocs.io/en/latest/ 17 | -------------------------------------------------------------------------------- /docs/patterns/deferredcallbacks.rst: -------------------------------------------------------------------------------- 1 | Deferred Request Callbacks 2 | ========================== 3 | 4 | One of the design principles of Flask is that response objects are created and 5 | passed down a chain of potential callbacks that can modify them or replace 6 | them. When the request handling starts, there is no response object yet. It is 7 | created as necessary either by a view function or by some other component in 8 | the system. 9 | 10 | What happens if you want to modify the response at a point where the response 11 | does not exist yet? A common example for that would be a 12 | :meth:`~flask.Flask.before_request` callback that wants to set a cookie on the 13 | response object. 14 | 15 | One way is to avoid the situation. Very often that is possible. For instance 16 | you can try to move that logic into a :meth:`~flask.Flask.after_request` 17 | callback instead. However, sometimes moving code there makes it 18 | more complicated or awkward to reason about. 19 | 20 | As an alternative, you can use :func:`~flask.after_this_request` to register 21 | callbacks that will execute after only the current request. This way you can 22 | defer code execution from anywhere in the application, based on the current 23 | request. 24 | 25 | At any time during a request, we can register a function to be called at the 26 | end of the request. For example you can remember the current language of the 27 | user in a cookie in a :meth:`~flask.Flask.before_request` callback:: 28 | 29 | from flask import request, after_this_request 30 | 31 | @app.before_request 32 | def detect_user_language(): 33 | language = request.cookies.get('user_lang') 34 | 35 | if language is None: 36 | language = guess_language_from_request() 37 | 38 | # when the response exists, set a cookie with the language 39 | @after_this_request 40 | def remember_language(response): 41 | response.set_cookie('user_lang', language) 42 | return response 43 | 44 | g.language = language 45 | -------------------------------------------------------------------------------- /docs/patterns/favicon.rst: -------------------------------------------------------------------------------- 1 | Adding a favicon 2 | ================ 3 | 4 | A "favicon" is an icon used by browsers for tabs and bookmarks. This helps 5 | to distinguish your website and to give it a unique brand. 6 | 7 | A common question is how to add a favicon to a Flask application. First, of 8 | course, you need an icon. It should be 16 × 16 pixels and in the ICO file 9 | format. This is not a requirement but a de-facto standard supported by all 10 | relevant browsers. Put the icon in your static directory as 11 | :file:`favicon.ico`. 12 | 13 | Now, to get browsers to find your icon, the correct way is to add a link 14 | tag in your HTML. So, for example: 15 | 16 | .. sourcecode:: html+jinja 17 | 18 | 19 | 20 | That's all you need for most browsers, however some really old ones do not 21 | support this standard. The old de-facto standard is to serve this file, 22 | with this name, at the website root. If your application is not mounted at 23 | the root path of the domain you either need to configure the web server to 24 | serve the icon at the root or if you can't do that you're out of luck. If 25 | however your application is the root you can simply route a redirect:: 26 | 27 | app.add_url_rule( 28 | "/favicon.ico", 29 | endpoint="favicon", 30 | redirect_to=url_for("static", filename="favicon.ico"), 31 | ) 32 | 33 | If you want to save the extra redirect request you can also write a view 34 | using :func:`~flask.send_from_directory`:: 35 | 36 | import os 37 | from flask import send_from_directory 38 | 39 | @app.route('/favicon.ico') 40 | def favicon(): 41 | return send_from_directory(os.path.join(app.root_path, 'static'), 42 | 'favicon.ico', mimetype='image/vnd.microsoft.icon') 43 | 44 | We can leave out the explicit mimetype and it will be guessed, but we may 45 | as well specify it to avoid the extra guessing, as it will always be the 46 | same. 47 | 48 | The above will serve the icon via your application and if possible it's 49 | better to configure your dedicated web server to serve it; refer to the 50 | web server's documentation. 51 | 52 | See also 53 | -------- 54 | 55 | * The `Favicon `_ article on 56 | Wikipedia 57 | -------------------------------------------------------------------------------- /docs/patterns/index.rst: -------------------------------------------------------------------------------- 1 | Patterns for Flask 2 | ================== 3 | 4 | Certain features and interactions are common enough that you will find 5 | them in most web applications. For example, many applications use a 6 | relational database and user authentication. They will open a database 7 | connection at the beginning of the request and get the information for 8 | the logged in user. At the end of the request, the database connection 9 | is closed. 10 | 11 | These types of patterns may be a bit outside the scope of Flask itself, 12 | but Flask makes it easy to implement them. Some common patterns are 13 | collected in the following pages. 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | packages 19 | appfactories 20 | appdispatch 21 | urlprocessors 22 | sqlite3 23 | sqlalchemy 24 | fileuploads 25 | caching 26 | viewdecorators 27 | wtforms 28 | templateinheritance 29 | flashing 30 | javascript 31 | lazyloading 32 | mongoengine 33 | favicon 34 | streaming 35 | deferredcallbacks 36 | methodoverrides 37 | requestchecksum 38 | celery 39 | subclassing 40 | singlepageapplications 41 | -------------------------------------------------------------------------------- /docs/patterns/jquery.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | AJAX with jQuery 4 | ================ 5 | 6 | Obsolete, see :doc:`/patterns/javascript` instead. 7 | -------------------------------------------------------------------------------- /docs/patterns/lazyloading.rst: -------------------------------------------------------------------------------- 1 | Lazily Loading Views 2 | ==================== 3 | 4 | Flask is usually used with the decorators. Decorators are simple and you 5 | have the URL right next to the function that is called for that specific 6 | URL. However there is a downside to this approach: it means all your code 7 | that uses decorators has to be imported upfront or Flask will never 8 | actually find your function. 9 | 10 | This can be a problem if your application has to import quick. It might 11 | have to do that on systems like Google's App Engine or other systems. So 12 | if you suddenly notice that your application outgrows this approach you 13 | can fall back to a centralized URL mapping. 14 | 15 | The system that enables having a central URL map is the 16 | :meth:`~flask.Flask.add_url_rule` function. Instead of using decorators, 17 | you have a file that sets up the application with all URLs. 18 | 19 | Converting to Centralized URL Map 20 | --------------------------------- 21 | 22 | Imagine the current application looks somewhat like this:: 23 | 24 | from flask import Flask 25 | app = Flask(__name__) 26 | 27 | @app.route('/') 28 | def index(): 29 | pass 30 | 31 | @app.route('/user/') 32 | def user(username): 33 | pass 34 | 35 | Then, with the centralized approach you would have one file with the views 36 | (:file:`views.py`) but without any decorator:: 37 | 38 | def index(): 39 | pass 40 | 41 | def user(username): 42 | pass 43 | 44 | And then a file that sets up an application which maps the functions to 45 | URLs:: 46 | 47 | from flask import Flask 48 | from yourapplication import views 49 | app = Flask(__name__) 50 | app.add_url_rule('/', view_func=views.index) 51 | app.add_url_rule('/user/', view_func=views.user) 52 | 53 | Loading Late 54 | ------------ 55 | 56 | So far we only split up the views and the routing, but the module is still 57 | loaded upfront. The trick is to actually load the view function as needed. 58 | This can be accomplished with a helper class that behaves just like a 59 | function but internally imports the real function on first use:: 60 | 61 | from werkzeug.utils import import_string, cached_property 62 | 63 | class LazyView(object): 64 | 65 | def __init__(self, import_name): 66 | self.__module__, self.__name__ = import_name.rsplit('.', 1) 67 | self.import_name = import_name 68 | 69 | @cached_property 70 | def view(self): 71 | return import_string(self.import_name) 72 | 73 | def __call__(self, *args, **kwargs): 74 | return self.view(*args, **kwargs) 75 | 76 | What's important here is is that `__module__` and `__name__` are properly 77 | set. This is used by Flask internally to figure out how to name the 78 | URL rules in case you don't provide a name for the rule yourself. 79 | 80 | Then you can define your central place to combine the views like this:: 81 | 82 | from flask import Flask 83 | from yourapplication.helpers import LazyView 84 | app = Flask(__name__) 85 | app.add_url_rule('/', 86 | view_func=LazyView('yourapplication.views.index')) 87 | app.add_url_rule('/user/', 88 | view_func=LazyView('yourapplication.views.user')) 89 | 90 | You can further optimize this in terms of amount of keystrokes needed to 91 | write this by having a function that calls into 92 | :meth:`~flask.Flask.add_url_rule` by prefixing a string with the project 93 | name and a dot, and by wrapping `view_func` in a `LazyView` as needed. :: 94 | 95 | def url(import_name, url_rules=[], **options): 96 | view = LazyView(f"yourapplication.{import_name}") 97 | for url_rule in url_rules: 98 | app.add_url_rule(url_rule, view_func=view, **options) 99 | 100 | # add a single route to the index view 101 | url('views.index', ['/']) 102 | 103 | # add two routes to a single function endpoint 104 | url_rules = ['/user/','/user/'] 105 | url('views.user', url_rules) 106 | 107 | One thing to keep in mind is that before and after request handlers have 108 | to be in a file that is imported upfront to work properly on the first 109 | request. The same goes for any kind of remaining decorator. 110 | -------------------------------------------------------------------------------- /docs/patterns/methodoverrides.rst: -------------------------------------------------------------------------------- 1 | Adding HTTP Method Overrides 2 | ============================ 3 | 4 | Some HTTP proxies do not support arbitrary HTTP methods or newer HTTP 5 | methods (such as PATCH). In that case it's possible to "proxy" HTTP 6 | methods through another HTTP method in total violation of the protocol. 7 | 8 | The way this works is by letting the client do an HTTP POST request and 9 | set the ``X-HTTP-Method-Override`` header. Then the method is replaced 10 | with the header value before being passed to Flask. 11 | 12 | This can be accomplished with an HTTP middleware:: 13 | 14 | class HTTPMethodOverrideMiddleware(object): 15 | allowed_methods = frozenset([ 16 | 'GET', 17 | 'HEAD', 18 | 'POST', 19 | 'DELETE', 20 | 'PUT', 21 | 'PATCH', 22 | 'OPTIONS' 23 | ]) 24 | bodyless_methods = frozenset(['GET', 'HEAD', 'OPTIONS', 'DELETE']) 25 | 26 | def __init__(self, app): 27 | self.app = app 28 | 29 | def __call__(self, environ, start_response): 30 | method = environ.get('HTTP_X_HTTP_METHOD_OVERRIDE', '').upper() 31 | if method in self.allowed_methods: 32 | environ['REQUEST_METHOD'] = method 33 | if method in self.bodyless_methods: 34 | environ['CONTENT_LENGTH'] = '0' 35 | return self.app(environ, start_response) 36 | 37 | To use this with Flask, wrap the app object with the middleware:: 38 | 39 | from flask import Flask 40 | 41 | app = Flask(__name__) 42 | app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app) 43 | -------------------------------------------------------------------------------- /docs/patterns/mongoengine.rst: -------------------------------------------------------------------------------- 1 | MongoDB with MongoEngine 2 | ======================== 3 | 4 | Using a document database like MongoDB is a common alternative to 5 | relational SQL databases. This pattern shows how to use 6 | `MongoEngine`_, a document mapper library, to integrate with MongoDB. 7 | 8 | A running MongoDB server and `Flask-MongoEngine`_ are required. :: 9 | 10 | pip install flask-mongoengine 11 | 12 | .. _MongoEngine: http://mongoengine.org 13 | .. _Flask-MongoEngine: https://flask-mongoengine.readthedocs.io 14 | 15 | 16 | Configuration 17 | ------------- 18 | 19 | Basic setup can be done by defining ``MONGODB_SETTINGS`` on 20 | ``app.config`` and creating a ``MongoEngine`` instance. :: 21 | 22 | from flask import Flask 23 | from flask_mongoengine import MongoEngine 24 | 25 | app = Flask(__name__) 26 | app.config['MONGODB_SETTINGS'] = { 27 | "db": "myapp", 28 | } 29 | db = MongoEngine(app) 30 | 31 | 32 | Mapping Documents 33 | ----------------- 34 | 35 | To declare a model that represents a Mongo document, create a class that 36 | inherits from ``Document`` and declare each of the fields. :: 37 | 38 | import mongoengine as me 39 | 40 | class Movie(me.Document): 41 | title = me.StringField(required=True) 42 | year = me.IntField() 43 | rated = me.StringField() 44 | director = me.StringField() 45 | actors = me.ListField() 46 | 47 | If the document has nested fields, use ``EmbeddedDocument`` to 48 | defined the fields of the embedded document and 49 | ``EmbeddedDocumentField`` to declare it on the parent document. :: 50 | 51 | class Imdb(me.EmbeddedDocument): 52 | imdb_id = me.StringField() 53 | rating = me.DecimalField() 54 | votes = me.IntField() 55 | 56 | class Movie(me.Document): 57 | ... 58 | imdb = me.EmbeddedDocumentField(Imdb) 59 | 60 | 61 | Creating Data 62 | ------------- 63 | 64 | Instantiate your document class with keyword arguments for the fields. 65 | You can also assign values to the field attributes after instantiation. 66 | Then call ``doc.save()``. :: 67 | 68 | bttf = Movie(title="Back To The Future", year=1985) 69 | bttf.actors = [ 70 | "Michael J. Fox", 71 | "Christopher Lloyd" 72 | ] 73 | bttf.imdb = Imdb(imdb_id="tt0088763", rating=8.5) 74 | bttf.save() 75 | 76 | 77 | Queries 78 | ------- 79 | 80 | Use the class ``objects`` attribute to make queries. A keyword argument 81 | looks for an equal value on the field. :: 82 | 83 | bttf = Movie.objects(title="Back To The Future").get_or_404() 84 | 85 | Query operators may be used by concatenating them with the field name 86 | using a double-underscore. ``objects``, and queries returned by 87 | calling it, are iterable. :: 88 | 89 | some_theron_movie = Movie.objects(actors__in=["Charlize Theron"]).first() 90 | 91 | for recents in Movie.objects(year__gte=2017): 92 | print(recents.title) 93 | 94 | 95 | Documentation 96 | ------------- 97 | 98 | There are many more ways to define and query documents with MongoEngine. 99 | For more information, check out the `official documentation 100 | `_. 101 | 102 | Flask-MongoEngine adds helpful utilities on top of MongoEngine. Check 103 | out their `documentation `_ as well. 104 | -------------------------------------------------------------------------------- /docs/patterns/requestchecksum.rst: -------------------------------------------------------------------------------- 1 | Request Content Checksums 2 | ========================= 3 | 4 | Various pieces of code can consume the request data and preprocess it. 5 | For instance JSON data ends up on the request object already read and 6 | processed, form data ends up there as well but goes through a different 7 | code path. This seems inconvenient when you want to calculate the 8 | checksum of the incoming request data. This is necessary sometimes for 9 | some APIs. 10 | 11 | Fortunately this is however very simple to change by wrapping the input 12 | stream. 13 | 14 | The following example calculates the SHA1 checksum of the incoming data as 15 | it gets read and stores it in the WSGI environment:: 16 | 17 | import hashlib 18 | 19 | class ChecksumCalcStream(object): 20 | 21 | def __init__(self, stream): 22 | self._stream = stream 23 | self._hash = hashlib.sha1() 24 | 25 | def read(self, bytes): 26 | rv = self._stream.read(bytes) 27 | self._hash.update(rv) 28 | return rv 29 | 30 | def readline(self, size_hint): 31 | rv = self._stream.readline(size_hint) 32 | self._hash.update(rv) 33 | return rv 34 | 35 | def generate_checksum(request): 36 | env = request.environ 37 | stream = ChecksumCalcStream(env['wsgi.input']) 38 | env['wsgi.input'] = stream 39 | return stream._hash 40 | 41 | To use this, all you need to do is to hook the calculating stream in 42 | before the request starts consuming data. (Eg: be careful accessing 43 | ``request.form`` or anything of that nature. ``before_request_handlers`` 44 | for instance should be careful not to access it). 45 | 46 | Example usage:: 47 | 48 | @app.route('/special-api', methods=['POST']) 49 | def special_api(): 50 | hash = generate_checksum(request) 51 | # Accessing this parses the input stream 52 | files = request.files 53 | # At this point the hash is fully constructed. 54 | checksum = hash.hexdigest() 55 | return f"Hash was: {checksum}" 56 | -------------------------------------------------------------------------------- /docs/patterns/singlepageapplications.rst: -------------------------------------------------------------------------------- 1 | Single-Page Applications 2 | ======================== 3 | 4 | Flask can be used to serve Single-Page Applications (SPA) by placing static 5 | files produced by your frontend framework in a subfolder inside of your 6 | project. You will also need to create a catch-all endpoint that routes all 7 | requests to your SPA. 8 | 9 | The following example demonstrates how to serve an SPA along with an API:: 10 | 11 | from flask import Flask, jsonify 12 | 13 | app = Flask(__name__, static_folder='app', static_url_path="/app") 14 | 15 | 16 | @app.route("/heartbeat") 17 | def heartbeat(): 18 | return jsonify({"status": "healthy"}) 19 | 20 | 21 | @app.route('/', defaults={'path': ''}) 22 | @app.route('/') 23 | def catch_all(path): 24 | return app.send_static_file("index.html") 25 | -------------------------------------------------------------------------------- /docs/patterns/streaming.rst: -------------------------------------------------------------------------------- 1 | Streaming Contents 2 | ================== 3 | 4 | Sometimes you want to send an enormous amount of data to the client, much 5 | more than you want to keep in memory. When you are generating the data on 6 | the fly though, how do you send that back to the client without the 7 | roundtrip to the filesystem? 8 | 9 | The answer is by using generators and direct responses. 10 | 11 | Basic Usage 12 | ----------- 13 | 14 | This is a basic view function that generates a lot of CSV data on the fly. 15 | The trick is to have an inner function that uses a generator to generate 16 | data and to then invoke that function and pass it to a response object:: 17 | 18 | @app.route('/large.csv') 19 | def generate_large_csv(): 20 | def generate(): 21 | for row in iter_all_rows(): 22 | yield f"{','.join(row)}\n" 23 | return generate(), {"Content-Type": "text/csv"} 24 | 25 | Each ``yield`` expression is directly sent to the browser. Note though 26 | that some WSGI middlewares might break streaming, so be careful there in 27 | debug environments with profilers and other things you might have enabled. 28 | 29 | Streaming from Templates 30 | ------------------------ 31 | 32 | The Jinja2 template engine supports rendering a template piece by 33 | piece, returning an iterator of strings. Flask provides the 34 | :func:`~flask.stream_template` and :func:`~flask.stream_template_string` 35 | functions to make this easier to use. 36 | 37 | .. code-block:: python 38 | 39 | from flask import stream_template 40 | 41 | @app.get("/timeline") 42 | def timeline(): 43 | return stream_template("timeline.html") 44 | 45 | The parts yielded by the render stream tend to match statement blocks in 46 | the template. 47 | 48 | 49 | Streaming with Context 50 | ---------------------- 51 | 52 | The :data:`~flask.request` will not be active while the generator is 53 | running, because the view has already returned at that point. If you try 54 | to access ``request``, you'll get a ``RuntimeError``. 55 | 56 | If your generator function relies on data in ``request``, use the 57 | :func:`~flask.stream_with_context` wrapper. This will keep the request 58 | context active during the generator. 59 | 60 | .. code-block:: python 61 | 62 | from flask import stream_with_context, request 63 | from markupsafe import escape 64 | 65 | @app.route('/stream') 66 | def streamed_response(): 67 | def generate(): 68 | yield '

Hello ' 69 | yield escape(request.args['name']) 70 | yield '!

' 71 | return stream_with_context(generate()) 72 | 73 | It can also be used as a decorator. 74 | 75 | .. code-block:: python 76 | 77 | @stream_with_context 78 | def generate(): 79 | ... 80 | 81 | return generate() 82 | 83 | The :func:`~flask.stream_template` and 84 | :func:`~flask.stream_template_string` functions automatically 85 | use :func:`~flask.stream_with_context` if a request is active. 86 | -------------------------------------------------------------------------------- /docs/patterns/subclassing.rst: -------------------------------------------------------------------------------- 1 | Subclassing Flask 2 | ================= 3 | 4 | The :class:`~flask.Flask` class is designed for subclassing. 5 | 6 | For example, you may want to override how request parameters are handled to preserve their order:: 7 | 8 | from flask import Flask, Request 9 | from werkzeug.datastructures import ImmutableOrderedMultiDict 10 | class MyRequest(Request): 11 | """Request subclass to override request parameter storage""" 12 | parameter_storage_class = ImmutableOrderedMultiDict 13 | class MyFlask(Flask): 14 | """Flask subclass using the custom request class""" 15 | request_class = MyRequest 16 | 17 | This is the recommended approach for overriding or augmenting Flask's internal functionality. 18 | -------------------------------------------------------------------------------- /docs/patterns/templateinheritance.rst: -------------------------------------------------------------------------------- 1 | Template Inheritance 2 | ==================== 3 | 4 | The most powerful part of Jinja is template inheritance. Template inheritance 5 | allows you to build a base "skeleton" template that contains all the common 6 | elements of your site and defines **blocks** that child templates can override. 7 | 8 | Sounds complicated but is very basic. It's easiest to understand it by starting 9 | with an example. 10 | 11 | 12 | Base Template 13 | ------------- 14 | 15 | This template, which we'll call :file:`layout.html`, defines a simple HTML skeleton 16 | document that you might use for a simple two-column page. It's the job of 17 | "child" templates to fill the empty blocks with content: 18 | 19 | .. sourcecode:: html+jinja 20 | 21 | 22 | 23 | 24 | {% block head %} 25 | 26 | {% block title %}{% endblock %} - My Webpage 27 | {% endblock %} 28 | 29 | 30 |
{% block content %}{% endblock %}
31 | 36 | 37 | 38 | 39 | In this example, the ``{% block %}`` tags define four blocks that child templates 40 | can fill in. All the `block` tag does is tell the template engine that a 41 | child template may override those portions of the template. 42 | 43 | Child Template 44 | -------------- 45 | 46 | A child template might look like this: 47 | 48 | .. sourcecode:: html+jinja 49 | 50 | {% extends "layout.html" %} 51 | {% block title %}Index{% endblock %} 52 | {% block head %} 53 | {{ super() }} 54 | 57 | {% endblock %} 58 | {% block content %} 59 |

Index

60 |

61 | Welcome on my awesome homepage. 62 | {% endblock %} 63 | 64 | The ``{% extends %}`` tag is the key here. It tells the template engine that 65 | this template "extends" another template. When the template system evaluates 66 | this template, first it locates the parent. The extends tag must be the 67 | first tag in the template. To render the contents of a block defined in 68 | the parent template, use ``{{ super() }}``. 69 | -------------------------------------------------------------------------------- /docs/server.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: flask 2 | 3 | Development Server 4 | ================== 5 | 6 | Flask provides a ``run`` command to run the application with a development server. In 7 | debug mode, this server provides an interactive debugger and will reload when code is 8 | changed. 9 | 10 | .. warning:: 11 | 12 | Do not use the development server when deploying to production. It 13 | is intended for use only during local development. It is not 14 | designed to be particularly efficient, stable, or secure. 15 | 16 | See :doc:`/deploying/index` for deployment options. 17 | 18 | Command Line 19 | ------------ 20 | 21 | The ``flask run`` CLI command is the recommended way to run the development server. Use 22 | the ``--app`` option to point to your application, and the ``--debug`` option to enable 23 | debug mode. 24 | 25 | .. code-block:: text 26 | 27 | $ flask --app hello run --debug 28 | 29 | This enables debug mode, including the interactive debugger and reloader, and then 30 | starts the server on http://localhost:5000/. Use ``flask run --help`` to see the 31 | available options, and :doc:`/cli` for detailed instructions about configuring and using 32 | the CLI. 33 | 34 | 35 | .. _address-already-in-use: 36 | 37 | Address already in use 38 | ~~~~~~~~~~~~~~~~~~~~~~ 39 | 40 | If another program is already using port 5000, you'll see an ``OSError`` 41 | when the server tries to start. It may have one of the following 42 | messages: 43 | 44 | - ``OSError: [Errno 98] Address already in use`` 45 | - ``OSError: [WinError 10013] An attempt was made to access a socket 46 | in a way forbidden by its access permissions`` 47 | 48 | Either identify and stop the other program, or use 49 | ``flask run --port 5001`` to pick a different port. 50 | 51 | You can use ``netstat`` or ``lsof`` to identify what process id is using 52 | a port, then use other operating system tools stop that process. The 53 | following example shows that process id 6847 is using port 5000. 54 | 55 | .. tabs:: 56 | 57 | .. tab:: ``netstat`` (Linux) 58 | 59 | .. code-block:: text 60 | 61 | $ netstat -nlp | grep 5000 62 | tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 6847/python 63 | 64 | .. tab:: ``lsof`` (macOS / Linux) 65 | 66 | .. code-block:: text 67 | 68 | $ lsof -P -i :5000 69 | Python 6847 IPv4 TCP localhost:5000 (LISTEN) 70 | 71 | .. tab:: ``netstat`` (Windows) 72 | 73 | .. code-block:: text 74 | 75 | > netstat -ano | findstr 5000 76 | TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING 6847 77 | 78 | macOS Monterey and later automatically starts a service that uses port 79 | 5000. You can choose to disable this service instead of using a different port by 80 | searching for "AirPlay Receiver" in System Preferences and toggling it off. 81 | 82 | 83 | Deferred Errors on Reload 84 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 85 | 86 | When using the ``flask run`` command with the reloader, the server will 87 | continue to run even if you introduce syntax errors or other 88 | initialization errors into the code. Accessing the site will show the 89 | interactive debugger for the error, rather than crashing the server. 90 | 91 | If a syntax error is already present when calling ``flask run``, it will 92 | fail immediately and show the traceback rather than waiting until the 93 | site is accessed. This is intended to make errors more visible initially 94 | while still allowing the server to handle errors on reload. 95 | 96 | 97 | In Code 98 | ------- 99 | 100 | The development server can also be started from Python with the :meth:`Flask.run` 101 | method. This method takes arguments similar to the CLI options to control the server. 102 | The main difference from the CLI command is that the server will crash if there are 103 | errors when reloading. ``debug=True`` can be passed to enable debug mode. 104 | 105 | Place the call in a main block, otherwise it will interfere when trying to import and 106 | run the application with a production server later. 107 | 108 | .. code-block:: python 109 | 110 | if __name__ == "__main__": 111 | app.run(debug=True) 112 | 113 | .. code-block:: text 114 | 115 | $ python hello.py 116 | -------------------------------------------------------------------------------- /docs/shell.rst: -------------------------------------------------------------------------------- 1 | Working with the Shell 2 | ====================== 3 | 4 | .. versionadded:: 0.3 5 | 6 | One of the reasons everybody loves Python is the interactive shell. It 7 | basically allows you to execute Python commands in real time and 8 | immediately get results back. Flask itself does not come with an 9 | interactive shell, because it does not require any specific setup upfront, 10 | just import your application and start playing around. 11 | 12 | There are however some handy helpers to make playing around in the shell a 13 | more pleasant experience. The main issue with interactive console 14 | sessions is that you're not triggering a request like a browser does which 15 | means that :data:`~flask.g`, :data:`~flask.request` and others are not 16 | available. But the code you want to test might depend on them, so what 17 | can you do? 18 | 19 | This is where some helper functions come in handy. Keep in mind however 20 | that these functions are not only there for interactive shell usage, but 21 | also for unit testing and other situations that require a faked request 22 | context. 23 | 24 | Generally it's recommended that you read :doc:`reqcontext` first. 25 | 26 | Command Line Interface 27 | ---------------------- 28 | 29 | Starting with Flask 0.11 the recommended way to work with the shell is the 30 | ``flask shell`` command which does a lot of this automatically for you. 31 | For instance the shell is automatically initialized with a loaded 32 | application context. 33 | 34 | For more information see :doc:`/cli`. 35 | 36 | Creating a Request Context 37 | -------------------------- 38 | 39 | The easiest way to create a proper request context from the shell is by 40 | using the :attr:`~flask.Flask.test_request_context` method which creates 41 | us a :class:`~flask.ctx.RequestContext`: 42 | 43 | >>> ctx = app.test_request_context() 44 | 45 | Normally you would use the ``with`` statement to make this request object 46 | active, but in the shell it's easier to use the 47 | :meth:`~flask.ctx.RequestContext.push` and 48 | :meth:`~flask.ctx.RequestContext.pop` methods by hand: 49 | 50 | >>> ctx.push() 51 | 52 | From that point onwards you can work with the request object until you 53 | call `pop`: 54 | 55 | >>> ctx.pop() 56 | 57 | Firing Before/After Request 58 | --------------------------- 59 | 60 | By just creating a request context, you still don't have run the code that 61 | is normally run before a request. This might result in your database 62 | being unavailable if you are connecting to the database in a 63 | before-request callback or the current user not being stored on the 64 | :data:`~flask.g` object etc. 65 | 66 | This however can easily be done yourself. Just call 67 | :meth:`~flask.Flask.preprocess_request`: 68 | 69 | >>> ctx = app.test_request_context() 70 | >>> ctx.push() 71 | >>> app.preprocess_request() 72 | 73 | Keep in mind that the :meth:`~flask.Flask.preprocess_request` function 74 | might return a response object, in that case just ignore it. 75 | 76 | To shutdown a request, you need to trick a bit before the after request 77 | functions (triggered by :meth:`~flask.Flask.process_response`) operate on 78 | a response object: 79 | 80 | >>> app.process_response(app.response_class()) 81 | 82 | >>> ctx.pop() 83 | 84 | The functions registered as :meth:`~flask.Flask.teardown_request` are 85 | automatically called when the context is popped. So this is the perfect 86 | place to automatically tear down resources that were needed by the request 87 | context (such as database connections). 88 | 89 | 90 | Further Improving the Shell Experience 91 | -------------------------------------- 92 | 93 | If you like the idea of experimenting in a shell, create yourself a module 94 | with stuff you want to star import into your interactive session. There 95 | you could also define some more helper methods for common things such as 96 | initializing the database, dropping tables etc. 97 | 98 | Just put them into a module (like `shelltools`) and import from there: 99 | 100 | >>> from shelltools import * 101 | -------------------------------------------------------------------------------- /docs/tutorial/deploy.rst: -------------------------------------------------------------------------------- 1 | Deploy to Production 2 | ==================== 3 | 4 | This part of the tutorial assumes you have a server that you want to 5 | deploy your application to. It gives an overview of how to create the 6 | distribution file and install it, but won't go into specifics about 7 | what server or software to use. You can set up a new environment on your 8 | development computer to try out the instructions below, but probably 9 | shouldn't use it for hosting a real public application. See 10 | :doc:`/deploying/index` for a list of many different ways to host your 11 | application. 12 | 13 | 14 | Build and Install 15 | ----------------- 16 | 17 | When you want to deploy your application elsewhere, you build a *wheel* 18 | (``.whl``) file. Install and use the ``build`` tool to do this. 19 | 20 | .. code-block:: none 21 | 22 | $ pip install build 23 | $ python -m build --wheel 24 | 25 | You can find the file in ``dist/flaskr-1.0.0-py3-none-any.whl``. The 26 | file name is in the format of {project name}-{version}-{python tag} 27 | -{abi tag}-{platform tag}. 28 | 29 | Copy this file to another machine, 30 | :ref:`set up a new virtualenv `, then install the 31 | file with ``pip``. 32 | 33 | .. code-block:: none 34 | 35 | $ pip install flaskr-1.0.0-py3-none-any.whl 36 | 37 | Pip will install your project along with its dependencies. 38 | 39 | Since this is a different machine, you need to run ``init-db`` again to 40 | create the database in the instance folder. 41 | 42 | .. code-block:: text 43 | 44 | $ flask --app flaskr init-db 45 | 46 | When Flask detects that it's installed (not in editable mode), it uses 47 | a different directory for the instance folder. You can find it at 48 | ``.venv/var/flaskr-instance`` instead. 49 | 50 | 51 | Configure the Secret Key 52 | ------------------------ 53 | 54 | In the beginning of the tutorial that you gave a default value for 55 | :data:`SECRET_KEY`. This should be changed to some random bytes in 56 | production. Otherwise, attackers could use the public ``'dev'`` key to 57 | modify the session cookie, or anything else that uses the secret key. 58 | 59 | You can use the following command to output a random secret key: 60 | 61 | .. code-block:: none 62 | 63 | $ python -c 'import secrets; print(secrets.token_hex())' 64 | 65 | '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' 66 | 67 | Create the ``config.py`` file in the instance folder, which the factory 68 | will read from if it exists. Copy the generated value into it. 69 | 70 | .. code-block:: python 71 | :caption: ``.venv/var/flaskr-instance/config.py`` 72 | 73 | SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' 74 | 75 | You can also set any other necessary configuration here, although 76 | ``SECRET_KEY`` is the only one needed for Flaskr. 77 | 78 | 79 | Run with a Production Server 80 | ---------------------------- 81 | 82 | When running publicly rather than in development, you should not use the 83 | built-in development server (``flask run``). The development server is 84 | provided by Werkzeug for convenience, but is not designed to be 85 | particularly efficient, stable, or secure. 86 | 87 | Instead, use a production WSGI server. For example, to use `Waitress`_, 88 | first install it in the virtual environment: 89 | 90 | .. code-block:: none 91 | 92 | $ pip install waitress 93 | 94 | You need to tell Waitress about your application, but it doesn't use 95 | ``--app`` like ``flask run`` does. You need to tell it to import and 96 | call the application factory to get an application object. 97 | 98 | .. code-block:: none 99 | 100 | $ waitress-serve --call 'flaskr:create_app' 101 | 102 | Serving on http://0.0.0.0:8080 103 | 104 | See :doc:`/deploying/index` for a list of many different ways to host 105 | your application. Waitress is just an example, chosen for the tutorial 106 | because it supports both Windows and Linux. There are many more WSGI 107 | servers and deployment options that you may choose for your project. 108 | 109 | .. _Waitress: https://docs.pylonsproject.org/projects/waitress/en/stable/ 110 | 111 | Continue to :doc:`next`. 112 | -------------------------------------------------------------------------------- /docs/tutorial/flaskr_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/tutorial/flaskr_edit.png -------------------------------------------------------------------------------- /docs/tutorial/flaskr_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/tutorial/flaskr_index.png -------------------------------------------------------------------------------- /docs/tutorial/flaskr_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/docs/tutorial/flaskr_login.png -------------------------------------------------------------------------------- /docs/tutorial/index.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | .. toctree:: 5 | :caption: Contents: 6 | :maxdepth: 1 7 | 8 | layout 9 | factory 10 | database 11 | views 12 | templates 13 | static 14 | blog 15 | install 16 | tests 17 | deploy 18 | next 19 | 20 | This tutorial will walk you through creating a basic blog application 21 | called Flaskr. Users will be able to register, log in, create posts, 22 | and edit or delete their own posts. You will be able to package and 23 | install the application on other computers. 24 | 25 | .. image:: flaskr_index.png 26 | :align: center 27 | :class: screenshot 28 | :alt: screenshot of index page 29 | 30 | It's assumed that you're already familiar with Python. The `official 31 | tutorial`_ in the Python docs is a great way to learn or review first. 32 | 33 | .. _official tutorial: https://docs.python.org/3/tutorial/ 34 | 35 | While it's designed to give a good starting point, the tutorial doesn't 36 | cover all of Flask's features. Check out the :doc:`/quickstart` for an 37 | overview of what Flask can do, then dive into the docs to find out more. 38 | The tutorial only uses what's provided by Flask and Python. In another 39 | project, you might decide to use :doc:`/extensions` or other libraries 40 | to make some tasks simpler. 41 | 42 | .. image:: flaskr_login.png 43 | :align: center 44 | :class: screenshot 45 | :alt: screenshot of login page 46 | 47 | Flask is flexible. It doesn't require you to use any particular project 48 | or code layout. However, when first starting, it's helpful to use a more 49 | structured approach. This means that the tutorial will require a bit of 50 | boilerplate up front, but it's done to avoid many common pitfalls that 51 | new developers encounter, and it creates a project that's easy to expand 52 | on. Once you become more comfortable with Flask, you can step out of 53 | this structure and take full advantage of Flask's flexibility. 54 | 55 | .. image:: flaskr_edit.png 56 | :align: center 57 | :class: screenshot 58 | :alt: screenshot of edit page 59 | 60 | :gh:`The tutorial project is available as an example in the Flask 61 | repository `, if you want to compare your project 62 | with the final product as you follow the tutorial. 63 | 64 | Continue to :doc:`layout`. 65 | -------------------------------------------------------------------------------- /docs/tutorial/install.rst: -------------------------------------------------------------------------------- 1 | Make the Project Installable 2 | ============================ 3 | 4 | Making your project installable means that you can build a *wheel* file and install that 5 | in another environment, just like you installed Flask in your project's environment. 6 | This makes deploying your project the same as installing any other library, so you're 7 | using all the standard Python tools to manage everything. 8 | 9 | Installing also comes with other benefits that might not be obvious from 10 | the tutorial or as a new Python user, including: 11 | 12 | * Currently, Python and Flask understand how to use the ``flaskr`` 13 | package only because you're running from your project's directory. 14 | Installing means you can import it no matter where you run from. 15 | 16 | * You can manage your project's dependencies just like other packages 17 | do, so ``pip install yourproject.whl`` installs them. 18 | 19 | * Test tools can isolate your test environment from your development 20 | environment. 21 | 22 | .. note:: 23 | This is being introduced late in the tutorial, but in your future 24 | projects you should always start with this. 25 | 26 | 27 | Describe the Project 28 | -------------------- 29 | 30 | The ``pyproject.toml`` file describes your project and how to build it. 31 | 32 | .. code-block:: toml 33 | :caption: ``pyproject.toml`` 34 | 35 | [project] 36 | name = "flaskr" 37 | version = "1.0.0" 38 | description = "The basic blog app built in the Flask tutorial." 39 | dependencies = [ 40 | "flask", 41 | ] 42 | 43 | [build-system] 44 | requires = ["flit_core<4"] 45 | build-backend = "flit_core.buildapi" 46 | 47 | See the official `Packaging tutorial `_ for more 48 | explanation of the files and options used. 49 | 50 | .. _packaging tutorial: https://packaging.python.org/tutorials/packaging-projects/ 51 | 52 | 53 | Install the Project 54 | ------------------- 55 | 56 | Use ``pip`` to install your project in the virtual environment. 57 | 58 | .. code-block:: none 59 | 60 | $ pip install -e . 61 | 62 | This tells pip to find ``pyproject.toml`` in the current directory and install the 63 | project in *editable* or *development* mode. Editable mode means that as you make 64 | changes to your local code, you'll only need to re-install if you change the metadata 65 | about the project, such as its dependencies. 66 | 67 | You can observe that the project is now installed with ``pip list``. 68 | 69 | .. code-block:: none 70 | 71 | $ pip list 72 | 73 | Package Version Location 74 | -------------- --------- ---------------------------------- 75 | click 6.7 76 | Flask 1.0 77 | flaskr 1.0.0 /home/user/Projects/flask-tutorial 78 | itsdangerous 0.24 79 | Jinja2 2.10 80 | MarkupSafe 1.0 81 | pip 9.0.3 82 | Werkzeug 0.14.1 83 | 84 | Nothing changes from how you've been running your project so far. 85 | ``--app`` is still set to ``flaskr`` and ``flask run`` still runs 86 | the application, but you can call it from anywhere, not just the 87 | ``flask-tutorial`` directory. 88 | 89 | Continue to :doc:`tests`. 90 | -------------------------------------------------------------------------------- /docs/tutorial/layout.rst: -------------------------------------------------------------------------------- 1 | Project Layout 2 | ============== 3 | 4 | Create a project directory and enter it: 5 | 6 | .. code-block:: none 7 | 8 | $ mkdir flask-tutorial 9 | $ cd flask-tutorial 10 | 11 | Then follow the :doc:`installation instructions ` to set 12 | up a Python virtual environment and install Flask for your project. 13 | 14 | The tutorial will assume you're working from the ``flask-tutorial`` 15 | directory from now on. The file names at the top of each code block are 16 | relative to this directory. 17 | 18 | ---- 19 | 20 | A Flask application can be as simple as a single file. 21 | 22 | .. code-block:: python 23 | :caption: ``hello.py`` 24 | 25 | from flask import Flask 26 | 27 | app = Flask(__name__) 28 | 29 | 30 | @app.route('/') 31 | def hello(): 32 | return 'Hello, World!' 33 | 34 | However, as a project gets bigger, it becomes overwhelming to keep all 35 | the code in one file. Python projects use *packages* to organize code 36 | into multiple modules that can be imported where needed, and the 37 | tutorial will do this as well. 38 | 39 | The project directory will contain: 40 | 41 | * ``flaskr/``, a Python package containing your application code and 42 | files. 43 | * ``tests/``, a directory containing test modules. 44 | * ``.venv/``, a Python virtual environment where Flask and other 45 | dependencies are installed. 46 | * Installation files telling Python how to install your project. 47 | * Version control config, such as `git`_. You should make a habit of 48 | using some type of version control for all your projects, no matter 49 | the size. 50 | * Any other project files you might add in the future. 51 | 52 | .. _git: https://git-scm.com/ 53 | 54 | By the end, your project layout will look like this: 55 | 56 | .. code-block:: none 57 | 58 | /home/user/Projects/flask-tutorial 59 | ├── flaskr/ 60 | │ ├── __init__.py 61 | │ ├── db.py 62 | │ ├── schema.sql 63 | │ ├── auth.py 64 | │ ├── blog.py 65 | │ ├── templates/ 66 | │ │ ├── base.html 67 | │ │ ├── auth/ 68 | │ │ │ ├── login.html 69 | │ │ │ └── register.html 70 | │ │ └── blog/ 71 | │ │ ├── create.html 72 | │ │ ├── index.html 73 | │ │ └── update.html 74 | │ └── static/ 75 | │ └── style.css 76 | ├── tests/ 77 | │ ├── conftest.py 78 | │ ├── data.sql 79 | │ ├── test_factory.py 80 | │ ├── test_db.py 81 | │ ├── test_auth.py 82 | │ └── test_blog.py 83 | ├── .venv/ 84 | ├── pyproject.toml 85 | └── MANIFEST.in 86 | 87 | If you're using version control, the following files that are generated 88 | while running your project should be ignored. There may be other files 89 | based on the editor you use. In general, ignore files that you didn't 90 | write. For example, with git: 91 | 92 | .. code-block:: none 93 | :caption: ``.gitignore`` 94 | 95 | .venv/ 96 | 97 | *.pyc 98 | __pycache__/ 99 | 100 | instance/ 101 | 102 | .pytest_cache/ 103 | .coverage 104 | htmlcov/ 105 | 106 | dist/ 107 | build/ 108 | *.egg-info/ 109 | 110 | Continue to :doc:`factory`. 111 | -------------------------------------------------------------------------------- /docs/tutorial/next.rst: -------------------------------------------------------------------------------- 1 | Keep Developing! 2 | ================ 3 | 4 | You've learned about quite a few Flask and Python concepts throughout 5 | the tutorial. Go back and review the tutorial and compare your code with 6 | the steps you took to get there. Compare your project to the 7 | :gh:`example project `, which might look a bit 8 | different due to the step-by-step nature of the tutorial. 9 | 10 | There's a lot more to Flask than what you've seen so far. Even so, 11 | you're now equipped to start developing your own web applications. Check 12 | out the :doc:`/quickstart` for an overview of what Flask can do, then 13 | dive into the docs to keep learning. Flask uses `Jinja`_, `Click`_, 14 | `Werkzeug`_, and `ItsDangerous`_ behind the scenes, and they all have 15 | their own documentation too. You'll also be interested in 16 | :doc:`/extensions` which make tasks like working with the database or 17 | validating form data easier and more powerful. 18 | 19 | If you want to keep developing your Flaskr project, here are some ideas 20 | for what to try next: 21 | 22 | * A detail view to show a single post. Click a post's title to go to 23 | its page. 24 | * Like / unlike a post. 25 | * Comments. 26 | * Tags. Clicking a tag shows all the posts with that tag. 27 | * A search box that filters the index page by name. 28 | * Paged display. Only show 5 posts per page. 29 | * Upload an image to go along with a post. 30 | * Format posts using Markdown. 31 | * An RSS feed of new posts. 32 | 33 | Have fun and make awesome applications! 34 | 35 | .. _Jinja: https://palletsprojects.com/p/jinja/ 36 | .. _Click: https://palletsprojects.com/p/click/ 37 | .. _Werkzeug: https://palletsprojects.com/p/werkzeug/ 38 | .. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ 39 | -------------------------------------------------------------------------------- /docs/tutorial/static.rst: -------------------------------------------------------------------------------- 1 | Static Files 2 | ============ 3 | 4 | The authentication views and templates work, but they look very plain 5 | right now. Some `CSS`_ can be added to add style to the HTML layout you 6 | constructed. The style won't change, so it's a *static* file rather than 7 | a template. 8 | 9 | Flask automatically adds a ``static`` view that takes a path relative 10 | to the ``flaskr/static`` directory and serves it. The ``base.html`` 11 | template already has a link to the ``style.css`` file: 12 | 13 | .. code-block:: html+jinja 14 | 15 | {{ url_for('static', filename='style.css') }} 16 | 17 | Besides CSS, other types of static files might be files with JavaScript 18 | functions, or a logo image. They are all placed under the 19 | ``flaskr/static`` directory and referenced with 20 | ``url_for('static', filename='...')``. 21 | 22 | This tutorial isn't focused on how to write CSS, so you can just copy 23 | the following into the ``flaskr/static/style.css`` file: 24 | 25 | .. code-block:: css 26 | :caption: ``flaskr/static/style.css`` 27 | 28 | html { font-family: sans-serif; background: #eee; padding: 1rem; } 29 | body { max-width: 960px; margin: 0 auto; background: white; } 30 | h1 { font-family: serif; color: #377ba8; margin: 1rem 0; } 31 | a { color: #377ba8; } 32 | hr { border: none; border-top: 1px solid lightgray; } 33 | nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } 34 | nav h1 { flex: auto; margin: 0; } 35 | nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } 36 | nav ul { display: flex; list-style: none; margin: 0; padding: 0; } 37 | nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } 38 | .content { padding: 0 1rem 1rem; } 39 | .content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } 40 | .content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } 41 | .flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } 42 | .post > header { display: flex; align-items: flex-end; font-size: 0.85em; } 43 | .post > header > div:first-of-type { flex: auto; } 44 | .post > header h1 { font-size: 1.5em; margin-bottom: 0; } 45 | .post .about { color: slategray; font-style: italic; } 46 | .post .body { white-space: pre-line; } 47 | .content:last-child { margin-bottom: 0; } 48 | .content form { margin: 1em 0; display: flex; flex-direction: column; } 49 | .content label { font-weight: bold; margin-bottom: 0.5em; } 50 | .content input, .content textarea { margin-bottom: 1em; } 51 | .content textarea { min-height: 12em; resize: vertical; } 52 | input.danger { color: #cc2f2e; } 53 | input[type=submit] { align-self: start; min-width: 10em; } 54 | 55 | You can find a less compact version of ``style.css`` in the 56 | :gh:`example code `. 57 | 58 | Go to http://127.0.0.1:5000/auth/login and the page should look like the 59 | screenshot below. 60 | 61 | .. image:: flaskr_login.png 62 | :align: center 63 | :class: screenshot 64 | :alt: screenshot of login page 65 | 66 | You can read more about CSS from `Mozilla's documentation `_. If 67 | you change a static file, refresh the browser page. If the change 68 | doesn't show up, try clearing your browser's cache. 69 | 70 | .. _CSS: https://developer.mozilla.org/docs/Web/CSS 71 | 72 | Continue to :doc:`blog`. 73 | -------------------------------------------------------------------------------- /examples/celery/README.md: -------------------------------------------------------------------------------- 1 | Background Tasks with Celery 2 | ============================ 3 | 4 | This example shows how to configure Celery with Flask, how to set up an API for 5 | submitting tasks and polling results, and how to use that API with JavaScript. See 6 | [Flask's documentation about Celery](https://flask.palletsprojects.com/patterns/celery/). 7 | 8 | From this directory, create a virtualenv and install the application into it. Then run a 9 | Celery worker. 10 | 11 | ```shell 12 | $ python3 -m venv .venv 13 | $ . ./.venv/bin/activate 14 | $ pip install -r requirements.txt && pip install -e . 15 | $ celery -A make_celery worker --loglevel INFO 16 | ``` 17 | 18 | In a separate terminal, activate the virtualenv and run the Flask development server. 19 | 20 | ```shell 21 | $ . ./.venv/bin/activate 22 | $ flask -A task_app run --debug 23 | ``` 24 | 25 | Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling 26 | requests in the browser dev tools and the Flask logs. You can see the tasks submitting 27 | and completing in the Celery logs. 28 | -------------------------------------------------------------------------------- /examples/celery/make_celery.py: -------------------------------------------------------------------------------- 1 | from task_app import create_app 2 | 3 | flask_app = create_app() 4 | celery_app = flask_app.extensions["celery"] 5 | -------------------------------------------------------------------------------- /examples/celery/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "flask-example-celery" 3 | version = "1.0.0" 4 | description = "Example Flask application with Celery background tasks." 5 | readme = "README.md" 6 | classifiers = ["Private :: Do Not Upload"] 7 | dependencies = ["flask", "celery[redis]"] 8 | 9 | [build-system] 10 | requires = ["flit_core<4"] 11 | build-backend = "flit_core.buildapi" 12 | 13 | [tool.flit.module] 14 | name = "task_app" 15 | 16 | [tool.ruff] 17 | src = ["src"] 18 | -------------------------------------------------------------------------------- /examples/celery/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile --resolver=backtracking pyproject.toml 6 | # 7 | amqp==5.1.1 8 | # via kombu 9 | async-timeout==4.0.2 10 | # via redis 11 | billiard==3.6.4.0 12 | # via celery 13 | blinker==1.6.2 14 | # via flask 15 | celery[redis]==5.2.7 16 | # via flask-example-celery (pyproject.toml) 17 | click==8.1.3 18 | # via 19 | # celery 20 | # click-didyoumean 21 | # click-plugins 22 | # click-repl 23 | # flask 24 | click-didyoumean==0.3.0 25 | # via celery 26 | click-plugins==1.1.1 27 | # via celery 28 | click-repl==0.2.0 29 | # via celery 30 | flask==2.3.2 31 | # via flask-example-celery (pyproject.toml) 32 | itsdangerous==2.1.2 33 | # via flask 34 | jinja2==3.1.2 35 | # via flask 36 | kombu==5.2.4 37 | # via celery 38 | markupsafe==2.1.2 39 | # via 40 | # jinja2 41 | # werkzeug 42 | prompt-toolkit==3.0.38 43 | # via click-repl 44 | pytz==2023.3 45 | # via celery 46 | redis==4.5.4 47 | # via celery 48 | six==1.16.0 49 | # via click-repl 50 | vine==5.0.0 51 | # via 52 | # amqp 53 | # celery 54 | # kombu 55 | wcwidth==0.2.6 56 | # via prompt-toolkit 57 | werkzeug==2.3.3 58 | # via flask 59 | -------------------------------------------------------------------------------- /examples/celery/src/task_app/__init__.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | from celery import Task 3 | from flask import Flask 4 | from flask import render_template 5 | 6 | 7 | def create_app() -> Flask: 8 | app = Flask(__name__) 9 | app.config.from_mapping( 10 | CELERY=dict( 11 | broker_url="redis://localhost", 12 | result_backend="redis://localhost", 13 | task_ignore_result=True, 14 | ), 15 | ) 16 | app.config.from_prefixed_env() 17 | celery_init_app(app) 18 | 19 | @app.route("/") 20 | def index() -> str: 21 | return render_template("index.html") 22 | 23 | from . import views 24 | 25 | app.register_blueprint(views.bp) 26 | return app 27 | 28 | 29 | def celery_init_app(app: Flask) -> Celery: 30 | class FlaskTask(Task): 31 | def __call__(self, *args: object, **kwargs: object) -> object: 32 | with app.app_context(): 33 | return self.run(*args, **kwargs) 34 | 35 | celery_app = Celery(app.name, task_cls=FlaskTask) 36 | celery_app.config_from_object(app.config["CELERY"]) 37 | celery_app.set_default() 38 | app.extensions["celery"] = celery_app 39 | return celery_app 40 | -------------------------------------------------------------------------------- /examples/celery/src/task_app/tasks.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from celery import shared_task 4 | from celery import Task 5 | 6 | 7 | @shared_task(ignore_result=False) 8 | def add(a: int, b: int) -> int: 9 | return a + b 10 | 11 | 12 | @shared_task() 13 | def block() -> None: 14 | time.sleep(5) 15 | 16 | 17 | @shared_task(bind=True, ignore_result=False) 18 | def process(self: Task, total: int) -> object: 19 | for i in range(total): 20 | self.update_state(state="PROGRESS", meta={"current": i + 1, "total": total}) 21 | time.sleep(1) 22 | 23 | return {"current": total, "total": total} 24 | -------------------------------------------------------------------------------- /examples/celery/src/task_app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Celery Example 6 | 7 | 8 |

Celery Example

9 | Execute background tasks with Celery. Submits tasks and shows results using JavaScript. 10 | 11 |
12 |

Add

13 |

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

15 |
16 |
17 | 18 |
19 |

Result:

20 | 21 |
22 |

Block

23 |

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

25 | 26 |
27 |

28 | 29 |
30 |

Process

31 |

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

33 |
34 | 35 |
36 |

37 | 38 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /examples/celery/src/task_app/views.py: -------------------------------------------------------------------------------- 1 | from celery.result import AsyncResult 2 | from flask import Blueprint 3 | from flask import request 4 | 5 | from . import tasks 6 | 7 | bp = Blueprint("tasks", __name__, url_prefix="/tasks") 8 | 9 | 10 | @bp.get("/result/") 11 | def result(id: str) -> dict[str, object]: 12 | result = AsyncResult(id) 13 | ready = result.ready() 14 | return { 15 | "ready": ready, 16 | "successful": result.successful() if ready else None, 17 | "value": result.get() if ready else result.result, 18 | } 19 | 20 | 21 | @bp.post("/add") 22 | def add() -> dict[str, object]: 23 | a = request.form.get("a", type=int) 24 | b = request.form.get("b", type=int) 25 | result = tasks.add.delay(a, b) 26 | return {"result_id": result.id} 27 | 28 | 29 | @bp.post("/block") 30 | def block() -> dict[str, object]: 31 | result = tasks.block.delay() 32 | return {"result_id": result.id} 33 | 34 | 35 | @bp.post("/process") 36 | def process() -> dict[str, object]: 37 | result = tasks.process.delay(total=request.form.get("total", type=int)) 38 | return {"result_id": result.id} 39 | -------------------------------------------------------------------------------- /examples/javascript/.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | *.pyc 3 | __pycache__/ 4 | instance/ 5 | .cache/ 6 | .pytest_cache/ 7 | .coverage 8 | htmlcov/ 9 | dist/ 10 | build/ 11 | *.egg-info/ 12 | .idea/ 13 | *.swp 14 | *~ 15 | -------------------------------------------------------------------------------- /examples/javascript/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /examples/javascript/README.rst: -------------------------------------------------------------------------------- 1 | JavaScript Ajax Example 2 | ======================= 3 | 4 | Demonstrates how to post form data and process a JSON response using 5 | JavaScript. This allows making requests without navigating away from the 6 | page. Demonstrates using |fetch|_, |XMLHttpRequest|_, and 7 | |jQuery.ajax|_. See the `Flask docs`_ about JavaScript and Ajax. 8 | 9 | .. |fetch| replace:: ``fetch`` 10 | .. _fetch: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch 11 | 12 | .. |XMLHttpRequest| replace:: ``XMLHttpRequest`` 13 | .. _XMLHttpRequest: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest 14 | 15 | .. |jQuery.ajax| replace:: ``jQuery.ajax`` 16 | .. _jQuery.ajax: https://api.jquery.com/jQuery.ajax/ 17 | 18 | .. _Flask docs: https://flask.palletsprojects.com/patterns/javascript/ 19 | 20 | 21 | Install 22 | ------- 23 | 24 | .. code-block:: text 25 | 26 | $ python3 -m venv .venv 27 | $ . .venv/bin/activate 28 | $ pip install -e . 29 | 30 | 31 | Run 32 | --- 33 | 34 | .. code-block:: text 35 | 36 | $ flask --app js_example run 37 | 38 | Open http://127.0.0.1:5000 in a browser. 39 | 40 | 41 | Test 42 | ---- 43 | 44 | .. code-block:: text 45 | 46 | $ pip install -e '.[test]' 47 | $ coverage run -m pytest 48 | $ coverage report 49 | -------------------------------------------------------------------------------- /examples/javascript/js_example/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | from js_example import views # noqa: E402, F401 6 | -------------------------------------------------------------------------------- /examples/javascript/js_example/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | JavaScript Example 3 | 4 | 5 | 14 | 23 |
24 |

{% block intro %}{% endblock %}

25 |
26 |
27 | 28 | + 29 | 30 | 31 |
32 | = 33 | {% block script %}{% endblock %} 34 | -------------------------------------------------------------------------------- /examples/javascript/js_example/templates/fetch.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block intro %} 4 | fetch 5 | is the modern plain JavaScript way to make requests. It's 6 | supported in all modern browsers. 7 | {% endblock %} 8 | 9 | {% block script %} 10 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /examples/javascript/js_example/templates/jquery.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block intro %} 4 | jQuery is a popular library that 5 | adds cross browser APIs for common tasks. However, it requires loading 6 | an extra library. 7 | {% endblock %} 8 | 9 | {% block script %} 10 | 11 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /examples/javascript/js_example/templates/xhr.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block intro %} 4 | XMLHttpRequest 5 | is the original JavaScript way to make requests. It's natively supported 6 | by all browsers, but has been superseded by 7 | fetch. 8 | {% endblock %} 9 | 10 | {% block script %} 11 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /examples/javascript/js_example/views.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | from flask import render_template 3 | from flask import request 4 | 5 | from . import app 6 | 7 | 8 | @app.route("/", defaults={"js": "fetch"}) 9 | @app.route("/") 10 | def index(js): 11 | return render_template(f"{js}.html", js=js) 12 | 13 | 14 | @app.route("/add", methods=["POST"]) 15 | def add(): 16 | a = request.form.get("a", 0, type=float) 17 | b = request.form.get("b", 0, type=float) 18 | return jsonify(result=a + b) 19 | -------------------------------------------------------------------------------- /examples/javascript/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "js_example" 3 | version = "1.1.0" 4 | description = "Demonstrates making AJAX requests to Flask." 5 | readme = "README.rst" 6 | license = {file = "LICENSE.txt"} 7 | maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] 8 | classifiers = ["Private :: Do Not Upload"] 9 | dependencies = ["flask"] 10 | 11 | [project.urls] 12 | Documentation = "https://flask.palletsprojects.com/patterns/javascript/" 13 | 14 | [project.optional-dependencies] 15 | test = ["pytest"] 16 | 17 | [build-system] 18 | requires = ["flit_core<4"] 19 | build-backend = "flit_core.buildapi" 20 | 21 | [tool.flit.module] 22 | name = "js_example" 23 | 24 | [tool.pytest.ini_options] 25 | testpaths = ["tests"] 26 | filterwarnings = ["error"] 27 | 28 | [tool.coverage.run] 29 | branch = true 30 | source = ["js_example", "tests"] 31 | 32 | [tool.ruff] 33 | src = ["src"] 34 | -------------------------------------------------------------------------------- /examples/javascript/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from js_example import app 4 | 5 | 6 | @pytest.fixture(name="app") 7 | def fixture_app(): 8 | app.testing = True 9 | yield app 10 | app.testing = False 11 | 12 | 13 | @pytest.fixture 14 | def client(app): 15 | return app.test_client() 16 | -------------------------------------------------------------------------------- /examples/javascript/tests/test_js_example.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask import template_rendered 3 | 4 | 5 | @pytest.mark.parametrize( 6 | ("path", "template_name"), 7 | ( 8 | ("/", "fetch.html"), 9 | ("/plain", "xhr.html"), 10 | ("/fetch", "fetch.html"), 11 | ("/jquery", "jquery.html"), 12 | ), 13 | ) 14 | def test_index(app, client, path, template_name): 15 | def check(sender, template, context): 16 | assert template.name == template_name 17 | 18 | with template_rendered.connected_to(check, app): 19 | client.get(path) 20 | 21 | 22 | @pytest.mark.parametrize( 23 | ("a", "b", "result"), ((2, 3, 5), (2.5, 3, 5.5), (2, None, 2), (2, "b", 2)) 24 | ) 25 | def test_add(client, a, b, result): 26 | response = client.post("/add", data={"a": a, "b": b}) 27 | assert response.get_json()["result"] == result 28 | -------------------------------------------------------------------------------- /examples/tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | *.pyc 3 | __pycache__/ 4 | instance/ 5 | .cache/ 6 | .pytest_cache/ 7 | .coverage 8 | htmlcov/ 9 | dist/ 10 | build/ 11 | *.egg-info/ 12 | .idea/ 13 | *.swp 14 | *~ 15 | -------------------------------------------------------------------------------- /examples/tutorial/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /examples/tutorial/README.rst: -------------------------------------------------------------------------------- 1 | Flaskr 2 | ====== 3 | 4 | The basic blog app built in the Flask `tutorial`_. 5 | 6 | .. _tutorial: https://flask.palletsprojects.com/tutorial/ 7 | 8 | 9 | Install 10 | ------- 11 | 12 | **Be sure to use the same version of the code as the version of the docs 13 | you're reading.** You probably want the latest tagged version, but the 14 | default Git version is the main branch. :: 15 | 16 | # clone the repository 17 | $ git clone https://github.com/pallets/flask 18 | $ cd flask 19 | # checkout the correct version 20 | $ git tag # shows the tagged versions 21 | $ git checkout latest-tag-found-above 22 | $ cd examples/tutorial 23 | 24 | Create a virtualenv and activate it:: 25 | 26 | $ python3 -m venv .venv 27 | $ . .venv/bin/activate 28 | 29 | Or on Windows cmd:: 30 | 31 | $ py -3 -m venv .venv 32 | $ .venv\Scripts\activate.bat 33 | 34 | Install Flaskr:: 35 | 36 | $ pip install -e . 37 | 38 | Or if you are using the main branch, install Flask from source before 39 | installing Flaskr:: 40 | 41 | $ pip install -e ../.. 42 | $ pip install -e . 43 | 44 | 45 | Run 46 | --- 47 | 48 | .. code-block:: text 49 | 50 | $ flask --app flaskr init-db 51 | $ flask --app flaskr run --debug 52 | 53 | Open http://127.0.0.1:5000 in a browser. 54 | 55 | 56 | Test 57 | ---- 58 | 59 | :: 60 | 61 | $ pip install '.[test]' 62 | $ pytest 63 | 64 | Run with coverage report:: 65 | 66 | $ coverage run -m pytest 67 | $ coverage report 68 | $ coverage html # open htmlcov/index.html in a browser 69 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask 4 | 5 | 6 | def create_app(test_config=None): 7 | """Create and configure an instance of the Flask application.""" 8 | app = Flask(__name__, instance_relative_config=True) 9 | app.config.from_mapping( 10 | # a default secret that should be overridden by instance config 11 | SECRET_KEY="dev", 12 | # store the database in the instance folder 13 | DATABASE=os.path.join(app.instance_path, "flaskr.sqlite"), 14 | ) 15 | 16 | if test_config is None: 17 | # load the instance config, if it exists, when not testing 18 | app.config.from_pyfile("config.py", silent=True) 19 | else: 20 | # load the test config if passed in 21 | app.config.update(test_config) 22 | 23 | # ensure the instance folder exists 24 | try: 25 | os.makedirs(app.instance_path) 26 | except OSError: 27 | pass 28 | 29 | @app.route("/hello") 30 | def hello(): 31 | return "Hello, World!" 32 | 33 | # register the database commands 34 | from . import db 35 | 36 | db.init_app(app) 37 | 38 | # apply the blueprints to the app 39 | from . import auth 40 | from . import blog 41 | 42 | app.register_blueprint(auth.bp) 43 | app.register_blueprint(blog.bp) 44 | 45 | # make url_for('index') == url_for('blog.index') 46 | # in another app, you might define a separate main index here with 47 | # app.route, while giving the blog blueprint a url_prefix, but for 48 | # the tutorial the blog will be the main index 49 | app.add_url_rule("/", endpoint="index") 50 | 51 | return app 52 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/auth.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | from flask import Blueprint 4 | from flask import flash 5 | from flask import g 6 | from flask import redirect 7 | from flask import render_template 8 | from flask import request 9 | from flask import session 10 | from flask import url_for 11 | from werkzeug.security import check_password_hash 12 | from werkzeug.security import generate_password_hash 13 | 14 | from .db import get_db 15 | 16 | bp = Blueprint("auth", __name__, url_prefix="/auth") 17 | 18 | 19 | def login_required(view): 20 | """View decorator that redirects anonymous users to the login page.""" 21 | 22 | @functools.wraps(view) 23 | def wrapped_view(**kwargs): 24 | if g.user is None: 25 | return redirect(url_for("auth.login")) 26 | 27 | return view(**kwargs) 28 | 29 | return wrapped_view 30 | 31 | 32 | @bp.before_app_request 33 | def load_logged_in_user(): 34 | """If a user id is stored in the session, load the user object from 35 | the database into ``g.user``.""" 36 | user_id = session.get("user_id") 37 | 38 | if user_id is None: 39 | g.user = None 40 | else: 41 | g.user = ( 42 | get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone() 43 | ) 44 | 45 | 46 | @bp.route("/register", methods=("GET", "POST")) 47 | def register(): 48 | """Register a new user. 49 | 50 | Validates that the username is not already taken. Hashes the 51 | password for security. 52 | """ 53 | if request.method == "POST": 54 | username = request.form["username"] 55 | password = request.form["password"] 56 | db = get_db() 57 | error = None 58 | 59 | if not username: 60 | error = "Username is required." 61 | elif not password: 62 | error = "Password is required." 63 | 64 | if error is None: 65 | try: 66 | db.execute( 67 | "INSERT INTO user (username, password) VALUES (?, ?)", 68 | (username, generate_password_hash(password)), 69 | ) 70 | db.commit() 71 | except db.IntegrityError: 72 | # The username was already taken, which caused the 73 | # commit to fail. Show a validation error. 74 | error = f"User {username} is already registered." 75 | else: 76 | # Success, go to the login page. 77 | return redirect(url_for("auth.login")) 78 | 79 | flash(error) 80 | 81 | return render_template("auth/register.html") 82 | 83 | 84 | @bp.route("/login", methods=("GET", "POST")) 85 | def login(): 86 | """Log in a registered user by adding the user id to the session.""" 87 | if request.method == "POST": 88 | username = request.form["username"] 89 | password = request.form["password"] 90 | db = get_db() 91 | error = None 92 | user = db.execute( 93 | "SELECT * FROM user WHERE username = ?", (username,) 94 | ).fetchone() 95 | 96 | if user is None: 97 | error = "Incorrect username." 98 | elif not check_password_hash(user["password"], password): 99 | error = "Incorrect password." 100 | 101 | if error is None: 102 | # store the user id in a new session and return to the index 103 | session.clear() 104 | session["user_id"] = user["id"] 105 | return redirect(url_for("index")) 106 | 107 | flash(error) 108 | 109 | return render_template("auth/login.html") 110 | 111 | 112 | @bp.route("/logout") 113 | def logout(): 114 | """Clear the current session, including the stored user id.""" 115 | session.clear() 116 | return redirect(url_for("index")) 117 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/blog.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask import flash 3 | from flask import g 4 | from flask import redirect 5 | from flask import render_template 6 | from flask import request 7 | from flask import url_for 8 | from werkzeug.exceptions import abort 9 | 10 | from .auth import login_required 11 | from .db import get_db 12 | 13 | bp = Blueprint("blog", __name__) 14 | 15 | 16 | @bp.route("/") 17 | def index(): 18 | """Show all the posts, most recent first.""" 19 | db = get_db() 20 | posts = db.execute( 21 | "SELECT p.id, title, body, created, author_id, username" 22 | " FROM post p JOIN user u ON p.author_id = u.id" 23 | " ORDER BY created DESC" 24 | ).fetchall() 25 | return render_template("blog/index.html", posts=posts) 26 | 27 | 28 | def get_post(id, check_author=True): 29 | """Get a post and its author by id. 30 | 31 | Checks that the id exists and optionally that the current user is 32 | the author. 33 | 34 | :param id: id of post to get 35 | :param check_author: require the current user to be the author 36 | :return: the post with author information 37 | :raise 404: if a post with the given id doesn't exist 38 | :raise 403: if the current user isn't the author 39 | """ 40 | post = ( 41 | get_db() 42 | .execute( 43 | "SELECT p.id, title, body, created, author_id, username" 44 | " FROM post p JOIN user u ON p.author_id = u.id" 45 | " WHERE p.id = ?", 46 | (id,), 47 | ) 48 | .fetchone() 49 | ) 50 | 51 | if post is None: 52 | abort(404, f"Post id {id} doesn't exist.") 53 | 54 | if check_author and post["author_id"] != g.user["id"]: 55 | abort(403) 56 | 57 | return post 58 | 59 | 60 | @bp.route("/create", methods=("GET", "POST")) 61 | @login_required 62 | def create(): 63 | """Create a new post for the current user.""" 64 | if request.method == "POST": 65 | title = request.form["title"] 66 | body = request.form["body"] 67 | error = None 68 | 69 | if not title: 70 | error = "Title is required." 71 | 72 | if error is not None: 73 | flash(error) 74 | else: 75 | db = get_db() 76 | db.execute( 77 | "INSERT INTO post (title, body, author_id) VALUES (?, ?, ?)", 78 | (title, body, g.user["id"]), 79 | ) 80 | db.commit() 81 | return redirect(url_for("blog.index")) 82 | 83 | return render_template("blog/create.html") 84 | 85 | 86 | @bp.route("//update", methods=("GET", "POST")) 87 | @login_required 88 | def update(id): 89 | """Update a post if the current user is the author.""" 90 | post = get_post(id) 91 | 92 | if request.method == "POST": 93 | title = request.form["title"] 94 | body = request.form["body"] 95 | error = None 96 | 97 | if not title: 98 | error = "Title is required." 99 | 100 | if error is not None: 101 | flash(error) 102 | else: 103 | db = get_db() 104 | db.execute( 105 | "UPDATE post SET title = ?, body = ? WHERE id = ?", (title, body, id) 106 | ) 107 | db.commit() 108 | return redirect(url_for("blog.index")) 109 | 110 | return render_template("blog/update.html", post=post) 111 | 112 | 113 | @bp.route("//delete", methods=("POST",)) 114 | @login_required 115 | def delete(id): 116 | """Delete a post. 117 | 118 | Ensures that the post exists and that the logged in user is the 119 | author of the post. 120 | """ 121 | get_post(id) 122 | db = get_db() 123 | db.execute("DELETE FROM post WHERE id = ?", (id,)) 124 | db.commit() 125 | return redirect(url_for("blog.index")) 126 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from datetime import datetime 3 | 4 | import click 5 | from flask import current_app 6 | from flask import g 7 | 8 | 9 | def get_db(): 10 | """Connect to the application's configured database. The connection 11 | is unique for each request and will be reused if this is called 12 | again. 13 | """ 14 | if "db" not in g: 15 | g.db = sqlite3.connect( 16 | current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES 17 | ) 18 | g.db.row_factory = sqlite3.Row 19 | 20 | return g.db 21 | 22 | 23 | def close_db(e=None): 24 | """If this request connected to the database, close the 25 | connection. 26 | """ 27 | db = g.pop("db", None) 28 | 29 | if db is not None: 30 | db.close() 31 | 32 | 33 | def init_db(): 34 | """Clear existing data and create new tables.""" 35 | db = get_db() 36 | 37 | with current_app.open_resource("schema.sql") as f: 38 | db.executescript(f.read().decode("utf8")) 39 | 40 | 41 | @click.command("init-db") 42 | def init_db_command(): 43 | """Clear existing data and create new tables.""" 44 | init_db() 45 | click.echo("Initialized the database.") 46 | 47 | 48 | sqlite3.register_converter("timestamp", lambda v: datetime.fromisoformat(v.decode())) 49 | 50 | 51 | def init_app(app): 52 | """Register database functions with the Flask app. This is called by 53 | the application factory. 54 | """ 55 | app.teardown_appcontext(close_db) 56 | app.cli.add_command(init_db_command) 57 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/schema.sql: -------------------------------------------------------------------------------- 1 | -- Initialize the database. 2 | -- Drop any existing data and create empty tables. 3 | 4 | DROP TABLE IF EXISTS user; 5 | DROP TABLE IF EXISTS post; 6 | 7 | CREATE TABLE user ( 8 | id INTEGER PRIMARY KEY AUTOINCREMENT, 9 | username TEXT UNIQUE NOT NULL, 10 | password TEXT NOT NULL 11 | ); 12 | 13 | CREATE TABLE post ( 14 | id INTEGER PRIMARY KEY AUTOINCREMENT, 15 | author_id INTEGER NOT NULL, 16 | created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 17 | title TEXT NOT NULL, 18 | body TEXT NOT NULL, 19 | FOREIGN KEY (author_id) REFERENCES user (id) 20 | ); 21 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/static/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | background: #eee; 4 | padding: 1rem; 5 | } 6 | 7 | body { 8 | max-width: 960px; 9 | margin: 0 auto; 10 | background: white; 11 | } 12 | 13 | h1, h2, h3, h4, h5, h6 { 14 | font-family: serif; 15 | color: #377ba8; 16 | margin: 1rem 0; 17 | } 18 | 19 | a { 20 | color: #377ba8; 21 | } 22 | 23 | hr { 24 | border: none; 25 | border-top: 1px solid lightgray; 26 | } 27 | 28 | nav { 29 | background: lightgray; 30 | display: flex; 31 | align-items: center; 32 | padding: 0 0.5rem; 33 | } 34 | 35 | nav h1 { 36 | flex: auto; 37 | margin: 0; 38 | } 39 | 40 | nav h1 a { 41 | text-decoration: none; 42 | padding: 0.25rem 0.5rem; 43 | } 44 | 45 | nav ul { 46 | display: flex; 47 | list-style: none; 48 | margin: 0; 49 | padding: 0; 50 | } 51 | 52 | nav ul li a, nav ul li span, header .action { 53 | display: block; 54 | padding: 0.5rem; 55 | } 56 | 57 | .content { 58 | padding: 0 1rem 1rem; 59 | } 60 | 61 | .content > header { 62 | border-bottom: 1px solid lightgray; 63 | display: flex; 64 | align-items: flex-end; 65 | } 66 | 67 | .content > header h1 { 68 | flex: auto; 69 | margin: 1rem 0 0.25rem 0; 70 | } 71 | 72 | .flash { 73 | margin: 1em 0; 74 | padding: 1em; 75 | background: #cae6f6; 76 | border: 1px solid #377ba8; 77 | } 78 | 79 | .post > header { 80 | display: flex; 81 | align-items: flex-end; 82 | font-size: 0.85em; 83 | } 84 | 85 | .post > header > div:first-of-type { 86 | flex: auto; 87 | } 88 | 89 | .post > header h1 { 90 | font-size: 1.5em; 91 | margin-bottom: 0; 92 | } 93 | 94 | .post .about { 95 | color: slategray; 96 | font-style: italic; 97 | } 98 | 99 | .post .body { 100 | white-space: pre-line; 101 | } 102 | 103 | .content:last-child { 104 | margin-bottom: 0; 105 | } 106 | 107 | .content form { 108 | margin: 1em 0; 109 | display: flex; 110 | flex-direction: column; 111 | } 112 | 113 | .content label { 114 | font-weight: bold; 115 | margin-bottom: 0.5em; 116 | } 117 | 118 | .content input, .content textarea { 119 | margin-bottom: 1em; 120 | } 121 | 122 | .content textarea { 123 | min-height: 12em; 124 | resize: vertical; 125 | } 126 | 127 | input.danger { 128 | color: #cc2f2e; 129 | } 130 | 131 | input[type=submit] { 132 | align-self: start; 133 | min-width: 10em; 134 | } 135 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block header %} 4 |

{% block title %}Log In{% endblock %}

5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/templates/auth/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block header %} 4 |

{% block title %}Register{% endblock %}

5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% block title %}{% endblock %} - Flaskr 3 | 4 | 16 |
17 |
18 | {% block header %}{% endblock %} 19 |
20 | {% for message in get_flashed_messages() %} 21 |
{{ message }}
22 | {% endfor %} 23 | {% block content %}{% endblock %} 24 |
25 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/templates/blog/create.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block header %} 4 |

{% block title %}New Post{% endblock %}

5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/templates/blog/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block header %} 4 |

{% block title %}Posts{% endblock %}

5 | {% if g.user %} 6 | New 7 | {% endif %} 8 | {% endblock %} 9 | 10 | {% block content %} 11 | {% for post in posts %} 12 |
13 |
14 |
15 |

{{ post['title'] }}

16 |
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
17 |
18 | {% if g.user['id'] == post['author_id'] %} 19 | Edit 20 | {% endif %} 21 |
22 |

{{ post['body'] }}

23 |
24 | {% if not loop.last %} 25 |
26 | {% endif %} 27 | {% endfor %} 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /examples/tutorial/flaskr/templates/blog/update.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block header %} 4 |

{% block title %}Edit "{{ post['title'] }}"{% endblock %}

5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /examples/tutorial/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "flaskr" 3 | version = "1.0.0" 4 | description = "The basic blog app built in the Flask tutorial." 5 | readme = "README.rst" 6 | license = {file = "LICENSE.txt"} 7 | maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] 8 | classifiers = ["Private :: Do Not Upload"] 9 | dependencies = [ 10 | "flask", 11 | ] 12 | 13 | [project.urls] 14 | Documentation = "https://flask.palletsprojects.com/tutorial/" 15 | 16 | [project.optional-dependencies] 17 | test = ["pytest"] 18 | 19 | [build-system] 20 | requires = ["flit_core<4"] 21 | build-backend = "flit_core.buildapi" 22 | 23 | [tool.flit.module] 24 | name = "flaskr" 25 | 26 | [tool.flit.sdist] 27 | include = [ 28 | "tests/", 29 | ] 30 | 31 | [tool.pytest.ini_options] 32 | testpaths = ["tests"] 33 | filterwarnings = ["error"] 34 | 35 | [tool.coverage.run] 36 | branch = true 37 | source = ["flaskr", "tests"] 38 | 39 | [tool.ruff] 40 | src = ["src"] 41 | -------------------------------------------------------------------------------- /examples/tutorial/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | 4 | import pytest 5 | 6 | from flaskr import create_app 7 | from flaskr.db import get_db 8 | from flaskr.db import init_db 9 | 10 | # read in SQL for populating test data 11 | with open(os.path.join(os.path.dirname(__file__), "data.sql"), "rb") as f: 12 | _data_sql = f.read().decode("utf8") 13 | 14 | 15 | @pytest.fixture 16 | def app(): 17 | """Create and configure a new app instance for each test.""" 18 | # create a temporary file to isolate the database for each test 19 | db_fd, db_path = tempfile.mkstemp() 20 | # create the app with common test config 21 | app = create_app({"TESTING": True, "DATABASE": db_path}) 22 | 23 | # create the database and load test data 24 | with app.app_context(): 25 | init_db() 26 | get_db().executescript(_data_sql) 27 | 28 | yield app 29 | 30 | # close and remove the temporary database 31 | os.close(db_fd) 32 | os.unlink(db_path) 33 | 34 | 35 | @pytest.fixture 36 | def client(app): 37 | """A test client for the app.""" 38 | return app.test_client() 39 | 40 | 41 | @pytest.fixture 42 | def runner(app): 43 | """A test runner for the app's Click commands.""" 44 | return app.test_cli_runner() 45 | 46 | 47 | class AuthActions: 48 | def __init__(self, client): 49 | self._client = client 50 | 51 | def login(self, username="test", password="test"): 52 | return self._client.post( 53 | "/auth/login", data={"username": username, "password": password} 54 | ) 55 | 56 | def logout(self): 57 | return self._client.get("/auth/logout") 58 | 59 | 60 | @pytest.fixture 61 | def auth(client): 62 | return AuthActions(client) 63 | -------------------------------------------------------------------------------- /examples/tutorial/tests/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO user (username, password) 2 | VALUES 3 | ('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'), 4 | ('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'); 5 | 6 | INSERT INTO post (title, body, author_id, created) 7 | VALUES 8 | ('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00'); 9 | -------------------------------------------------------------------------------- /examples/tutorial/tests/test_auth.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask import g 3 | from flask import session 4 | 5 | from flaskr.db import get_db 6 | 7 | 8 | def test_register(client, app): 9 | # test that viewing the page renders without template errors 10 | assert client.get("/auth/register").status_code == 200 11 | 12 | # test that successful registration redirects to the login page 13 | response = client.post("/auth/register", data={"username": "a", "password": "a"}) 14 | assert response.headers["Location"] == "/auth/login" 15 | 16 | # test that the user was inserted into the database 17 | with app.app_context(): 18 | assert ( 19 | get_db().execute("SELECT * FROM user WHERE username = 'a'").fetchone() 20 | is not None 21 | ) 22 | 23 | 24 | @pytest.mark.parametrize( 25 | ("username", "password", "message"), 26 | ( 27 | ("", "", b"Username is required."), 28 | ("a", "", b"Password is required."), 29 | ("test", "test", b"already registered"), 30 | ), 31 | ) 32 | def test_register_validate_input(client, username, password, message): 33 | response = client.post( 34 | "/auth/register", data={"username": username, "password": password} 35 | ) 36 | assert message in response.data 37 | 38 | 39 | def test_login(client, auth): 40 | # test that viewing the page renders without template errors 41 | assert client.get("/auth/login").status_code == 200 42 | 43 | # test that successful login redirects to the index page 44 | response = auth.login() 45 | assert response.headers["Location"] == "/" 46 | 47 | # login request set the user_id in the session 48 | # check that the user is loaded from the session 49 | with client: 50 | client.get("/") 51 | assert session["user_id"] == 1 52 | assert g.user["username"] == "test" 53 | 54 | 55 | @pytest.mark.parametrize( 56 | ("username", "password", "message"), 57 | (("a", "test", b"Incorrect username."), ("test", "a", b"Incorrect password.")), 58 | ) 59 | def test_login_validate_input(auth, username, password, message): 60 | response = auth.login(username, password) 61 | assert message in response.data 62 | 63 | 64 | def test_logout(client, auth): 65 | auth.login() 66 | 67 | with client: 68 | auth.logout() 69 | assert "user_id" not in session 70 | -------------------------------------------------------------------------------- /examples/tutorial/tests/test_blog.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from flaskr.db import get_db 4 | 5 | 6 | def test_index(client, auth): 7 | response = client.get("/") 8 | assert b"Log In" in response.data 9 | assert b"Register" in response.data 10 | 11 | auth.login() 12 | response = client.get("/") 13 | assert b"test title" in response.data 14 | assert b"by test on 2018-01-01" in response.data 15 | assert b"test\nbody" in response.data 16 | assert b'href="/1/update"' in response.data 17 | 18 | 19 | @pytest.mark.parametrize("path", ("/create", "/1/update", "/1/delete")) 20 | def test_login_required(client, path): 21 | response = client.post(path) 22 | assert response.headers["Location"] == "/auth/login" 23 | 24 | 25 | def test_author_required(app, client, auth): 26 | # change the post author to another user 27 | with app.app_context(): 28 | db = get_db() 29 | db.execute("UPDATE post SET author_id = 2 WHERE id = 1") 30 | db.commit() 31 | 32 | auth.login() 33 | # current user can't modify other user's post 34 | assert client.post("/1/update").status_code == 403 35 | assert client.post("/1/delete").status_code == 403 36 | # current user doesn't see edit link 37 | assert b'href="/1/update"' not in client.get("/").data 38 | 39 | 40 | @pytest.mark.parametrize("path", ("/2/update", "/2/delete")) 41 | def test_exists_required(client, auth, path): 42 | auth.login() 43 | assert client.post(path).status_code == 404 44 | 45 | 46 | def test_create(client, auth, app): 47 | auth.login() 48 | assert client.get("/create").status_code == 200 49 | client.post("/create", data={"title": "created", "body": ""}) 50 | 51 | with app.app_context(): 52 | db = get_db() 53 | count = db.execute("SELECT COUNT(id) FROM post").fetchone()[0] 54 | assert count == 2 55 | 56 | 57 | def test_update(client, auth, app): 58 | auth.login() 59 | assert client.get("/1/update").status_code == 200 60 | client.post("/1/update", data={"title": "updated", "body": ""}) 61 | 62 | with app.app_context(): 63 | db = get_db() 64 | post = db.execute("SELECT * FROM post WHERE id = 1").fetchone() 65 | assert post["title"] == "updated" 66 | 67 | 68 | @pytest.mark.parametrize("path", ("/create", "/1/update")) 69 | def test_create_update_validate(client, auth, path): 70 | auth.login() 71 | response = client.post(path, data={"title": "", "body": ""}) 72 | assert b"Title is required." in response.data 73 | 74 | 75 | def test_delete(client, auth, app): 76 | auth.login() 77 | response = client.post("/1/delete") 78 | assert response.headers["Location"] == "/" 79 | 80 | with app.app_context(): 81 | db = get_db() 82 | post = db.execute("SELECT * FROM post WHERE id = 1").fetchone() 83 | assert post is None 84 | -------------------------------------------------------------------------------- /examples/tutorial/tests/test_db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | import pytest 4 | 5 | from flaskr.db import get_db 6 | 7 | 8 | def test_get_close_db(app): 9 | with app.app_context(): 10 | db = get_db() 11 | assert db is get_db() 12 | 13 | with pytest.raises(sqlite3.ProgrammingError) as e: 14 | db.execute("SELECT 1") 15 | 16 | assert "closed" in str(e.value) 17 | 18 | 19 | def test_init_db_command(runner, monkeypatch): 20 | class Recorder: 21 | called = False 22 | 23 | def fake_init_db(): 24 | Recorder.called = True 25 | 26 | monkeypatch.setattr("flaskr.db.init_db", fake_init_db) 27 | result = runner.invoke(args=["init-db"]) 28 | assert "Initialized" in result.output 29 | assert Recorder.called 30 | -------------------------------------------------------------------------------- /examples/tutorial/tests/test_factory.py: -------------------------------------------------------------------------------- 1 | from flaskr import create_app 2 | 3 | 4 | def test_config(): 5 | """Test create_app without passing test config.""" 6 | assert not create_app().testing 7 | assert create_app({"TESTING": True}).testing 8 | 9 | 10 | def test_hello(client): 11 | response = client.get("/hello") 12 | assert response.data == b"Hello, World!" 13 | -------------------------------------------------------------------------------- /src/flask/__init__.py: -------------------------------------------------------------------------------- 1 | from . import json as json 2 | from .app import Flask as Flask 3 | from .blueprints import Blueprint as Blueprint 4 | from .config import Config as Config 5 | from .ctx import after_this_request as after_this_request 6 | from .ctx import copy_current_request_context as copy_current_request_context 7 | from .ctx import has_app_context as has_app_context 8 | from .ctx import has_request_context as has_request_context 9 | from .globals import current_app as current_app 10 | from .globals import g as g 11 | from .globals import request as request 12 | from .globals import session as session 13 | from .helpers import abort as abort 14 | from .helpers import flash as flash 15 | from .helpers import get_flashed_messages as get_flashed_messages 16 | from .helpers import get_template_attribute as get_template_attribute 17 | from .helpers import make_response as make_response 18 | from .helpers import redirect as redirect 19 | from .helpers import send_file as send_file 20 | from .helpers import send_from_directory as send_from_directory 21 | from .helpers import stream_with_context as stream_with_context 22 | from .helpers import url_for as url_for 23 | from .json import jsonify as jsonify 24 | from .signals import appcontext_popped as appcontext_popped 25 | from .signals import appcontext_pushed as appcontext_pushed 26 | from .signals import appcontext_tearing_down as appcontext_tearing_down 27 | from .signals import before_render_template as before_render_template 28 | from .signals import got_request_exception as got_request_exception 29 | from .signals import message_flashed as message_flashed 30 | from .signals import request_finished as request_finished 31 | from .signals import request_started as request_started 32 | from .signals import request_tearing_down as request_tearing_down 33 | from .signals import template_rendered as template_rendered 34 | from .templating import render_template as render_template 35 | from .templating import render_template_string as render_template_string 36 | from .templating import stream_template as stream_template 37 | from .templating import stream_template_string as stream_template_string 38 | from .wrappers import Request as Request 39 | from .wrappers import Response as Response 40 | -------------------------------------------------------------------------------- /src/flask/__main__.py: -------------------------------------------------------------------------------- 1 | from .cli import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /src/flask/globals.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | from contextvars import ContextVar 5 | 6 | from werkzeug.local import LocalProxy 7 | 8 | if t.TYPE_CHECKING: # pragma: no cover 9 | from .app import Flask 10 | from .ctx import _AppCtxGlobals 11 | from .ctx import AppContext 12 | from .ctx import RequestContext 13 | from .sessions import SessionMixin 14 | from .wrappers import Request 15 | 16 | 17 | _no_app_msg = """\ 18 | Working outside of application context. 19 | 20 | This typically means that you attempted to use functionality that needed 21 | the current application. To solve this, set up an application context 22 | with app.app_context(). See the documentation for more information.\ 23 | """ 24 | _cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") 25 | app_ctx: AppContext = LocalProxy( # type: ignore[assignment] 26 | _cv_app, unbound_message=_no_app_msg 27 | ) 28 | current_app: Flask = LocalProxy( # type: ignore[assignment] 29 | _cv_app, "app", unbound_message=_no_app_msg 30 | ) 31 | g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] 32 | _cv_app, "g", unbound_message=_no_app_msg 33 | ) 34 | 35 | _no_req_msg = """\ 36 | Working outside of request context. 37 | 38 | This typically means that you attempted to use functionality that needed 39 | an active HTTP request. Consult the documentation on testing for 40 | information about how to avoid this problem.\ 41 | """ 42 | _cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx") 43 | request_ctx: RequestContext = LocalProxy( # type: ignore[assignment] 44 | _cv_request, unbound_message=_no_req_msg 45 | ) 46 | request: Request = LocalProxy( # type: ignore[assignment] 47 | _cv_request, "request", unbound_message=_no_req_msg 48 | ) 49 | session: SessionMixin = LocalProxy( # type: ignore[assignment] 50 | _cv_request, "session", unbound_message=_no_req_msg 51 | ) 52 | -------------------------------------------------------------------------------- /src/flask/logging.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import sys 5 | import typing as t 6 | 7 | from werkzeug.local import LocalProxy 8 | 9 | from .globals import request 10 | 11 | if t.TYPE_CHECKING: # pragma: no cover 12 | from .sansio.app import App 13 | 14 | 15 | @LocalProxy 16 | def wsgi_errors_stream() -> t.TextIO: 17 | """Find the most appropriate error stream for the application. If a request 18 | is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``. 19 | 20 | If you configure your own :class:`logging.StreamHandler`, you may want to 21 | use this for the stream. If you are using file or dict configuration and 22 | can't import this directly, you can refer to it as 23 | ``ext://flask.logging.wsgi_errors_stream``. 24 | """ 25 | if request: 26 | return request.environ["wsgi.errors"] # type: ignore[no-any-return] 27 | 28 | return sys.stderr 29 | 30 | 31 | def has_level_handler(logger: logging.Logger) -> bool: 32 | """Check if there is a handler in the logging chain that will handle the 33 | given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`. 34 | """ 35 | level = logger.getEffectiveLevel() 36 | current = logger 37 | 38 | while current: 39 | if any(handler.level <= level for handler in current.handlers): 40 | return True 41 | 42 | if not current.propagate: 43 | break 44 | 45 | current = current.parent # type: ignore 46 | 47 | return False 48 | 49 | 50 | #: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format 51 | #: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``. 52 | default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore 53 | default_handler.setFormatter( 54 | logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s") 55 | ) 56 | 57 | 58 | def create_logger(app: App) -> logging.Logger: 59 | """Get the Flask app's logger and configure it if needed. 60 | 61 | The logger name will be the same as 62 | :attr:`app.import_name `. 63 | 64 | When :attr:`~flask.Flask.debug` is enabled, set the logger level to 65 | :data:`logging.DEBUG` if it is not set. 66 | 67 | If there is no handler for the logger's effective level, add a 68 | :class:`~logging.StreamHandler` for 69 | :func:`~flask.logging.wsgi_errors_stream` with a basic format. 70 | """ 71 | logger = logging.getLogger(app.name) 72 | 73 | if app.debug and not logger.level: 74 | logger.setLevel(logging.DEBUG) 75 | 76 | if not has_level_handler(logger): 77 | logger.addHandler(default_handler) 78 | 79 | return logger 80 | -------------------------------------------------------------------------------- /src/flask/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/src/flask/py.typed -------------------------------------------------------------------------------- /src/flask/sansio/README.md: -------------------------------------------------------------------------------- 1 | # Sansio 2 | 3 | This folder contains code that can be used by alternative Flask 4 | implementations, for example Quart. The code therefore cannot do any 5 | IO, nor be part of a likely IO path. Finally this code cannot use the 6 | Flask globals. 7 | -------------------------------------------------------------------------------- /src/flask/signals.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from blinker import Namespace 4 | 5 | # This namespace is only for signals provided by Flask itself. 6 | _signals = Namespace() 7 | 8 | template_rendered = _signals.signal("template-rendered") 9 | before_render_template = _signals.signal("before-render-template") 10 | request_started = _signals.signal("request-started") 11 | request_finished = _signals.signal("request-finished") 12 | request_tearing_down = _signals.signal("request-tearing-down") 13 | got_request_exception = _signals.signal("got-request-exception") 14 | appcontext_tearing_down = _signals.signal("appcontext-tearing-down") 15 | appcontext_pushed = _signals.signal("appcontext-pushed") 16 | appcontext_popped = _signals.signal("appcontext-popped") 17 | message_flashed = _signals.signal("message-flashed") 18 | -------------------------------------------------------------------------------- /src/flask/typing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import collections.abc as cabc 4 | import typing as t 5 | 6 | if t.TYPE_CHECKING: # pragma: no cover 7 | from _typeshed.wsgi import WSGIApplication # noqa: F401 8 | from werkzeug.datastructures import Headers # noqa: F401 9 | from werkzeug.sansio.response import Response # noqa: F401 10 | 11 | # The possible types that are directly convertible or are a Response object. 12 | ResponseValue = t.Union[ 13 | "Response", 14 | str, 15 | bytes, 16 | list[t.Any], 17 | # Only dict is actually accepted, but Mapping allows for TypedDict. 18 | t.Mapping[str, t.Any], 19 | t.Iterator[str], 20 | t.Iterator[bytes], 21 | cabc.AsyncIterable[str], # for Quart, until App is generic. 22 | cabc.AsyncIterable[bytes], 23 | ] 24 | 25 | # the possible types for an individual HTTP header 26 | HeaderValue = str | list[str] | tuple[str, ...] 27 | 28 | # the possible types for HTTP headers 29 | HeadersValue = t.Union[ 30 | "Headers", 31 | t.Mapping[str, HeaderValue], 32 | t.Sequence[tuple[str, HeaderValue]], 33 | ] 34 | 35 | # The possible types returned by a route function. 36 | ResponseReturnValue = t.Union[ 37 | ResponseValue, 38 | tuple[ResponseValue, HeadersValue], 39 | tuple[ResponseValue, int], 40 | tuple[ResponseValue, int, HeadersValue], 41 | "WSGIApplication", 42 | ] 43 | 44 | # Allow any subclass of werkzeug.Response, such as the one from Flask, 45 | # as a callback argument. Using werkzeug.Response directly makes a 46 | # callback annotated with flask.Response fail type checking. 47 | ResponseClass = t.TypeVar("ResponseClass", bound="Response") 48 | 49 | AppOrBlueprintKey = str | None # The App key is None, whereas blueprints are named 50 | AfterRequestCallable = ( 51 | t.Callable[[ResponseClass], ResponseClass] 52 | | t.Callable[[ResponseClass], t.Awaitable[ResponseClass]] 53 | ) 54 | BeforeFirstRequestCallable = t.Callable[[], None] | t.Callable[[], t.Awaitable[None]] 55 | BeforeRequestCallable = ( 56 | t.Callable[[], ResponseReturnValue | None] 57 | | t.Callable[[], t.Awaitable[ResponseReturnValue | None]] 58 | ) 59 | ShellContextProcessorCallable = t.Callable[[], dict[str, t.Any]] 60 | TeardownCallable = ( 61 | t.Callable[[BaseException | None], None] 62 | | t.Callable[[BaseException | None], t.Awaitable[None]] 63 | ) 64 | TemplateContextProcessorCallable = ( 65 | t.Callable[[], dict[str, t.Any]] | t.Callable[[], t.Awaitable[dict[str, t.Any]]] 66 | ) 67 | TemplateFilterCallable = t.Callable[..., t.Any] 68 | TemplateGlobalCallable = t.Callable[..., t.Any] 69 | TemplateTestCallable = t.Callable[..., bool] 70 | URLDefaultCallable = t.Callable[[str, dict[str, t.Any]], None] 71 | URLValuePreprocessorCallable = t.Callable[[str | None, dict[str, t.Any] | None], None] 72 | 73 | # This should take Exception, but that either breaks typing the argument 74 | # with a specific exception, or decorating multiple times with different 75 | # exceptions (and using a union type on the argument). 76 | # https://github.com/pallets/flask/issues/4095 77 | # https://github.com/pallets/flask/issues/4295 78 | # https://github.com/pallets/flask/issues/4297 79 | ErrorHandlerCallable = ( 80 | t.Callable[[t.Any], ResponseReturnValue] 81 | | t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]] 82 | ) 83 | 84 | RouteCallable = ( 85 | t.Callable[..., ResponseReturnValue] 86 | | t.Callable[..., t.Awaitable[ResponseReturnValue]] 87 | ) 88 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import pytest 5 | from _pytest import monkeypatch 6 | 7 | from flask import Flask 8 | from flask.globals import request_ctx 9 | 10 | 11 | @pytest.fixture(scope="session", autouse=True) 12 | def _standard_os_environ(): 13 | """Set up ``os.environ`` at the start of the test session to have 14 | standard values. Returns a list of operations that is used by 15 | :func:`._reset_os_environ` after each test. 16 | """ 17 | mp = monkeypatch.MonkeyPatch() 18 | out = ( 19 | (os.environ, "FLASK_ENV_FILE", monkeypatch.notset), 20 | (os.environ, "FLASK_APP", monkeypatch.notset), 21 | (os.environ, "FLASK_DEBUG", monkeypatch.notset), 22 | (os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset), 23 | (os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset), 24 | ) 25 | 26 | for _, key, value in out: 27 | if value is monkeypatch.notset: 28 | mp.delenv(key, False) 29 | else: 30 | mp.setenv(key, value) 31 | 32 | yield out 33 | mp.undo() 34 | 35 | 36 | @pytest.fixture(autouse=True) 37 | def _reset_os_environ(monkeypatch, _standard_os_environ): 38 | """Reset ``os.environ`` to the standard environ after each test, 39 | in case a test changed something without cleaning up. 40 | """ 41 | monkeypatch._setitem.extend(_standard_os_environ) 42 | 43 | 44 | @pytest.fixture 45 | def app(): 46 | app = Flask("flask_test", root_path=os.path.dirname(__file__)) 47 | app.config.update( 48 | TESTING=True, 49 | SECRET_KEY="test key", 50 | ) 51 | return app 52 | 53 | 54 | @pytest.fixture 55 | def app_ctx(app): 56 | with app.app_context() as ctx: 57 | yield ctx 58 | 59 | 60 | @pytest.fixture 61 | def req_ctx(app): 62 | with app.test_request_context() as ctx: 63 | yield ctx 64 | 65 | 66 | @pytest.fixture 67 | def client(app): 68 | return app.test_client() 69 | 70 | 71 | @pytest.fixture 72 | def test_apps(monkeypatch): 73 | monkeypatch.syspath_prepend(os.path.join(os.path.dirname(__file__), "test_apps")) 74 | original_modules = set(sys.modules.keys()) 75 | 76 | yield 77 | 78 | # Remove any imports cached during the test. Otherwise "import app" 79 | # will work in the next test even though it's no longer on the path. 80 | for key in sys.modules.keys() - original_modules: 81 | sys.modules.pop(key) 82 | 83 | 84 | @pytest.fixture(autouse=True) 85 | def leak_detector(): 86 | yield 87 | 88 | # make sure we're not leaking a request context since we are 89 | # testing flask internally in debug mode in a few cases 90 | leaks = [] 91 | while request_ctx: 92 | leaks.append(request_ctx._get_current_object()) 93 | request_ctx.pop() 94 | 95 | assert leaks == [] 96 | 97 | 98 | @pytest.fixture 99 | def modules_tmp_path(tmp_path, monkeypatch): 100 | """A temporary directory added to sys.path.""" 101 | rv = tmp_path / "modules_tmp" 102 | rv.mkdir() 103 | monkeypatch.syspath_prepend(os.fspath(rv)) 104 | return rv 105 | 106 | 107 | @pytest.fixture 108 | def modules_tmp_path_prefix(modules_tmp_path, monkeypatch): 109 | monkeypatch.setattr(sys, "prefix", os.fspath(modules_tmp_path)) 110 | return modules_tmp_path 111 | 112 | 113 | @pytest.fixture 114 | def site_packages(modules_tmp_path, monkeypatch): 115 | """Create a fake site-packages.""" 116 | py_dir = f"python{sys.version_info.major}.{sys.version_info.minor}" 117 | rv = modules_tmp_path / "lib" / py_dir / "site-packages" 118 | rv.mkdir(parents=True) 119 | monkeypatch.syspath_prepend(os.fspath(rv)) 120 | return rv 121 | 122 | 123 | @pytest.fixture 124 | def purge_module(request): 125 | def inner(name): 126 | request.addfinalizer(lambda: sys.modules.pop(name, None)) 127 | 128 | return inner 129 | -------------------------------------------------------------------------------- /tests/static/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "TEST_KEY": "foo", 3 | "SECRET_KEY": "config" 4 | } 5 | -------------------------------------------------------------------------------- /tests/static/config.toml: -------------------------------------------------------------------------------- 1 | TEST_KEY="foo" 2 | SECRET_KEY="config" 3 | -------------------------------------------------------------------------------- /tests/static/index.html: -------------------------------------------------------------------------------- 1 |

Hello World!

2 | -------------------------------------------------------------------------------- /tests/templates/_macro.html: -------------------------------------------------------------------------------- 1 | {% macro hello(name) %}Hello {{ name }}!{% endmacro %} 2 | -------------------------------------------------------------------------------- /tests/templates/context_template.html: -------------------------------------------------------------------------------- 1 |

{{ value }}|{{ injected_value }} 2 | -------------------------------------------------------------------------------- /tests/templates/escaping_template.html: -------------------------------------------------------------------------------- 1 | {{ text }} 2 | {{ html }} 3 | {% autoescape false %}{{ text }} 4 | {{ html }}{% endautoescape %} 5 | {% autoescape true %}{{ text }} 6 | {{ html }}{% endautoescape %} 7 | -------------------------------------------------------------------------------- /tests/templates/mail.txt: -------------------------------------------------------------------------------- 1 | {{ foo}} Mail 2 | -------------------------------------------------------------------------------- /tests/templates/nested/nested.txt: -------------------------------------------------------------------------------- 1 | I'm nested 2 | -------------------------------------------------------------------------------- /tests/templates/non_escaping_template.txt: -------------------------------------------------------------------------------- 1 | {{ text }} 2 | {{ html }} 3 | {% autoescape false %}{{ text }} 4 | {{ html }}{% endautoescape %} 5 | {% autoescape true %}{{ text }} 6 | {{ html }}{% endautoescape %} 7 | {{ text }} 8 | {{ html }} 9 | -------------------------------------------------------------------------------- /tests/templates/simple_template.html: -------------------------------------------------------------------------------- 1 |

{{ whiskey }}

2 | -------------------------------------------------------------------------------- /tests/templates/template_filter.html: -------------------------------------------------------------------------------- 1 | {{ value|super_reverse }} 2 | -------------------------------------------------------------------------------- /tests/templates/template_test.html: -------------------------------------------------------------------------------- 1 | {% if value is boolean %} 2 | Success! 3 | {% endif %} 4 | -------------------------------------------------------------------------------- /tests/test_apps/.env: -------------------------------------------------------------------------------- 1 | FOO=env 2 | SPAM=1 3 | EGGS=2 4 | HAM=火腿 5 | -------------------------------------------------------------------------------- /tests/test_apps/.flaskenv: -------------------------------------------------------------------------------- 1 | FOO=flaskenv 2 | BAR=bar 3 | EGGS=0 4 | -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | app.config["DEBUG"] = True 5 | from blueprintapp.apps.admin import admin # noqa: E402 6 | from blueprintapp.apps.frontend import frontend # noqa: E402 7 | 8 | app.register_blueprint(admin) 9 | app.register_blueprint(frontend) 10 | -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/tests/test_apps/blueprintapp/apps/__init__.py -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/apps/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask import render_template 3 | 4 | admin = Blueprint( 5 | "admin", 6 | __name__, 7 | url_prefix="/admin", 8 | template_folder="templates", 9 | static_folder="static", 10 | ) 11 | 12 | 13 | @admin.route("/") 14 | def index(): 15 | return render_template("admin/index.html") 16 | 17 | 18 | @admin.route("/index2") 19 | def index2(): 20 | return render_template("./admin/index.html") 21 | -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/apps/admin/static/css/test.css: -------------------------------------------------------------------------------- 1 | /* nested file */ 2 | -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/apps/admin/static/test.txt: -------------------------------------------------------------------------------- 1 | Admin File 2 | -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/apps/admin/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | Hello from the Admin 2 | -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/apps/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask import render_template 3 | 4 | frontend = Blueprint("frontend", __name__, template_folder="templates") 5 | 6 | 7 | @frontend.route("/") 8 | def index(): 9 | return render_template("frontend/index.html") 10 | 11 | 12 | @frontend.route("/missing") 13 | def missing_template(): 14 | return render_template("missing_template.html") 15 | -------------------------------------------------------------------------------- /tests/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html: -------------------------------------------------------------------------------- 1 | Hello from the Frontend 2 | -------------------------------------------------------------------------------- /tests/test_apps/cliapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/tests/test_apps/cliapp/__init__.py -------------------------------------------------------------------------------- /tests/test_apps/cliapp/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | testapp = Flask("testapp") 4 | -------------------------------------------------------------------------------- /tests/test_apps/cliapp/factory.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | 4 | def create_app(): 5 | return Flask("app") 6 | 7 | 8 | def create_app2(foo, bar): 9 | return Flask("_".join(["app2", foo, bar])) 10 | 11 | 12 | def no_app(): 13 | pass 14 | -------------------------------------------------------------------------------- /tests/test_apps/cliapp/importerrorapp.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | raise ImportError() 4 | 5 | testapp = Flask("testapp") 6 | -------------------------------------------------------------------------------- /tests/test_apps/cliapp/inner1/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | application = Flask(__name__) 4 | -------------------------------------------------------------------------------- /tests/test_apps/cliapp/inner1/inner2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/flask/a5f9742398c9429ef84ac8a57b0f3eb418394d9e/tests/test_apps/cliapp/inner1/inner2/__init__.py -------------------------------------------------------------------------------- /tests/test_apps/cliapp/inner1/inner2/flask.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | -------------------------------------------------------------------------------- /tests/test_apps/cliapp/message.txt: -------------------------------------------------------------------------------- 1 | So long, and thanks for all the fish. 2 | -------------------------------------------------------------------------------- /tests/test_apps/cliapp/multiapp.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app1 = Flask("app1") 4 | app2 = Flask("app2") 5 | -------------------------------------------------------------------------------- /tests/test_apps/helloworld/hello.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | 6 | @app.route("/") 7 | def hello(): 8 | return "Hello World!" 9 | -------------------------------------------------------------------------------- /tests/test_apps/helloworld/wsgi.py: -------------------------------------------------------------------------------- 1 | from hello import app # noqa: F401 2 | -------------------------------------------------------------------------------- /tests/test_apps/subdomaintestmodule/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Module 2 | 3 | mod = Module(__name__, "foo", subdomain="foo") 4 | -------------------------------------------------------------------------------- /tests/test_apps/subdomaintestmodule/static/hello.txt: -------------------------------------------------------------------------------- 1 | Hello Subdomain 2 | -------------------------------------------------------------------------------- /tests/test_async.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | from flask import Blueprint 6 | from flask import Flask 7 | from flask import request 8 | from flask.views import MethodView 9 | from flask.views import View 10 | 11 | pytest.importorskip("asgiref") 12 | 13 | 14 | class AppError(Exception): 15 | pass 16 | 17 | 18 | class BlueprintError(Exception): 19 | pass 20 | 21 | 22 | class AsyncView(View): 23 | methods = ["GET", "POST"] 24 | 25 | async def dispatch_request(self): 26 | await asyncio.sleep(0) 27 | return request.method 28 | 29 | 30 | class AsyncMethodView(MethodView): 31 | async def get(self): 32 | await asyncio.sleep(0) 33 | return "GET" 34 | 35 | async def post(self): 36 | await asyncio.sleep(0) 37 | return "POST" 38 | 39 | 40 | @pytest.fixture(name="async_app") 41 | def _async_app(): 42 | app = Flask(__name__) 43 | 44 | @app.route("/", methods=["GET", "POST"]) 45 | @app.route("/home", methods=["GET", "POST"]) 46 | async def index(): 47 | await asyncio.sleep(0) 48 | return request.method 49 | 50 | @app.errorhandler(AppError) 51 | async def handle(_): 52 | return "", 412 53 | 54 | @app.route("/error") 55 | async def error(): 56 | raise AppError() 57 | 58 | blueprint = Blueprint("bp", __name__) 59 | 60 | @blueprint.route("/", methods=["GET", "POST"]) 61 | async def bp_index(): 62 | await asyncio.sleep(0) 63 | return request.method 64 | 65 | @blueprint.errorhandler(BlueprintError) 66 | async def bp_handle(_): 67 | return "", 412 68 | 69 | @blueprint.route("/error") 70 | async def bp_error(): 71 | raise BlueprintError() 72 | 73 | app.register_blueprint(blueprint, url_prefix="/bp") 74 | 75 | app.add_url_rule("/view", view_func=AsyncView.as_view("view")) 76 | app.add_url_rule("/methodview", view_func=AsyncMethodView.as_view("methodview")) 77 | 78 | return app 79 | 80 | 81 | @pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"]) 82 | def test_async_route(path, async_app): 83 | test_client = async_app.test_client() 84 | response = test_client.get(path) 85 | assert b"GET" in response.get_data() 86 | response = test_client.post(path) 87 | assert b"POST" in response.get_data() 88 | 89 | 90 | @pytest.mark.parametrize("path", ["/error", "/bp/error"]) 91 | def test_async_error_handler(path, async_app): 92 | test_client = async_app.test_client() 93 | response = test_client.get(path) 94 | assert response.status_code == 412 95 | 96 | 97 | def test_async_before_after_request(): 98 | app_before_called = False 99 | app_after_called = False 100 | bp_before_called = False 101 | bp_after_called = False 102 | 103 | app = Flask(__name__) 104 | 105 | @app.route("/") 106 | def index(): 107 | return "" 108 | 109 | @app.before_request 110 | async def before(): 111 | nonlocal app_before_called 112 | app_before_called = True 113 | 114 | @app.after_request 115 | async def after(response): 116 | nonlocal app_after_called 117 | app_after_called = True 118 | return response 119 | 120 | blueprint = Blueprint("bp", __name__) 121 | 122 | @blueprint.route("/") 123 | def bp_index(): 124 | return "" 125 | 126 | @blueprint.before_request 127 | async def bp_before(): 128 | nonlocal bp_before_called 129 | bp_before_called = True 130 | 131 | @blueprint.after_request 132 | async def bp_after(response): 133 | nonlocal bp_after_called 134 | bp_after_called = True 135 | return response 136 | 137 | app.register_blueprint(blueprint, url_prefix="/bp") 138 | 139 | test_client = app.test_client() 140 | test_client.get("/") 141 | assert app_before_called 142 | assert app_after_called 143 | test_client.get("/bp/") 144 | assert bp_before_called 145 | assert bp_after_called 146 | -------------------------------------------------------------------------------- /tests/test_converters.py: -------------------------------------------------------------------------------- 1 | from werkzeug.routing import BaseConverter 2 | 3 | from flask import request 4 | from flask import session 5 | from flask import url_for 6 | 7 | 8 | def test_custom_converters(app, client): 9 | class ListConverter(BaseConverter): 10 | def to_python(self, value): 11 | return value.split(",") 12 | 13 | def to_url(self, value): 14 | base_to_url = super().to_url 15 | return ",".join(base_to_url(x) for x in value) 16 | 17 | app.url_map.converters["list"] = ListConverter 18 | 19 | @app.route("/") 20 | def index(args): 21 | return "|".join(args) 22 | 23 | assert client.get("/1,2,3").data == b"1|2|3" 24 | 25 | with app.test_request_context(): 26 | assert url_for("index", args=[4, 5, 6]) == "/4,5,6" 27 | 28 | 29 | def test_context_available(app, client): 30 | class ContextConverter(BaseConverter): 31 | def to_python(self, value): 32 | assert request is not None 33 | assert session is not None 34 | return value 35 | 36 | app.url_map.converters["ctx"] = ContextConverter 37 | 38 | @app.get("/") 39 | def index(name): 40 | return name 41 | 42 | assert client.get("/admin").data == b"admin" 43 | -------------------------------------------------------------------------------- /tests/test_instance_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | import flask 6 | 7 | 8 | def test_explicit_instance_paths(modules_tmp_path): 9 | with pytest.raises(ValueError, match=".*must be absolute"): 10 | flask.Flask(__name__, instance_path="instance") 11 | 12 | app = flask.Flask(__name__, instance_path=os.fspath(modules_tmp_path)) 13 | assert app.instance_path == os.fspath(modules_tmp_path) 14 | 15 | 16 | def test_uninstalled_module_paths(modules_tmp_path, purge_module): 17 | (modules_tmp_path / "config_module_app.py").write_text( 18 | "import os\n" 19 | "import flask\n" 20 | "here = os.path.abspath(os.path.dirname(__file__))\n" 21 | "app = flask.Flask(__name__)\n" 22 | ) 23 | purge_module("config_module_app") 24 | 25 | from config_module_app import app 26 | 27 | assert app.instance_path == os.fspath(modules_tmp_path / "instance") 28 | 29 | 30 | def test_uninstalled_package_paths(modules_tmp_path, purge_module): 31 | app = modules_tmp_path / "config_package_app" 32 | app.mkdir() 33 | (app / "__init__.py").write_text( 34 | "import os\n" 35 | "import flask\n" 36 | "here = os.path.abspath(os.path.dirname(__file__))\n" 37 | "app = flask.Flask(__name__)\n" 38 | ) 39 | purge_module("config_package_app") 40 | 41 | from config_package_app import app 42 | 43 | assert app.instance_path == os.fspath(modules_tmp_path / "instance") 44 | 45 | 46 | def test_uninstalled_namespace_paths(tmp_path, monkeypatch, purge_module): 47 | def create_namespace(package): 48 | project = tmp_path / f"project-{package}" 49 | monkeypatch.syspath_prepend(os.fspath(project)) 50 | ns = project / "namespace" / package 51 | ns.mkdir(parents=True) 52 | (ns / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n") 53 | return project 54 | 55 | _ = create_namespace("package1") 56 | project2 = create_namespace("package2") 57 | purge_module("namespace.package2") 58 | purge_module("namespace") 59 | 60 | from namespace.package2 import app 61 | 62 | assert app.instance_path == os.fspath(project2 / "instance") 63 | 64 | 65 | def test_installed_module_paths( 66 | modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages 67 | ): 68 | (site_packages / "site_app.py").write_text( 69 | "import flask\napp = flask.Flask(__name__)\n" 70 | ) 71 | purge_module("site_app") 72 | 73 | from site_app import app 74 | 75 | assert app.instance_path == os.fspath( 76 | modules_tmp_path / "var" / "site_app-instance" 77 | ) 78 | 79 | 80 | def test_installed_package_paths( 81 | modules_tmp_path, modules_tmp_path_prefix, purge_module, monkeypatch 82 | ): 83 | installed_path = modules_tmp_path / "path" 84 | installed_path.mkdir() 85 | monkeypatch.syspath_prepend(installed_path) 86 | 87 | app = installed_path / "installed_package" 88 | app.mkdir() 89 | (app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n") 90 | purge_module("installed_package") 91 | 92 | from installed_package import app 93 | 94 | assert app.instance_path == os.fspath( 95 | modules_tmp_path / "var" / "installed_package-instance" 96 | ) 97 | 98 | 99 | def test_prefix_package_paths( 100 | modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages 101 | ): 102 | app = site_packages / "site_package" 103 | app.mkdir() 104 | (app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n") 105 | purge_module("site_package") 106 | 107 | import site_package 108 | 109 | assert site_package.app.instance_path == os.fspath( 110 | modules_tmp_path / "var" / "site_package-instance" 111 | ) 112 | -------------------------------------------------------------------------------- /tests/test_json_tag.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from datetime import timezone 3 | from uuid import uuid4 4 | 5 | import pytest 6 | from markupsafe import Markup 7 | 8 | from flask.json.tag import JSONTag 9 | from flask.json.tag import TaggedJSONSerializer 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "data", 14 | ( 15 | {" t": (1, 2, 3)}, 16 | {" t__": b"a"}, 17 | {" di": " di"}, 18 | {"x": (1, 2, 3), "y": 4}, 19 | (1, 2, 3), 20 | [(1, 2, 3)], 21 | b"\xff", 22 | Markup(""), 23 | uuid4(), 24 | datetime.now(tz=timezone.utc).replace(microsecond=0), 25 | ), 26 | ) 27 | def test_dump_load_unchanged(data): 28 | s = TaggedJSONSerializer() 29 | assert s.loads(s.dumps(data)) == data 30 | 31 | 32 | def test_duplicate_tag(): 33 | class TagDict(JSONTag): 34 | key = " d" 35 | 36 | s = TaggedJSONSerializer() 37 | pytest.raises(KeyError, s.register, TagDict) 38 | s.register(TagDict, force=True, index=0) 39 | assert isinstance(s.tags[" d"], TagDict) 40 | assert isinstance(s.order[0], TagDict) 41 | 42 | 43 | def test_custom_tag(): 44 | class Foo: # noqa: B903, for Python2 compatibility 45 | def __init__(self, data): 46 | self.data = data 47 | 48 | class TagFoo(JSONTag): 49 | __slots__ = () 50 | key = " f" 51 | 52 | def check(self, value): 53 | return isinstance(value, Foo) 54 | 55 | def to_json(self, value): 56 | return self.serializer.tag(value.data) 57 | 58 | def to_python(self, value): 59 | return Foo(value) 60 | 61 | s = TaggedJSONSerializer() 62 | s.register(TagFoo) 63 | assert s.loads(s.dumps(Foo("bar"))).data == "bar" 64 | 65 | 66 | def test_tag_interface(): 67 | t = JSONTag(None) 68 | pytest.raises(NotImplementedError, t.check, None) 69 | pytest.raises(NotImplementedError, t.to_json, None) 70 | pytest.raises(NotImplementedError, t.to_python, None) 71 | 72 | 73 | def test_tag_order(): 74 | class Tag1(JSONTag): 75 | key = " 1" 76 | 77 | class Tag2(JSONTag): 78 | key = " 2" 79 | 80 | s = TaggedJSONSerializer() 81 | 82 | s.register(Tag1, index=-1) 83 | assert isinstance(s.order[-2], Tag1) 84 | 85 | s.register(Tag2, index=None) 86 | assert isinstance(s.order[-1], Tag2) 87 | -------------------------------------------------------------------------------- /tests/test_logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from io import StringIO 4 | 5 | import pytest 6 | 7 | from flask.logging import default_handler 8 | from flask.logging import has_level_handler 9 | from flask.logging import wsgi_errors_stream 10 | 11 | 12 | @pytest.fixture(autouse=True) 13 | def reset_logging(pytestconfig): 14 | root_handlers = logging.root.handlers[:] 15 | logging.root.handlers = [] 16 | root_level = logging.root.level 17 | 18 | logger = logging.getLogger("flask_test") 19 | logger.handlers = [] 20 | logger.setLevel(logging.NOTSET) 21 | 22 | logging_plugin = pytestconfig.pluginmanager.unregister(name="logging-plugin") 23 | 24 | yield 25 | 26 | logging.root.handlers[:] = root_handlers 27 | logging.root.setLevel(root_level) 28 | 29 | logger.handlers = [] 30 | logger.setLevel(logging.NOTSET) 31 | 32 | if logging_plugin: 33 | pytestconfig.pluginmanager.register(logging_plugin, "logging-plugin") 34 | 35 | 36 | def test_logger(app): 37 | assert app.logger.name == "flask_test" 38 | assert app.logger.level == logging.NOTSET 39 | assert app.logger.handlers == [default_handler] 40 | 41 | 42 | def test_logger_debug(app): 43 | app.debug = True 44 | assert app.logger.level == logging.DEBUG 45 | assert app.logger.handlers == [default_handler] 46 | 47 | 48 | def test_existing_handler(app): 49 | logging.root.addHandler(logging.StreamHandler()) 50 | assert app.logger.level == logging.NOTSET 51 | assert not app.logger.handlers 52 | 53 | 54 | def test_wsgi_errors_stream(app, client): 55 | @app.route("/") 56 | def index(): 57 | app.logger.error("test") 58 | return "" 59 | 60 | stream = StringIO() 61 | client.get("/", errors_stream=stream) 62 | assert "ERROR in test_logging: test" in stream.getvalue() 63 | 64 | assert wsgi_errors_stream._get_current_object() is sys.stderr 65 | 66 | with app.test_request_context(errors_stream=stream): 67 | assert wsgi_errors_stream._get_current_object() is stream 68 | 69 | 70 | def test_has_level_handler(): 71 | logger = logging.getLogger("flask.app") 72 | assert not has_level_handler(logger) 73 | 74 | handler = logging.StreamHandler() 75 | logging.root.addHandler(handler) 76 | assert has_level_handler(logger) 77 | 78 | logger.propagate = False 79 | assert not has_level_handler(logger) 80 | logger.propagate = True 81 | 82 | handler.setLevel(logging.ERROR) 83 | assert not has_level_handler(logger) 84 | 85 | 86 | def test_log_view_exception(app, client): 87 | @app.route("/") 88 | def index(): 89 | raise Exception("test") 90 | 91 | app.testing = False 92 | stream = StringIO() 93 | rv = client.get("/", errors_stream=stream) 94 | assert rv.status_code == 500 95 | assert rv.data 96 | err = stream.getvalue() 97 | assert "Exception on / [GET]" in err 98 | assert "Exception: test" in err 99 | -------------------------------------------------------------------------------- /tests/test_regression.py: -------------------------------------------------------------------------------- 1 | import flask 2 | 3 | 4 | def test_aborting(app): 5 | class Foo(Exception): 6 | whatever = 42 7 | 8 | @app.errorhandler(Foo) 9 | def handle_foo(e): 10 | return str(e.whatever) 11 | 12 | @app.route("/") 13 | def index(): 14 | raise flask.abort(flask.redirect(flask.url_for("test"))) 15 | 16 | @app.route("/test") 17 | def test(): 18 | raise Foo() 19 | 20 | with app.test_client() as c: 21 | rv = c.get("/") 22 | location_parts = rv.headers["Location"].rpartition("/") 23 | 24 | if location_parts[0]: 25 | # For older Werkzeug that used absolute redirects. 26 | assert location_parts[0] == "http://localhost" 27 | 28 | assert location_parts[2] == "test" 29 | rv = c.get("/test") 30 | assert rv.data == b"42" 31 | -------------------------------------------------------------------------------- /tests/test_request.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from flask import Flask 4 | from flask import Request 5 | from flask import request 6 | from flask.testing import FlaskClient 7 | 8 | 9 | def test_max_content_length(app: Flask, client: FlaskClient) -> None: 10 | app.config["MAX_CONTENT_LENGTH"] = 50 11 | 12 | @app.post("/") 13 | def index(): 14 | request.form["myfile"] 15 | AssertionError() 16 | 17 | @app.errorhandler(413) 18 | def catcher(error): 19 | return "42" 20 | 21 | rv = client.post("/", data={"myfile": "foo" * 50}) 22 | assert rv.data == b"42" 23 | 24 | 25 | def test_limit_config(app: Flask): 26 | app.config["MAX_CONTENT_LENGTH"] = 100 27 | app.config["MAX_FORM_MEMORY_SIZE"] = 50 28 | app.config["MAX_FORM_PARTS"] = 3 29 | r = Request({}) 30 | 31 | # no app context, use Werkzeug defaults 32 | assert r.max_content_length is None 33 | assert r.max_form_memory_size == 500_000 34 | assert r.max_form_parts == 1_000 35 | 36 | # in app context, use config 37 | with app.app_context(): 38 | assert r.max_content_length == 100 39 | assert r.max_form_memory_size == 50 40 | assert r.max_form_parts == 3 41 | 42 | # regardless of app context, use override 43 | r.max_content_length = 90 44 | r.max_form_memory_size = 30 45 | r.max_form_parts = 4 46 | 47 | assert r.max_content_length == 90 48 | assert r.max_form_memory_size == 30 49 | assert r.max_form_parts == 4 50 | 51 | with app.app_context(): 52 | assert r.max_content_length == 90 53 | assert r.max_form_memory_size == 30 54 | assert r.max_form_parts == 4 55 | 56 | 57 | def test_trusted_hosts_config(app: Flask) -> None: 58 | app.config["TRUSTED_HOSTS"] = ["example.test", ".other.test"] 59 | 60 | @app.get("/") 61 | def index() -> str: 62 | return "" 63 | 64 | client = app.test_client() 65 | r = client.get(base_url="http://example.test") 66 | assert r.status_code == 200 67 | r = client.get(base_url="http://a.other.test") 68 | assert r.status_code == 200 69 | r = client.get(base_url="http://bad.test") 70 | assert r.status_code == 400 71 | -------------------------------------------------------------------------------- /tests/test_session_interface.py: -------------------------------------------------------------------------------- 1 | import flask 2 | from flask.globals import request_ctx 3 | from flask.sessions import SessionInterface 4 | 5 | 6 | def test_open_session_with_endpoint(): 7 | """If request.endpoint (or other URL matching behavior) is needed 8 | while loading the session, RequestContext.match_request() can be 9 | called manually. 10 | """ 11 | 12 | class MySessionInterface(SessionInterface): 13 | def save_session(self, app, session, response): 14 | pass 15 | 16 | def open_session(self, app, request): 17 | request_ctx.match_request() 18 | assert request.endpoint is not None 19 | 20 | app = flask.Flask(__name__) 21 | app.session_interface = MySessionInterface() 22 | 23 | @app.get("/") 24 | def index(): 25 | return "Hello, World!" 26 | 27 | response = app.test_client().get("/") 28 | assert response.status_code == 200 29 | -------------------------------------------------------------------------------- /tests/test_subclassing.py: -------------------------------------------------------------------------------- 1 | from io import StringIO 2 | 3 | import flask 4 | 5 | 6 | def test_suppressed_exception_logging(): 7 | class SuppressedFlask(flask.Flask): 8 | def log_exception(self, exc_info): 9 | pass 10 | 11 | out = StringIO() 12 | app = SuppressedFlask(__name__) 13 | 14 | @app.route("/") 15 | def index(): 16 | raise Exception("test") 17 | 18 | rv = app.test_client().get("/", errors_stream=out) 19 | assert rv.status_code == 500 20 | assert b"Internal Server Error" in rv.data 21 | assert not out.getvalue() 22 | -------------------------------------------------------------------------------- /tests/type_check/typing_app_decorators.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from flask import Flask 4 | from flask import Response 5 | 6 | app = Flask(__name__) 7 | 8 | 9 | @app.after_request 10 | def after_sync(response: Response) -> Response: 11 | return Response() 12 | 13 | 14 | @app.after_request 15 | async def after_async(response: Response) -> Response: 16 | return Response() 17 | 18 | 19 | @app.before_request 20 | def before_sync() -> None: ... 21 | 22 | 23 | @app.before_request 24 | async def before_async() -> None: ... 25 | 26 | 27 | @app.teardown_appcontext 28 | def teardown_sync(exc: BaseException | None) -> None: ... 29 | 30 | 31 | @app.teardown_appcontext 32 | async def teardown_async(exc: BaseException | None) -> None: ... 33 | -------------------------------------------------------------------------------- /tests/type_check/typing_error_handler.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from http import HTTPStatus 4 | 5 | from werkzeug.exceptions import BadRequest 6 | from werkzeug.exceptions import NotFound 7 | 8 | from flask import Flask 9 | 10 | app = Flask(__name__) 11 | 12 | 13 | @app.errorhandler(400) 14 | @app.errorhandler(HTTPStatus.BAD_REQUEST) 15 | @app.errorhandler(BadRequest) 16 | def handle_400(e: BadRequest) -> str: 17 | return "" 18 | 19 | 20 | @app.errorhandler(ValueError) 21 | def handle_custom(e: ValueError) -> str: 22 | return "" 23 | 24 | 25 | @app.errorhandler(ValueError) 26 | def handle_accept_base(e: Exception) -> str: 27 | return "" 28 | 29 | 30 | @app.errorhandler(BadRequest) 31 | @app.errorhandler(404) 32 | def handle_multiple(e: BadRequest | NotFound) -> str: 33 | return "" 34 | -------------------------------------------------------------------------------- /tests/type_check/typing_route.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | from http import HTTPStatus 5 | 6 | from flask import Flask 7 | from flask import jsonify 8 | from flask import stream_template 9 | from flask.templating import render_template 10 | from flask.views import View 11 | from flask.wrappers import Response 12 | 13 | app = Flask(__name__) 14 | 15 | 16 | @app.route("/str") 17 | def hello_str() -> str: 18 | return "

Hello, World!

" 19 | 20 | 21 | @app.route("/bytes") 22 | def hello_bytes() -> bytes: 23 | return b"

Hello, World!

" 24 | 25 | 26 | @app.route("/json") 27 | def hello_json() -> Response: 28 | return jsonify("Hello, World!") 29 | 30 | 31 | @app.route("/json/dict") 32 | def hello_json_dict() -> dict[str, t.Any]: 33 | return {"response": "Hello, World!"} 34 | 35 | 36 | @app.route("/json/dict") 37 | def hello_json_list() -> list[t.Any]: 38 | return [{"message": "Hello"}, {"message": "World"}] 39 | 40 | 41 | class StatusJSON(t.TypedDict): 42 | status: str 43 | 44 | 45 | @app.route("/typed-dict") 46 | def typed_dict() -> StatusJSON: 47 | return {"status": "ok"} 48 | 49 | 50 | @app.route("/generator") 51 | def hello_generator() -> t.Generator[str, None, None]: 52 | def show() -> t.Generator[str, None, None]: 53 | for x in range(100): 54 | yield f"data:{x}\n\n" 55 | 56 | return show() 57 | 58 | 59 | @app.route("/generator-expression") 60 | def hello_generator_expression() -> t.Iterator[bytes]: 61 | return (f"data:{x}\n\n".encode() for x in range(100)) 62 | 63 | 64 | @app.route("/iterator") 65 | def hello_iterator() -> t.Iterator[str]: 66 | return iter([f"data:{x}\n\n" for x in range(100)]) 67 | 68 | 69 | @app.route("/status") 70 | @app.route("/status/") 71 | def tuple_status(code: int = 200) -> tuple[str, int]: 72 | return "hello", code 73 | 74 | 75 | @app.route("/status-enum") 76 | def tuple_status_enum() -> tuple[str, int]: 77 | return "hello", HTTPStatus.OK 78 | 79 | 80 | @app.route("/headers") 81 | def tuple_headers() -> tuple[str, dict[str, str]]: 82 | return "Hello, World!", {"Content-Type": "text/plain"} 83 | 84 | 85 | @app.route("/template") 86 | @app.route("/template/") 87 | def return_template(name: str | None = None) -> str: 88 | return render_template("index.html", name=name) 89 | 90 | 91 | @app.route("/template") 92 | def return_template_stream() -> t.Iterator[str]: 93 | return stream_template("index.html", name="Hello") 94 | 95 | 96 | @app.route("/async") 97 | async def async_route() -> str: 98 | return "Hello" 99 | 100 | 101 | class RenderTemplateView(View): 102 | def __init__(self: RenderTemplateView, template_name: str) -> None: 103 | self.template_name = template_name 104 | 105 | def dispatch_request(self: RenderTemplateView) -> str: 106 | return render_template(self.template_name) 107 | 108 | 109 | app.add_url_rule( 110 | "/about", 111 | view_func=RenderTemplateView.as_view("about_page", template_name="about.html"), 112 | ) 113 | --------------------------------------------------------------------------------