[go: up one dir, main page]

0% found this document useful (0 votes)
33 views319 pages

Developer's Flask Project Guide

Uploaded by

Isidro Arias
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views319 pages

Developer's Flask Project Guide

Uploaded by

Isidro Arias
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 319

Flask

flask-without_docs.d ......................................................................................
.gitignore.............................................................................................5
.readthedocs.yaml .................................................................................... 5
.editorconfig.........................................................................................5
.pre-commit-config.yaml.............................................................................5
.devcontainer............................................................................................
on-create-command.sh..............................................................................6
devcontainer.json.................................................................................6
README.md..............................................................................................6
tox.ini ................................................................................................ 7
LICENSE.txt ........................................................................................... 8
pyproject.toml........................................................................................8
CODE_OF_CONDUCT.md..................................................................................10
.github...................................................................................................
pull_request_template.md........................................................................11
ISSUE_TEMPLATE ...............................................................................
feature-request.md............................................................................11
config.yml.....................................................................................12
bug-report.md..................................................................................12
workflows.............................................................................................
pre-commit.yaml ............................................................................... 12
lock.yaml ...................................................................................... 13
tests.yaml.....................................................................................13
publish.yaml...................................................................................14
CONTRIBUTING.rst .................................................................................... 15
requirements............................................................................................
build.in...........................................................................................18
dev.in.............................................................................................18
tests.in...........................................................................................19
docs.in............................................................................................19
typing.in ......................................................................................... 19
tests-min.in......................................................................................19
build.txt ......................................................................................... 19
tests.txt ......................................................................................... 19
tests-dev.txt.....................................................................................20
tests-min.txt.....................................................................................20
typing.txt ........................................................................................ 20
docs.txt...........................................................................................21
dev.txt............................................................................................22
examples ................................................................................................
celery.................................................................................................
make_celery.py ................................................................................ 25
pyproject.toml ................................................................................ 25
README.md ...................................................................................... 25
requirements.txt..............................................................................26
src .................................................................................................
task_app ......................................................................................

1
2

tasks.py.................................................................................26
views.py.................................................................................27
__init__.py..............................................................................27
templates...................................................................................
index.html............................................................................28
javascript.............................................................................................
.gitignore.....................................................................................30
pyproject.toml ................................................................................ 30
tests ...............................................................................................
conftest.py.................................................................................30
test_js_example.py ........................................................................ 31
README.rst.....................................................................................31
LICENSE.txt....................................................................................32
js_example .......................................................................................
__init__.py.................................................................................32
views.py .................................................................................... 32
templates ......................................................................................
jquery.html..............................................................................33
fetch.html...............................................................................33
xhr.html.................................................................................34
base.html................................................................................34
tutorial...............................................................................................
.gitignore.....................................................................................35
pyproject.toml ................................................................................ 35
README.rst.....................................................................................36
LICENSE.txt....................................................................................37
tests ...............................................................................................
test_factory.py............................................................................37
data.sql .................................................................................... 37
test_db.py..................................................................................38
conftest.py.................................................................................38
test_auth.py................................................................................39
test_blog.py................................................................................40
flaskr ..............................................................................................
schema.sql..................................................................................41
db.py........................................................................................42
__init__.py.................................................................................42
static...........................................................................................
style.css................................................................................43
auth.py......................................................................................45
blog.py......................................................................................47
templates ......................................................................................
base.html................................................................................49
auth.........................................................................................
login.html............................................................................49
register.html........................................................................49
blog .........................................................................................
create.html .......................................................................... 50
update.html .......................................................................... 50
index.html............................................................................50
CHANGES.rst .......................................................................................... 51
tests......................................................................................................
static .................................................................................................
index.html.....................................................................................73
config.toml....................................................................................73
config.json....................................................................................73
3

test_subclassing.py..............................................................................74
templates.............................................................................................
nested .............................................................................................
nested.txt..................................................................................74
mail.txt........................................................................................74
simple_template.html ......................................................................... 74
template_filter.html ......................................................................... 74
context_template.html........................................................................74
template_test.html............................................................................74
_macro.html....................................................................................74
escaping_template.html.......................................................................74
non_escaping_template.txt ................................................................... 75
test_regression.py...............................................................................75
test_session_interface.py ...................................................................... 75
test_converters.py...............................................................................76
test_apps ............................................................................................
.flaskenv ...................................................................................... 76
.env ............................................................................................ 77
subdomaintestmodule............................................................................
static...........................................................................................
hello.txt................................................................................77
__init__.py.................................................................................77
helloworld.........................................................................................
wsgi.py......................................................................................77
hello.py .................................................................................... 77
cliapp..............................................................................................
__init__.py.................................................................................77
message.txt.................................................................................77
app.py.......................................................................................77
multiapp.py.................................................................................77
importerrorapp.py..........................................................................78
inner1..........................................................................................
inner2.......................................................................................
__init__.py .......................................................................... 78
flask.py..............................................................................78
__init__.py..............................................................................78
factory.py..................................................................................78
blueprintapp......................................................................................
__init__.py.................................................................................78
apps............................................................................................
__init__.py..............................................................................78
frontend ....................................................................................
templates................................................................................
frontend..............................................................................
index.html ..................................................................... 78
__init__.py .......................................................................... 79
admin.......................................................................................
templates................................................................................
admin.................................................................................
index.html ..................................................................... 79
static.....................................................................................
test.txt...........................................................................79
css.....................................................................................
test.css........................................................................79
__init__.py .......................................................................... 79
test_request.py .................................................................................. 79
4

test_json_tag.py.................................................................................80
test_logging.py .................................................................................. 82
test_async.py.....................................................................................83
test_instance_config.py.........................................................................85
type_check ..........................................................................................
typing_app_decorators.py .................................................................... 87
typing_error_handler.py......................................................................87
typing_route.py ............................................................................... 88
conftest.py.......................................................................................90
test_signals.py .................................................................................. 92
test_appctx.py....................................................................................94
test_views.py.....................................................................................97
test_config.py .................................................................................. 101
test_user_error_handler.py....................................................................105
test_reqctx.py .................................................................................. 109
test_json.py.....................................................................................114
test_helpers.py ................................................................................. 119
test_testing.py ................................................................................. 124
test_templating.py..............................................................................129
test_cli.py......................................................................................136
test_blueprints.py..............................................................................146
test_basic.py....................................................................................160
src........................................................................................................
flask...................................................................................................
py.typed ...................................................................................... 188
__main__.py...................................................................................188
signals.py....................................................................................188
globals.py....................................................................................188
logging.py....................................................................................189
__init__.py...................................................................................190
typing.py ..................................................................................... 191
blueprints.py ................................................................................ 192
debughelpers.py..............................................................................194
views.py ...................................................................................... 197
templating.py ................................................................................ 200
wrappers.py...................................................................................203
testing.py....................................................................................206
config.py ..................................................................................... 211
ctx.py.........................................................................................216
sessions.py...................................................................................222
json................................................................................................
__init__.py................................................................................228
provider.py................................................................................230
tag.py......................................................................................233
helpers.py....................................................................................238
cli.py.........................................................................................247
app.py.........................................................................................263
sansio..............................................................................................
README.md..................................................................................285
blueprints.py ............................................................................. 285
scaffold.py................................................................................294
app.py......................................................................................305
5

.gitignore
1 .idea/
2 .vscode/
3 .venv*/
4 venv*/
5 __pycache__/
6 dist/
7 .coverage*
8 htmlcov/
9 .tox/
10 docs/_build/

.readthedocs.yaml
1 version: 2
2 build:
3 os: ubuntu-22.04
4 tools:
5 python: '3.12'
6 python:
7 install:
8 - requirements: requirements/docs.txt
9 - method: pip
10 path: .
11 sphinx:
12 builder: dirhtml
13 fail_on_warning: true

.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

.pre-commit-config.yaml
1 repos:
2 - repo: https://github.com/astral-sh/ruff-pre-commit
3 rev: v0.7.3
4 hooks:
5 - id: ruff
6 - id: ruff-format
7 - repo: https://github.com/pre-commit/pre-commit-hooks
8 rev: v5.0.0
9 hooks:
10 - id: check-merge-conflict
11 - id: debug-statements
12 - id: fix-byte-order-marker
13 - id: trailing-whitespace
14 - id: end-of-file-fixer
6

.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

.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 }

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
19 ## A Simple Example
20
21 ```python
22 # save this as app.py
23 from flask import Flask
24
25 app = Flask(__name__)
26
27 @app.route("/")
28 def hello():
29 return "Hello, World!"
30 ```
31
32 ```
33 $ flask run
34 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
35 ```
36
7

37
38 ## Donate
39
40 The Pallets organization develops and supports Flask and the libraries
41 it uses. In order to grow the community of contributors and users, and
42 allow the maintainers to devote more time to the projects, [please
43 donate today][].
44
45 [please donate today]: https://palletsprojects.com/donate

tox.ini
1 [tox]
2 envlist =
3 py3{13,12,11,10,9}
4 pypy310
5 py313-min
6 py39-dev
7 style
8 typing
9 docs
10 skip_missing_interpreters = true
11
12 [testenv]
13 package = wheel
14 wheel_build_env = .pkg
15 envtmpdir = {toxworkdir}/tmp/{envname}
16 constrain_package_deps = true
17 use_frozen_constraints = true
18 deps =
19 -r requirements/tests.txt
20 min: -r requirements/tests-min.txt
21 dev: -r requirements/tests-dev.txt
22 commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs}
23
24 [testenv:style]
25 deps = pre-commit
26 skip_install = true
27 commands = pre-commit run --all-files
28
29 [testenv:typing]
30 deps = -r requirements/typing.txt
31 commands =
32 mypy
33 pyright
34
35 [testenv:docs]
36 deps = -r requirements/docs.txt
37 commands = sphinx-build -E -W -b dirhtml docs docs/_build/dirhtml
38
39 [testenv:update-actions]
40 labels = update
41 deps = gha-update
42 skip_install = true
43 commands = gha-update
44
45 [testenv:update-pre_commit]
46 labels = update
47 deps = pre-commit
48 skip_install = true
49 commands = pre-commit autoupdate -j4
50
51 [testenv:update-requirements]
52 labels = update
53 deps = pip-tools
54 skip_install = true
55 change_dir = requirements
56 commands =
57 pip-compile build.in -q {posargs:-U}
58 pip-compile docs.in -q {posargs:-U}
8

59 pip-compile tests.in -q {posargs:-U}


60 pip-compile tests-min.in -q
61 pip-compile typing.in -q {posargs:-U}
62 pip-compile dev.in -q {posargs:-U}

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.

pyproject.toml
1 [project]
2 name = "Flask"
3 version = "3.1.0"
4 description = "A simple framework for building complex web applications."
5 readme = "README.md"
6 license = {file = "LICENSE.txt"}
7 maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}]
8 classifiers = [
9 "Development Status :: 5 - Production/Stable",
10 "Environment :: Web Environment",
11 "Framework :: Flask",
12 "Intended Audience :: Developers",
13 "License :: OSI Approved :: BSD License",
14 "Operating System :: OS Independent",
15 "Programming Language :: Python",
16 "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
17 "Topic :: Internet :: WWW/HTTP :: WSGI",
18 "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
19 "Topic :: Software Development :: Libraries :: Application Frameworks",
20 "Typing :: Typed",
21 ]
22 requires-python = ">=3.9"
23 dependencies = [
24 "Werkzeug>=3.1",
25 "Jinja2>=3.1.2",
26 "itsdangerous>=2.2",
27 "click>=8.1.3",
28 "blinker>=1.9",
29 "importlib-metadata>=3.6; python_version < '3.10'",
30 ]
pyproject.toml 9

31
32 [project.urls]
33 Donate = "https://palletsprojects.com/donate"
34 Documentation = "https://flask.palletsprojects.com/"
35 Changes = "https://flask.palletsprojects.com/changes/"
36 Source = "https://github.com/pallets/flask/"
37 Chat = "https://discord.gg/pallets"
38
39 [project.optional-dependencies]
40 async = ["asgiref>=3.2"]
41 dotenv = ["python-dotenv"]
42
43 [project.scripts]
44 flask = "flask.cli:main"
45
46 [build-system]
47 requires = ["flit_core<4"]
48 build-backend = "flit_core.buildapi"
49
50 [tool.flit.module]
51 name = "flask"
52
53 [tool.flit.sdist]
54 include = [
55 "docs/",
56 "examples/",
57 "requirements/",
58 "tests/",
59 "CHANGES.rst",
60 "CONTRIBUTING.rst",
61 "tox.ini",
62 ]
63 exclude = [
64 "docs/_build/",
65 ]
66
67 [tool.pytest.ini_options]
68 testpaths = ["tests"]
69 filterwarnings = [
70 "error",
71 ]
72
73 [tool.coverage.run]
74 branch = true
75 source = ["flask", "tests"]
76
77 [tool.coverage.paths]
78 source = ["src", "*/site-packages"]
79
80 [tool.mypy]
81 python_version = "3.9"
82 files = ["src/flask", "tests/type_check"]
83 show_error_codes = true
84 pretty = true
85 strict = true
86
87 [[tool.mypy.overrides]]
88 module = [
89 "asgiref.*",
90 "dotenv.*",
91 "cryptography.*",
92 "importlib_metadata",
93 ]
94 ignore_missing_imports = true
95
96 [tool.pyright]
97 pythonVersion = "3.9"
98 include = ["src/flask", "tests/type_check"]
99 typeCheckingMode = "basic"
100
101 [tool.ruff]
10

102 src = ["src"]


103 fix = true
104 show-fixes = true
105 output-format = "full"
106
107 [tool.ruff.lint]
108 select = [
109 "B", # flake8-bugbear
110 "E", # pycodestyle error
111 "F", # pyflakes
112 "I", # isort
113 "UP", # pyupgrade
114 "W", # pycodestyle warning
115 ]
116
117 [tool.ruff.lint.isort]
118 force-single-line = true
119 order-by-type = false
120
121 [tool.gha-update]
122 tag-only = [
123 "slsa-framework/slsa-github-generator",
124 ]

CODE_OF_CONDUCT.md
1 # Contributor Covenant Code of Conduct
2
3 ## Our Pledge
4
5 In the interest of fostering an open and welcoming environment, we as
6 contributors and maintainers pledge to making participation in our project and
7 our community a harassment-free experience for everyone, regardless of age, body
8 size, disability, ethnicity, sex characteristics, gender identity and expression,
9 level of experience, education, socio-economic status, nationality, personal
10 appearance, race, religion, or sexual identity and orientation.
11
12 ## Our Standards
13
14 Examples of behavior that contributes to creating a positive environment
15 include:
16
17 * Using welcoming and inclusive language
18 * Being respectful of differing viewpoints and experiences
19 * Gracefully accepting constructive criticism
20 * Focusing on what is best for the community
21 * Showing empathy towards other community members
22
23 Examples of unacceptable behavior by participants include:
24
25 * The use of sexualized language or imagery and unwelcome sexual attention or
26 advances
27 * Trolling, insulting/derogatory comments, and personal or political attacks
28 * Public or private harassment
29 * Publishing others' private information, such as a physical or electronic
30 address, without explicit permission
31 * Other conduct which could reasonably be considered inappropriate in a
32 professional setting
33
34 ## Our Responsibilities
35
36 Project maintainers are responsible for clarifying the standards of acceptable
37 behavior and are expected to take appropriate and fair corrective action in
38 response to any instances of unacceptable behavior.
39
40 Project maintainers have the right and responsibility to remove, edit, or
41 reject comments, commits, code, wiki edits, issues, and other contributions
42 that are not aligned to this Code of Conduct, or to ban temporarily or
43 permanently any contributor for other behaviors that they deem inappropriate,
44 threatening, offensive, or harmful.
11

45
46 ## Scope
47
48 This Code of Conduct applies both within project spaces and in public spaces
49 when an individual is representing the project or its community. Examples of
50 representing a project or community include using an official project e-mail
51 address, posting via an official social media account, or acting as an appointed
52 representative at an online or offline event. Representation of a project may be
53 further defined and clarified by project maintainers.
54
55 ## Enforcement
56
57 Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 reported by contacting the project team at report@palletsprojects.com. All
59 complaints will be reviewed and investigated and will result in a response that
60 is deemed necessary and appropriate to the circumstances. The project team is
61 obligated to maintain confidentiality with regard to the reporter of an incident.
62 Further details of specific enforcement policies may be posted separately.
63
64 Project maintainers who do not follow or enforce the Code of Conduct in good
65 faith may face temporary or permanent repercussions as determined by other
66 members of the project's leadership.
67
68 ## Attribution
69
70 This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72
73 [homepage]: https://www.contributor-covenant.org
74
75 For answers to common questions about this code of conduct, see
76 https://www.contributor-covenant.org/faq

.github/pull_request_template.md
1 <!--
2 Before opening a PR, open a ticket describing the issue or feature the
3 PR will address. An issue is not required for fixing typos in
4 documentation, or other simple non-code changes.
5
6 Replace this comment with a description of the change. Describe how it
7 addresses the linked ticket.
8 -->
9
10 <!--
11 Link to relevant issues or previous PRs, one per line. Use "fixes" to
12 automatically close an issue.
13
14 fixes #<issue number>
15 -->
16
17 <!--
18 Ensure each step in CONTRIBUTING.rst is complete, especially the following:
19
20 - Add tests that demonstrate the correct behavior of the change. Tests
21 should fail without the change.
22 - Add or update relevant docs, in the docs folder and in code.
23 - Add an entry in CHANGES.rst summarizing the change and linking to the issue.
24 - Add `.. versionchanged::` entries in any relevant code docs.
25 -->

.github/ISSUE_TEMPLATE/feature-request.md
1 ---
2 name: Feature request
3 about: Suggest a new feature for Flask
4 ---
5
12

6 <!--
7 Replace this comment with a description of what the feature should do.
8 Include details such as links to relevant specs or previous discussions.
9 -->
10
11 <!--
12 Replace this comment with an example of the problem which this feature
13 would resolve. Is this problem solvable without changes to Flask, such
14 as by subclassing or using an extension?
15 -->

.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
7 url: https://github.com/pallets/flask/discussions/
8 about: Ask questions about your own code on the Discussions tab.
9 - name: Questions on
10 url: https://discord.gg/pallets
11 about: Ask questions about your own code on our Discord chat.

.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 <!--
7 This issue tracker is a tool to address bugs in Flask itself. Please use
8 GitHub Discussions or the Pallets Discord for questions about your own code.
9
10 Replace this comment with a clear outline of what the bug is.
11 -->
12
13 <!--
14 Describe how to replicate the bug.
15
16 Include a minimal reproducible example that demonstrates the bug.
17 Include the full traceback if there was an exception.
18 -->
19
20 <!--
21 Describe the expected behavior that should have happened but didn't.
22 -->
23
24 Environment:
25
26 - Python version:
27 - Flask version:

.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:
13

10 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2


11 - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
12 with:
13 python-version: 3.x
14 - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
15 - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
16 if: ${{ !cancelled() }}

.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 concurrency:
14 group: lock
15 jobs:
16 lock:
17 runs-on: ubuntu-latest
18 steps:
19 - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
20 with:
21 issue-inactive-days: 14
22 pr-inactive-days: 14
23 discussion-inactive-days: 14

.github/workflows/tests.yaml
1 name: Tests
2 on:
3 push:
4 branches: [main, stable]
5 paths-ignore: ['docs/**', '*.md', '*.rst']
6 pull_request:
7 paths-ignore: [ 'docs/**', '*.md', '*.rst' ]
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 - {python: '3.12'}
18 - {name: Windows, python: '3.12', os: windows-latest}
19 - {name: Mac, python: '3.12', os: macos-latest}
20 - {python: '3.11'}
21 - {python: '3.10'}
22 - {python: '3.9'}
23 - {name: PyPy, python: 'pypy-3.10', tox: pypy310}
24 - {name: Minimum Versions, python: '3.12', tox: py-min}
25 - {name: Development Versions, python: '3.9', tox: py-dev}
26 steps:
27 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
28 - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
29 with:
30 python-version: ${{ matrix.python }}
31 allow-prereleases: true
32 cache: pip
14

33 cache-dependency-path: requirements*/*.txt
34 - run: pip install tox
35 - run: tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }}
36 typing:
37 runs-on: ubuntu-latest
38 steps:
39 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
40 - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
41 with:
42 python-version: '3.x'
43 cache: pip
44 cache-dependency-path: requirements*/*.txt
45 - name: cache mypy
46 uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
47 with:
48 path: ./.mypy_cache
49 key: mypy|${{ hashFiles('pyproject.toml') }}
50 - run: pip install tox
51 - run: tox run -e typing

.github/workflows/publish.yaml
1 name: Publish
2 on:
3 push:
4 tags:
5 - '*'
6 jobs:
7 build:
8 runs-on: ubuntu-latest
9 outputs:
10 hash: ${{ steps.hash.outputs.hash }}
11 steps:
12 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
13 - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
14 with:
15 python-version: '3.x'
16 cache: pip
17 cache-dependency-path: requirements*/*.txt
18 - run: pip install -r requirements/build.txt
19 # Use the commit date instead of the current date during the build.
20 - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
21 - run: python -m build
22 # Generate hashes used for provenance.
23 - name: generate hash
24 id: hash
25 run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT
26 - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
27 with:
28 path: ./dist
29 provenance:
30 needs: [build]
31 permissions:
32 actions: read
33 id-token: write
34 contents: write
35 # Can't pin with hash due to how this workflow works.
36 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
37 with:
38 base64-subjects: ${{ needs.build.outputs.hash }}
39 create-release:
40 # Upload the sdist, wheels, and provenance to a GitHub release. They remain
41 # available as build artifacts for a while as well.
42 needs: [provenance]
43 runs-on: ubuntu-latest
44 permissions:
45 contents: write
46 steps:
47 - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
48 - name: create release
15

49 run: >
50 gh release create --draft --repo ${{ github.repository }}
51 ${{ github.ref_name }}
52 *.intoto.jsonl/* artifact/*
53 env:
54 GH_TOKEN: ${{ github.token }}
55 publish-pypi:
56 needs: [provenance]
57 # Wait for approval before attempting to upload to PyPI. This allows reviewing the
58 # files in the draft release.
59 environment:
60 name: publish
61 url: https://pypi.org/project/Flask/${{ github.ref_name }}
62 runs-on: ubuntu-latest
63 permissions:
64 id-token: write
65 steps:
66 - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
67 - uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2
68 with:
69 packages-dir: artifact/

CONTRIBUTING.rst
1 How to contribute to Flask
2 ==========================
3
4 Thank you for considering contributing to Flask!
5
6
7 Support questions
8 -----------------
9
10 Please don't use the issue tracker for this. The issue tracker is a tool
11 to address bugs and feature requests in Flask itself. Use one of the
12 following resources for questions about using Flask or issues with your
13 own code:
14
15 - The ``#questions`` channel on our Discord chat:
16 https://discord.gg/pallets
17 - Ask on `Stack Overflow`_. Search with Google first using:
18 ``site:stackoverflow.com flask {search term, exception message, etc.}``
19 - Ask on our `GitHub Discussions`_ for long term discussion or larger
20 questions.
21
22 .. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?tab=Frequent
23 .. _GitHub Discussions: https://github.com/pallets/flask/discussions
24
25
26 Reporting issues
27 ----------------
28
29 Include the following information in your post:
30
31 - Describe what you expected to happen.
32 - If possible, include a `minimal reproducible example`_ to help us
33 identify the issue. This also helps check that the issue is not with
34 your own code.
35 - Describe what actually happened. Include the full traceback if there
36 was an exception.
37 - List your Python and Flask versions. If possible, check if this
38 issue is already fixed in the latest releases or the latest code in
39 the repository.
40
41 .. _minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example
42
43
44 Submitting patches
45 ------------------
46
CONTRIBUTING.rst 16

47 If there is not an open issue for what you want to submit, prefer
48 opening one for discussion before working on a PR. You can work on any
49 issue that doesn't have an open PR linked to it or a maintainer assigned
50 to it. These show up in the sidebar. No need to ask if you can work on
51 an issue that interests you.
52
53 Include the following in your patch:
54
55 - Use `Black`_ to format your code. This and other tools will run
56 automatically if you install `pre-commit`_ using the instructions
57 below.
58 - Include tests if your patch adds or changes code. Make sure the test
59 fails without your patch.
60 - Update any relevant docs pages and docstrings. Docs pages and
61 docstrings should be wrapped at 72 characters.
62 - Add an entry in ``CHANGES.rst``. Use the same style as other
63 entries. Also include ``.. versionchanged::`` inline changelogs in
64 relevant docstrings.
65
66 .. _Black: https://black.readthedocs.io
67 .. _pre-commit: https://pre-commit.com
68
69
70 First time setup using GitHub Codespaces
71 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
72
73 `GitHub Codespaces`_ creates a development environment that is already set up for the
74 project. By default it opens in Visual Studio Code for the Web, but this can
75 be changed in your GitHub profile settings to use Visual Studio Code or JetBrains
76 PyCharm on your local computer.
77
78 - Make sure you have a `GitHub account`_.
79 - From the project's repository page, click the green "Code" button and then "Create
80 codespace on main".
81 - The codespace will be set up, then Visual Studio Code will open. However, you'll
82 need to wait a bit longer for the Python extension to be installed. You'll know it's
83 ready when the terminal at the bottom shows that the virtualenv was activated.
84 - Check out a branch and `start coding`_.
85
86 .. _GitHub Codespaces: https://docs.github.com/en/codespaces
87 .. _devcontainer:
,→ https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-t
88
89 First time setup in your local environment
90 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
91
92 - Make sure you have a `GitHub account`_.
93 - Download and install the `latest version of git`_.
94 - Configure git with your `username`_ and `email`_.
95
96 .. code-block:: text
97
98 $ git config --global user.name 'your name'
99 $ git config --global user.email 'your email'
100
101 - Fork Flask to your GitHub account by clicking the `Fork`_ button.
102 - `Clone`_ your fork locally, replacing ``your-username`` in the command below with
103 your actual username.
104
105 .. code-block:: text
106
107 $ git clone https://github.com/your-username/flask
108 $ cd flask
109
110 - Create a virtualenv. Use the latest version of Python.
111
112 - Linux/macOS
113
114 .. code-block:: text
115
116 $ python3 -m venv .venv
CONTRIBUTING.rst 17

117 $ . .venv/bin/activate
118
119 - Windows
120
121 .. code-block:: text
122
123 > py -3 -m venv .venv
124 > .venv\Scripts\activate
125
126 - Install the development dependencies, then install Flask in editable mode.
127
128 .. code-block:: text
129
130 $ python -m pip install -U pip
131 $ pip install -r requirements/dev.txt && pip install -e .
132
133 - Install the pre-commit hooks.
134
135 .. code-block:: text
136
137 $ pre-commit install --install-hooks
138
139 .. _GitHub account: https://github.com/join
140 .. _latest version of git: https://git-scm.com/downloads
141 .. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git
142 .. _email:
,→ https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address
143 .. _Fork: https://github.com/pallets/flask/fork
144 .. _Clone:
,→ https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork
145
146 .. _start coding:
147
148 Start coding
149 ~~~~~~~~~~~~
150
151 - Create a branch to identify the issue you would like to work on. If you're
152 submitting a bug or documentation fix, branch off of the latest ".x" branch.
153
154 .. code-block:: text
155
156 $ git fetch origin
157 $ git checkout -b your-branch-name origin/2.0.x
158
159 If you're submitting a feature addition or change, branch off of the "main" branch.
160
161 .. code-block:: text
162
163 $ git fetch origin
164 $ git checkout -b your-branch-name origin/main
165
166 - Using your favorite editor, make your changes, `committing as you go`_.
167
168 - If you are in a codespace, you will be prompted to `create a fork`_ the first
169 time you make a commit. Enter ``Y`` to continue.
170
171 - Include tests that cover any code changes you make. Make sure the test fails without
172 your patch. Run the tests as described below.
173 - Push your commits to your fork on GitHub and `create a pull request`_. Link to the
174 issue being addressed with ``fixes #123`` in the pull request description.
175
176 .. code-block:: text
177
178 $ git push --set-upstream origin your-branch-name
179
180 .. _committing as you go:
,→ https://afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes
181 .. _create a fork:
,→ https://docs.github.com/en/codespaces/developing-in-codespaces/using-source-control-in-your-codespace#about-automatic-forking
182 .. _create a pull request:
,→ https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
18

183
184 .. _Running the tests:
185
186 Running the tests
187 ~~~~~~~~~~~~~~~~~
188
189 Run the basic test suite with pytest.
190
191 .. code-block:: text
192
193 $ pytest
194
195 This runs the tests for the current environment, which is usually
196 sufficient. CI will run the full suite when you submit your pull
197 request. You can run the full test suite with tox if you don't want to
198 wait.
199
200 .. code-block:: text
201
202 $ tox
203
204
205 Running test coverage
206 ~~~~~~~~~~~~~~~~~~~~~
207
208 Generating a report of lines that do not have test coverage can indicate
209 where to start contributing. Run ``pytest`` using ``coverage`` and
210 generate a report.
211
212 If you are using GitHub Codespaces, ``coverage`` is already installed
213 so you can skip the installation command.
214
215 .. code-block:: text
216
217 $ pip install coverage
218 $ coverage run -m pytest
219 $ coverage html
220
221 Open ``htmlcov/index.html`` in your browser to explore the report.
222
223 Read more about `coverage <https://coverage.readthedocs.io>`__.
224
225
226 Building the docs
227 ~~~~~~~~~~~~~~~~~
228
229 Build the docs in the ``docs`` directory using Sphinx.
230
231 .. code-block:: text
232
233 $ cd docs
234 $ make html
235
236 Open ``_build/html/index.html`` in your browser to view the docs.
237
238 Read more about `Sphinx <https://www.sphinx-doc.org/en/stable/>`__.

requirements/build.in
1 build

requirements/dev.in
1 -r docs.txt
2 -r tests.txt
3 -r typing.txt
4 pre-commit
5 tox
19

requirements/tests.in
1 pytest
2 asgiref
3 greenlet ; python_version < "3.11"
4 python-dotenv

requirements/docs.in
1 pallets-sphinx-themes
2 sphinx
3 sphinxcontrib-log-cabinet
4 sphinx-tabs

requirements/typing.in
1 mypy
2 pyright
3 pytest
4 types-contextvars
5 types-dataclasses
6 asgiref
7 cryptography
8 python-dotenv

requirements/tests-min.in
1 werkzeug==3.1.0
2 jinja2==3.1.2
3 markupsafe==2.1.1
4 itsdangerous==2.2.0
5 click==8.1.3
6 blinker==1.9.0

requirements/build.txt
1 #
2 # This file is autogenerated by pip-compile with Python 3.13
3 # by the following command:
4 #
5 # pip-compile build.in
6 #
7 build==1.2.2.post1
8 # via -r build.in
9 packaging==24.2
10 # via build
11 pyproject-hooks==1.2.0
12 # via build

requirements/tests.txt
1 #
2 # This file is autogenerated by pip-compile with Python 3.13
3 # by the following command:
4 #
5 # pip-compile tests.in
6 #
7 asgiref==3.8.1
8 # via -r tests.in
9 iniconfig==2.0.0
10 # via pytest
11 packaging==24.2
20

12 # via pytest
13 pluggy==1.5.0
14 # via pytest
15 pytest==8.3.3
16 # via -r tests.in
17 python-dotenv==1.0.1
18 # via -r tests.in

requirements/tests-dev.txt
1 https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz
2 https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz
3 https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz
4 https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz
5 https://github.com/pallets/click/archive/refs/heads/main.tar.gz
6 https://github.com/pallets-eco/blinker/archive/refs/heads/main.tar.gz

requirements/tests-min.txt
1 #
2 # This file is autogenerated by pip-compile with Python 3.13
3 # by the following command:
4 #
5 # pip-compile tests-min.in
6 #
7 blinker==1.9.0
8 # via -r tests-min.in
9 click==8.1.3
10 # via -r tests-min.in
11 itsdangerous==2.2.0
12 # via -r tests-min.in
13 jinja2==3.1.2
14 # via -r tests-min.in
15 markupsafe==2.1.1
16 # via
17 # -r tests-min.in
18 # jinja2
19 # werkzeug
20 werkzeug==3.1.0
21 # via -r tests-min.in

requirements/typing.txt
1 #
2 # This file is autogenerated by pip-compile with Python 3.13
3 # by the following command:
4 #
5 # pip-compile typing.in
6 #
7 asgiref==3.8.1
8 # via -r typing.in
9 cffi==1.17.1
10 # via cryptography
11 cryptography==43.0.3
12 # via -r typing.in
13 iniconfig==2.0.0
14 # via pytest
15 mypy==1.13.0
16 # via -r typing.in
17 mypy-extensions==1.0.0
18 # via mypy
19 nodeenv==1.9.1
20 # via pyright
21 packaging==24.2
22 # via pytest
23 pluggy==1.5.0
21

24 # via pytest
25 pycparser==2.22
26 # via cffi
27 pyright==1.1.389
28 # via -r typing.in
29 pytest==8.3.3
30 # via -r typing.in
31 python-dotenv==1.0.1
32 # via -r typing.in
33 types-contextvars==2.4.7.3
34 # via -r typing.in
35 types-dataclasses==0.6.6
36 # via -r typing.in
37 typing-extensions==4.12.2
38 # via
39 # mypy
40 # pyright

requirements/docs.txt
1 #
2 # This file is autogenerated by pip-compile with Python 3.13
3 # by the following command:
4 #
5 # pip-compile docs.in
6 #
7 alabaster==1.0.0
8 # via sphinx
9 babel==2.16.0
10 # via sphinx
11 certifi==2024.8.30
12 # via requests
13 charset-normalizer==3.4.0
14 # via requests
15 docutils==0.21.2
16 # via
17 # sphinx
18 # sphinx-tabs
19 idna==3.10
20 # via requests
21 imagesize==1.4.1
22 # via sphinx
23 jinja2==3.1.4
24 # via sphinx
25 markupsafe==3.0.2
26 # via jinja2
27 packaging==24.2
28 # via
29 # pallets-sphinx-themes
30 # sphinx
31 pallets-sphinx-themes==2.3.0
32 # via -r docs.in
33 pygments==2.18.0
34 # via
35 # sphinx
36 # sphinx-tabs
37 requests==2.32.3
38 # via sphinx
39 snowballstemmer==2.2.0
40 # via sphinx
41 sphinx==8.1.3
42 # via
43 # -r docs.in
44 # pallets-sphinx-themes
45 # sphinx-notfound-page
46 # sphinx-tabs
47 # sphinxcontrib-log-cabinet
48 sphinx-notfound-page==1.0.4
49 # via pallets-sphinx-themes
50 sphinx-tabs==3.4.7
22

51 # via -r docs.in
52 sphinxcontrib-applehelp==2.0.0
53 # via sphinx
54 sphinxcontrib-devhelp==2.0.0
55 # via sphinx
56 sphinxcontrib-htmlhelp==2.1.0
57 # via sphinx
58 sphinxcontrib-jsmath==1.0.1
59 # via sphinx
60 sphinxcontrib-log-cabinet==1.0.1
61 # via -r docs.in
62 sphinxcontrib-qthelp==2.0.0
63 # via sphinx
64 sphinxcontrib-serializinghtml==2.0.0
65 # via sphinx
66 urllib3==2.2.3
67 # via requests

requirements/dev.txt
1 #
2 # This file is autogenerated by pip-compile with Python 3.13
3 # by the following command:
4 #
5 # pip-compile dev.in
6 #
7 alabaster==1.0.0
8 # via
9 # -r /Users/david/Projects/flask/requirements/docs.txt
10 # sphinx
11 asgiref==3.8.1
12 # via
13 # -r /Users/david/Projects/flask/requirements/tests.txt
14 # -r /Users/david/Projects/flask/requirements/typing.txt
15 babel==2.16.0
16 # via
17 # -r /Users/david/Projects/flask/requirements/docs.txt
18 # sphinx
19 cachetools==5.5.0
20 # via tox
21 certifi==2024.8.30
22 # via
23 # -r /Users/david/Projects/flask/requirements/docs.txt
24 # requests
25 cffi==1.17.1
26 # via
27 # -r /Users/david/Projects/flask/requirements/typing.txt
28 # cryptography
29 cfgv==3.4.0
30 # via pre-commit
31 chardet==5.2.0
32 # via tox
33 charset-normalizer==3.4.0
34 # via
35 # -r /Users/david/Projects/flask/requirements/docs.txt
36 # requests
37 colorama==0.4.6
38 # via tox
39 cryptography==43.0.3
40 # via -r /Users/david/Projects/flask/requirements/typing.txt
41 distlib==0.3.9
42 # via virtualenv
43 docutils==0.21.2
44 # via
45 # -r /Users/david/Projects/flask/requirements/docs.txt
46 # sphinx
47 # sphinx-tabs
48 filelock==3.16.1
49 # via
50 # tox
requirements/dev.txt 23

51 # virtualenv
52 identify==2.6.2
53 # via pre-commit
54 idna==3.10
55 # via
56 # -r /Users/david/Projects/flask/requirements/docs.txt
57 # requests
58 imagesize==1.4.1
59 # via
60 # -r /Users/david/Projects/flask/requirements/docs.txt
61 # sphinx
62 iniconfig==2.0.0
63 # via
64 # -r /Users/david/Projects/flask/requirements/tests.txt
65 # -r /Users/david/Projects/flask/requirements/typing.txt
66 # pytest
67 jinja2==3.1.4
68 # via
69 # -r /Users/david/Projects/flask/requirements/docs.txt
70 # sphinx
71 markupsafe==3.0.2
72 # via
73 # -r /Users/david/Projects/flask/requirements/docs.txt
74 # jinja2
75 mypy==1.13.0
76 # via -r /Users/david/Projects/flask/requirements/typing.txt
77 mypy-extensions==1.0.0
78 # via
79 # -r /Users/david/Projects/flask/requirements/typing.txt
80 # mypy
81 nodeenv==1.9.1
82 # via
83 # -r /Users/david/Projects/flask/requirements/typing.txt
84 # pre-commit
85 # pyright
86 packaging==24.2
87 # via
88 # -r /Users/david/Projects/flask/requirements/docs.txt
89 # -r /Users/david/Projects/flask/requirements/tests.txt
90 # -r /Users/david/Projects/flask/requirements/typing.txt
91 # pallets-sphinx-themes
92 # pyproject-api
93 # pytest
94 # sphinx
95 # tox
96 pallets-sphinx-themes==2.3.0
97 # via -r /Users/david/Projects/flask/requirements/docs.txt
98 platformdirs==4.3.6
99 # via
100 # tox
101 # virtualenv
102 pluggy==1.5.0
103 # via
104 # -r /Users/david/Projects/flask/requirements/tests.txt
105 # -r /Users/david/Projects/flask/requirements/typing.txt
106 # pytest
107 # tox
108 pre-commit==4.0.1
109 # via -r dev.in
110 pycparser==2.22
111 # via
112 # -r /Users/david/Projects/flask/requirements/typing.txt
113 # cffi
114 pygments==2.18.0
115 # via
116 # -r /Users/david/Projects/flask/requirements/docs.txt
117 # sphinx
118 # sphinx-tabs
119 pyproject-api==1.8.0
120 # via tox
121 pyright==1.1.389
requirements/dev.txt 24

122 # via -r /Users/david/Projects/flask/requirements/typing.txt


123 pytest==8.3.3
124 # via
125 # -r /Users/david/Projects/flask/requirements/tests.txt
126 # -r /Users/david/Projects/flask/requirements/typing.txt
127 python-dotenv==1.0.1
128 # via
129 # -r /Users/david/Projects/flask/requirements/tests.txt
130 # -r /Users/david/Projects/flask/requirements/typing.txt
131 pyyaml==6.0.2
132 # via pre-commit
133 requests==2.32.3
134 # via
135 # -r /Users/david/Projects/flask/requirements/docs.txt
136 # sphinx
137 snowballstemmer==2.2.0
138 # via
139 # -r /Users/david/Projects/flask/requirements/docs.txt
140 # sphinx
141 sphinx==8.1.3
142 # via
143 # -r /Users/david/Projects/flask/requirements/docs.txt
144 # pallets-sphinx-themes
145 # sphinx-notfound-page
146 # sphinx-tabs
147 # sphinxcontrib-log-cabinet
148 sphinx-notfound-page==1.0.4
149 # via
150 # -r /Users/david/Projects/flask/requirements/docs.txt
151 # pallets-sphinx-themes
152 sphinx-tabs==3.4.7
153 # via -r /Users/david/Projects/flask/requirements/docs.txt
154 sphinxcontrib-applehelp==2.0.0
155 # via
156 # -r /Users/david/Projects/flask/requirements/docs.txt
157 # sphinx
158 sphinxcontrib-devhelp==2.0.0
159 # via
160 # -r /Users/david/Projects/flask/requirements/docs.txt
161 # sphinx
162 sphinxcontrib-htmlhelp==2.1.0
163 # via
164 # -r /Users/david/Projects/flask/requirements/docs.txt
165 # sphinx
166 sphinxcontrib-jsmath==1.0.1
167 # via
168 # -r /Users/david/Projects/flask/requirements/docs.txt
169 # sphinx
170 sphinxcontrib-log-cabinet==1.0.1
171 # via -r /Users/david/Projects/flask/requirements/docs.txt
172 sphinxcontrib-qthelp==2.0.0
173 # via
174 # -r /Users/david/Projects/flask/requirements/docs.txt
175 # sphinx
176 sphinxcontrib-serializinghtml==2.0.0
177 # via
178 # -r /Users/david/Projects/flask/requirements/docs.txt
179 # sphinx
180 tox==4.23.2
181 # via -r dev.in
182 types-contextvars==2.4.7.3
183 # via -r /Users/david/Projects/flask/requirements/typing.txt
184 types-dataclasses==0.6.6
185 # via -r /Users/david/Projects/flask/requirements/typing.txt
186 typing-extensions==4.12.2
187 # via
188 # -r /Users/david/Projects/flask/requirements/typing.txt
189 # mypy
190 # pyright
191 urllib3==2.2.3
192 # via
25

193 # -r /Users/david/Projects/flask/requirements/docs.txt
194 # requests
195 virtualenv==20.27.1
196 # via
197 # pre-commit
198 # tox

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"]

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"]

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.
26

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

examples/celery/src/task_app/tasks.py
1 import time
2
3 from celery import shared_task
4 from celery import Task
5
6
27

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}

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/<id>")
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}

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:
28

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

examples/celery/src/task_app/templates/index.html
1 <!doctype html>
2 <html>
3 <head>
4 <meta charset=UTF-8>
5 <title>Celery Example</title>
6 </head>
7 <body>
8 <h2>Celery Example</h2>
9 Execute background tasks with Celery. Submits tasks and shows results using JavaScript.
10
11 <hr>
12 <h4>Add</h4>
13 <p>Start a task to add two numbers, then poll for the result.
14 <form id=add method=post action="{{ url_for("tasks.add") }}">
15 <label>A <input type=number name=a value=4></label><br>
16 <label>B <input type=number name=b value=2></label><br>
17 <input type=submit>
18 </form>
19 <p>Result: <span id=add-result></span></p>
20
21 <hr>
22 <h4>Block</h4>
23 <p>Start a task that takes 5 seconds. However, the response will return immediately.
24 <form id=block method=post action="{{ url_for("tasks.block") }}">
25 <input type=submit>
26 </form>
27 <p id=block-result></p>
28
29 <hr>
30 <h4>Process</h4>
31 <p>Start a task that counts, waiting one second each time, showing progress.
32 <form id=process method=post action="{{ url_for("tasks.process") }}">
33 <label>Total <input type=number name=total value="10"></label><br>
34 <input type=submit>
35 </form>
examples/celery/src/task_app/templates/index.html 29

36 <p id=process-result></p>
37
38 <script>
39 const taskForm = (formName, doPoll, report) => {
40 document.forms[formName].addEventListener("submit", (event) => {
41 event.preventDefault()
42 fetch(event.target.action, {
43 method: "POST",
44 body: new FormData(event.target)
45 })
46 .then(response => response.json())
47 .then(data => {
48 report(null)
49
50 const poll = () => {
51 fetch(`/tasks/result/${data["result_id"]}`)
52 .then(response => response.json())
53 .then(data => {
54 report(data)
55
56 if (!data["ready"]) {
57 setTimeout(poll, 500)
58 } else if (!data["successful"]) {
59 console.error(formName, data)
60 }
61 })
62 }
63
64 if (doPoll) {
65 poll()
66 }
67 })
68 })
69 }
70
71 taskForm("add", true, data => {
72 const el = document.getElementById("add-result")
73
74 if (data === null) {
75 el.innerText = "submitted"
76 } else if (!data["ready"]) {
77 el.innerText = "waiting"
78 } else if (!data["successful"]) {
79 el.innerText = "error, check console"
80 } else {
81 el.innerText = data["value"]
82 }
83 })
84
85 taskForm("block", false, data => {
86 document.getElementById("block-result").innerText = (
87 "request finished, check celery log to see task finish in 5 seconds"
88 )
89 })
90
91 taskForm("process", true, data => {
92 const el = document.getElementById("process-result")
93
94 if (data === null) {
95 el.innerText = "submitted"
96 } else if (!data["ready"]) {
97 el.innerText = `${data["value"]["current"]} / ${data["value"]["total"]}`
98 } else if (!data["successful"]) {
99 el.innerText = "error, check console"
100 } else {
101 el.innerText = "� done"
102 }
103 console.log(data)
104 })
105
106 </script>
30

107 </body>
108 </html>

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 *~

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"]

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
31

9 yield app
10 app.testing = False
11
12
13 @pytest.fixture
14 def client(app):
15 return app.test_client()

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

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 .
32

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

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.

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

examples/javascript/js_example/views.py
1 from flask import jsonify
2 from flask import render_template
3 from flask import request
4
33

5 from . import app


6
7
8 @app.route("/", defaults={"js": "fetch"})
9 @app.route("/<any(xhr, jquery, fetch):js>")
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)

examples/javascript/js_example/templates/jquery.html
1 {% extends 'base.html' %}
2
3 {% block intro %}
4 <a href="https://jquery.com/">jQuery</a> 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 <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
11 <script>
12 function addSubmit(ev) {
13 ev.preventDefault();
14 $.ajax({
15 method: 'POST',
16 url: {{ url_for('add')|tojson }},
17 data: $(this).serialize()
18 }).done(addShow);
19 }
20
21 function addShow(data) {
22 $('#result').text(data.result);
23 }
24
25 $('#calc').on('submit', addSubmit);
26 </script>
27 {% endblock %}

examples/javascript/js_example/templates/fetch.html
1 {% extends 'base.html' %}
2
3 {% block intro %}
4 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch"><code>fetch</code></a>
5 is the <em>modern</em> plain JavaScript way to make requests. It's
6 supported in all modern browsers.
7 {% endblock %}
8
9 {% block script %}
10 <script>
11 function addSubmit(ev) {
12 ev.preventDefault();
13 fetch({{ url_for('add')|tojson }}, {
14 method: 'POST',
15 body: new FormData(this)
16 })
17 .then(parseJSON)
18 .then(addShow);
19 }
20
21 function parseJSON(response) {
34

22 return response.json();
23 }
24
25 function addShow(data) {
26 var span = document.getElementById('result');
27 span.innerText = data.result;
28 }
29
30 var form = document.getElementById('calc');
31 form.addEventListener('submit', addSubmit);
32 </script>
33 {% endblock %}

examples/javascript/js_example/templates/xhr.html
1 {% extends 'base.html' %}
2
3 {% block intro %}
4 <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest"><code>XMLHttpRequest</code></a>
5 is the original JavaScript way to make requests. It's natively supported
6 by all browsers, but has been superseded by
7 <a href="{{ url_for("index", js="fetch") }}"><code>fetch</code></a>.
8 {% endblock %}
9
10 {% block script %}
11 <script>
12 function addSubmit(ev) {
13 ev.preventDefault();
14 var request = new XMLHttpRequest();
15 request.addEventListener('load', addShow);
16 request.open('POST', {{ url_for('add')|tojson }});
17 request.send(new FormData(this));
18 }
19
20 function addShow() {
21 var data = JSON.parse(this.responseText);
22 var span = document.getElementById('result');
23 span.innerText = data.result;
24 }
25
26 var form = document.getElementById('calc');
27 form.addEventListener('submit', addSubmit);
28 </script>
29 {% endblock %}

examples/javascript/js_example/templates/base.html
1 <!doctype html>
2 <title>JavaScript Example</title>
3 <link rel="stylesheet" href="https://unpkg.com/normalize.css@8.0.1/normalize.css">
4 <link rel="stylesheet" href="https://unpkg.com/sakura.css@1.3.1/css/sakura.css">
5 <style>
6 ul { margin: 0; padding: 0; display: flex; list-style-type: none; }
7 li > * { padding: 1em; }
8 li.active > a { color: #5e5e5e; border-bottom: 2px solid #4a4a4a; }
9 form { display: flex; }
10 label > input { width: 3em; }
11 form > * { padding-right: 1em; }
12 #result { font-weight: bold; }
13 </style>
14 <ul>
15 <li><span>Type:</span>
16 <li class="{% if js == 'fetch' %}active{% endif %}">
17 <a href="{{ url_for('index', js='fetch') }}">Fetch</a>
18 <li class="{% if js == 'xhr' %}active{% endif %}">
19 <a href="{{ url_for('index', js='xhr') }}">XHR</a>
20 <li class="{% if js == 'jquery' %}active{% endif %}">
21 <a href="{{ url_for('index', js='jquery') }}">jQuery</a>
35

22 </ul>
23 <hr>
24 <p>{% block intro %}{% endblock %}</p>
25 <hr>
26 <form id="calc">
27 <label>a <input name="a"></label>
28 <span>+</span>
29 <label>b <input name="b"></label>
30 <input type="submit" value="Calculate">
31 </form>
32 <span>= <span id="result"></span></span>
33 {% block script %}{% endblock %}

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 *~

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
36

37 source = ["flaskr", "tests"]


38
39 [tool.ruff]
40 src = ["src"]

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
37

64 Run with coverage report::


65
66 $ coverage run -m pytest
67 $ coverage report
68 $ coverage html # open htmlcov/index.html in a browser

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.

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!"

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');
38

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

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
39

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)

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
40

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

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
41

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

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 );
42

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)

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)
43

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

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;
examples/tutorial/flaskr/static/style.css 44

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;
45

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 }

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
examples/tutorial/flaskr/auth.py 46

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."
47

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"))

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
examples/tutorial/flaskr/blog.py 48

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("/<int:id>/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("/<int:id>/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)
49

122 db = get_db()
123 db.execute("DELETE FROM post WHERE id = ?", (id,))
124 db.commit()
125 return redirect(url_for("blog.index"))

examples/tutorial/flaskr/templates/base.html
1 <!doctype html>
2 <title>{% block title %}{% endblock %} - Flaskr</title>
3 <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
4 <nav>
5 <h1><a href="{{ url_for('index') }}">Flaskr</a></h1>
6 <ul>
7 {% if g.user %}
8 <li><span>{{ g.user['username'] }}</span>
9 <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
10 {% else %}
11 <li><a href="{{ url_for('auth.register') }}">Register</a>
12 <li><a href="{{ url_for('auth.login') }}">Log In</a>
13 {% endif %}
14 </ul>
15 </nav>
16 <section class="content">
17 <header>
18 {% block header %}{% endblock %}
19 </header>
20 {% for message in get_flashed_messages() %}
21 <div class="flash">{{ message }}</div>
22 {% endfor %}
23 {% block content %}{% endblock %}
24 </section>

examples/tutorial/flaskr/templates/auth/login.html
1 {% extends 'base.html' %}
2
3 {% block header %}
4 <h1>{% block title %}Log In{% endblock %}</h1>
5 {% endblock %}
6
7 {% block content %}
8 <form method="post">
9 <label for="username">Username</label>
10 <input name="username" id="username" required>
11 <label for="password">Password</label>
12 <input type="password" name="password" id="password" required>
13 <input type="submit" value="Log In">
14 </form>
15 {% endblock %}

examples/tutorial/flaskr/templates/auth/register.html
1 {% extends 'base.html' %}
2
3 {% block header %}
4 <h1>{% block title %}Register{% endblock %}</h1>
5 {% endblock %}
6
7 {% block content %}
8 <form method="post">
9 <label for="username">Username</label>
10 <input name="username" id="username" required>
11 <label for="password">Password</label>
12 <input type="password" name="password" id="password" required>
13 <input type="submit" value="Register">
50

14 </form>
15 {% endblock %}

examples/tutorial/flaskr/templates/blog/create.html
1 {% extends 'base.html' %}
2
3 {% block header %}
4 <h1>{% block title %}New Post{% endblock %}</h1>
5 {% endblock %}
6
7 {% block content %}
8 <form method="post">
9 <label for="title">Title</label>
10 <input name="title" id="title" value="{{ request.form['title'] }}" required>
11 <label for="body">Body</label>
12 <textarea name="body" id="body">{{ request.form['body'] }}</textarea>
13 <input type="submit" value="Save">
14 </form>
15 {% endblock %}

examples/tutorial/flaskr/templates/blog/update.html
1 {% extends 'base.html' %}
2
3 {% block header %}
4 <h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
5 {% endblock %}
6
7 {% block content %}
8 <form method="post">
9 <label for="title">Title</label>
10 <input name="title" id="title" value="{{ request.form['title'] or post['title'] }}" required>
11 <label for="body">Body</label>
12 <textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
13 <input type="submit" value="Save">
14 </form>
15 <hr>
16 <form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
17 <input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
18 </form>
19 {% endblock %}

examples/tutorial/flaskr/templates/blog/index.html
1 {% extends 'base.html' %}
2
3 {% block header %}
4 <h1>{% block title %}Posts{% endblock %}</h1>
5 {% if g.user %}
6 <a class="action" href="{{ url_for('blog.create') }}">New</a>
7 {% endif %}
8 {% endblock %}
9
10 {% block content %}
11 {% for post in posts %}
12 <article class="post">
13 <header>
14 <div>
15 <h1>{{ post['title'] }}</h1>
16 <div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
17 </div>
18 {% if g.user['id'] == post['author_id'] %}
19 <a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
20 {% endif %}
21 </header>
51

22 <p class="body">{{ post['body'] }}</p>


23 </article>
24 {% if not loop.last %}
25 <hr>
26 {% endif %}
27 {% endfor %}
28 {% endblock %}

CHANGES.rst
1 Version 3.1.0
2 -------------
3
4 Released 2024-11-13
5
6 - Drop support for Python 3.8. :pr:`5623`
7 - Update minimum dependency versions to latest feature releases.
8 Werkzeug >= 3.1, ItsDangerous >= 2.2, Blinker >= 1.9. :pr:`5624,5633`
9 - Provide a configuration option to control automatic option
10 responses. :pr:`5496`
11 - ``Flask.open_resource``/``open_instance_resource`` and
12 ``Blueprint.open_resource`` take an ``encoding`` parameter to use when
13 opening in text mode. It defaults to ``utf-8``. :issue:`5504`
14 - ``Request.max_content_length`` can be customized per-request instead of only
15 through the ``MAX_CONTENT_LENGTH`` config. Added
16 ``MAX_FORM_MEMORY_SIZE`` and ``MAX_FORM_PARTS`` config. Added documentation
17 about resource limits to the security page. :issue:`5625`
18 - Add support for the ``Partitioned`` cookie attribute (CHIPS), with the
19 ``SESSION_COOKIE_PARTITIONED`` config. :issue:`5472`
20 - ``-e path`` takes precedence over default ``.env`` and ``.flaskenv`` files.
21 ``load_dotenv`` loads default files in addition to a path unless
22 ``load_defaults=False`` is passed. :issue:`5628`
23 - Support key rotation with the ``SECRET_KEY_FALLBACKS`` config, a list of old
24 secret keys that can still be used for unsigning. Extensions will need to
25 add support. :issue:`5621`
26 - Fix how setting ``host_matching=True`` or ``subdomain_matching=False``
27 interacts with ``SERVER_NAME``. Setting ``SERVER_NAME`` no longer restricts
28 requests to only that domain. :issue:`5553`
29 - ``Request.trusted_hosts`` is checked during routing, and can be set through
30 the ``TRUSTED_HOSTS`` config. :issue:`5636`
31
32
33 Version 3.0.3
34 -------------
35
36 Released 2024-04-07
37
38 - The default ``hashlib.sha1`` may not be available in FIPS builds. Don't
39 access it at import time so the developer has time to change the default.
40 :issue:`5448`
41 - Don't initialize the ``cli`` attribute in the sansio scaffold, but rather in
42 the ``Flask`` concrete class. :pr:`5270`
43
44
45 Version 3.0.2
46 -------------
47
48 Released 2024-02-03
49
50 - Correct type for ``jinja_loader`` property. :issue:`5388`
51 - Fix error with ``--extra-files`` and ``--exclude-patterns`` CLI options.
52 :issue:`5391`
53
54
55 Version 3.0.1
56 -------------
57
58 Released 2024-01-18
59
60 - Correct type for ``path`` argument to ``send_file``. :issue:`5336`
CHANGES.rst 52

61 - Fix a typo in an error message for the ``flask run --key`` option. :pr:`5344`
62 - Session data is untagged without relying on the built-in ``json.loads``
63 ``object_hook``. This allows other JSON providers that don't implement that.
64 :issue:`5381`
65 - Address more type findings when using mypy strict mode. :pr:`5383`
66
67
68 Version 3.0.0
69 -------------
70
71 Released 2023-09-30
72
73 - Remove previously deprecated code. :pr:`5223`
74 - Deprecate the ``__version__`` attribute. Use feature detection, or
75 ``importlib.metadata.version("flask")``, instead. :issue:`5230`
76 - Restructure the code such that the Flask (app) and Blueprint
77 classes have Sans-IO bases. :pr:`5127`
78 - Allow self as an argument to url_for. :pr:`5264`
79 - Require Werkzeug >= 3.0.0.
80
81
82 Version 2.3.3
83 -------------
84
85 Released 2023-08-21
86
87 - Python 3.12 compatibility.
88 - Require Werkzeug >= 2.3.7.
89 - Use ``flit_core`` instead of ``setuptools`` as build backend.
90 - Refactor how an app's root and instance paths are determined. :issue:`5160`
91
92
93 Version 2.3.2
94 -------------
95
96 Released 2023-05-01
97
98 - Set ``Vary: Cookie`` header when the session is accessed, modified, or refreshed.
99 - Update Werkzeug requirement to >=2.3.3 to apply recent bug fixes.
100
101
102 Version 2.3.1
103 -------------
104
105 Released 2023-04-25
106
107 - Restore deprecated ``from flask import Markup``. :issue:`5084`
108
109
110 Version 2.3.0
111 -------------
112
113 Released 2023-04-25
114
115 - Drop support for Python 3.7. :pr:`5072`
116 - Update minimum requirements to the latest versions: Werkzeug>=2.3.0, Jinja2>3.1.2,
117 itsdangerous>=2.1.2, click>=8.1.3.
118 - Remove previously deprecated code. :pr:`4995`
119
120 - The ``push`` and ``pop`` methods of the deprecated ``_app_ctx_stack`` and
121 ``_request_ctx_stack`` objects are removed. ``top`` still exists to give
122 extensions more time to update, but it will be removed.
123 - The ``FLASK_ENV`` environment variable, ``ENV`` config key, and ``app.env``
124 property are removed.
125 - The ``session_cookie_name``, ``send_file_max_age_default``, ``use_x_sendfile``,
126 ``propagate_exceptions``, and ``templates_auto_reload`` properties on ``app``
127 are removed.
128 - The ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and
129 ``JSONIFY_PRETTYPRINT_REGULAR`` config keys are removed.
130 - The ``app.before_first_request`` and ``bp.before_app_first_request`` decorators
131 are removed.
CHANGES.rst 53

132 - ``json_encoder`` and ``json_decoder`` attributes on app and blueprint, and the
133 corresponding ``json.JSONEncoder`` and ``JSONDecoder`` classes, are removed.
134 - The ``json.htmlsafe_dumps`` and ``htmlsafe_dump`` functions are removed.
135 - Calling setup methods on blueprints after registration is an error instead of a
136 warning. :pr:`4997`
137
138 - Importing ``escape`` and ``Markup`` from ``flask`` is deprecated. Import them
139 directly from ``markupsafe`` instead. :pr:`4996`
140 - The ``app.got_first_request`` property is deprecated. :pr:`4997`
141 - The ``locked_cached_property`` decorator is deprecated. Use a lock inside the
142 decorated function if locking is needed. :issue:`4993`
143 - Signals are always available. ``blinker>=1.6.2`` is a required dependency. The
144 ``signals_available`` attribute is deprecated. :issue:`5056`
145 - Signals support ``async`` subscriber functions. :pr:`5049`
146 - Remove uses of locks that could cause requests to block each other very briefly.
147 :issue:`4993`
148 - Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``.
149 :pr:`4947`
150 - Ensure subdomains are applied with nested blueprints. :issue:`4834`
151 - ``config.from_file`` can use ``text=False`` to indicate that the parser wants a
152 binary file instead. :issue:`4989`
153 - If a blueprint is created with an empty name it raises a ``ValueError``.
154 :issue:`5010`
155 - ``SESSION_COOKIE_DOMAIN`` does not fall back to ``SERVER_NAME``. The default is not
156 to set the domain, which modern browsers interpret as an exact match rather than
157 a subdomain match. Warnings about ``localhost`` and IP addresses are also removed.
158 :issue:`5051`
159 - The ``routes`` command shows each rule's ``subdomain`` or ``host`` when domain
160 matching is in use. :issue:`5004`
161 - Use postponed evaluation of annotations. :pr:`5071`
162
163
164 Version 2.2.5
165 -------------
166
167 Released 2023-05-02
168
169 - Update for compatibility with Werkzeug 2.3.3.
170 - Set ``Vary: Cookie`` header when the session is accessed, modified, or refreshed.
171
172
173 Version 2.2.4
174 -------------
175
176 Released 2023-04-25
177
178 - Update for compatibility with Werkzeug 2.3.
179
180
181 Version 2.2.3
182 -------------
183
184 Released 2023-02-15
185
186 - Autoescape is enabled by default for ``.svg`` template files. :issue:`4831`
187 - Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892`
188 - Add ``--debug`` option to the ``flask run`` command. :issue:`4777`
189
190
191 Version 2.2.2
192 -------------
193
194 Released 2022-08-08
195
196 - Update Werkzeug dependency to >= 2.2.2. This includes fixes related
197 to the new faster router, header parsing, and the development
198 server. :pr:`4754`
199 - Fix the default value for ``app.env`` to be ``"production"``. This
200 attribute remains deprecated. :issue:`4740`
201
202
CHANGES.rst 54

203 Version 2.2.1


204 -------------
205
206 Released 2022-08-03
207
208 - Setting or accessing ``json_encoder`` or ``json_decoder`` raises a
209 deprecation warning. :issue:`4732`
210
211
212 Version 2.2.0
213 -------------
214
215 Released 2022-08-01
216
217 - Remove previously deprecated code. :pr:`4667`
218
219 - Old names for some ``send_file`` parameters have been removed.
220 ``download_name`` replaces ``attachment_filename``, ``max_age``
221 replaces ``cache_timeout``, and ``etag`` replaces ``add_etags``.
222 Additionally, ``path`` replaces ``filename`` in
223 ``send_from_directory``.
224 - The ``RequestContext.g`` property returning ``AppContext.g`` is
225 removed.
226
227 - Update Werkzeug dependency to >= 2.2.
228 - The app and request contexts are managed using Python context vars
229 directly rather than Werkzeug's ``LocalStack``. This should result
230 in better performance and memory use. :pr:`4682`
231
232 - Extension maintainers, be aware that ``_app_ctx_stack.top``
233 and ``_request_ctx_stack.top`` are deprecated. Store data on
234 ``g`` instead using a unique prefix, like
235 ``g._extension_name_attr``.
236
237 - The ``FLASK_ENV`` environment variable and ``app.env`` attribute are
238 deprecated, removing the distinction between development and debug
239 mode. Debug mode should be controlled directly using the ``--debug``
240 option or ``app.run(debug=True)``. :issue:`4714`
241 - Some attributes that proxied config keys on ``app`` are deprecated:
242 ``session_cookie_name``, ``send_file_max_age_default``,
243 ``use_x_sendfile``, ``propagate_exceptions``, and
244 ``templates_auto_reload``. Use the relevant config keys instead.
245 :issue:`4716`
246 - Add new customization points to the ``Flask`` app object for many
247 previously global behaviors.
248
249 - ``flask.url_for`` will call ``app.url_for``. :issue:`4568`
250 - ``flask.abort`` will call ``app.aborter``.
251 ``Flask.aborter_class`` and ``Flask.make_aborter`` can be used
252 to customize this aborter. :issue:`4567`
253 - ``flask.redirect`` will call ``app.redirect``. :issue:`4569`
254 - ``flask.json`` is an instance of ``JSONProvider``. A different
255 provider can be set to use a different JSON library.
256 ``flask.jsonify`` will call ``app.json.response``, other
257 functions in ``flask.json`` will call corresponding functions in
258 ``app.json``. :pr:`4692`
259
260 - JSON configuration is moved to attributes on the default
261 ``app.json`` provider. ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``,
262 ``JSONIFY_MIMETYPE``, and ``JSONIFY_PRETTYPRINT_REGULAR`` are
263 deprecated. :pr:`4692`
264 - Setting custom ``json_encoder`` and ``json_decoder`` classes on the
265 app or a blueprint, and the corresponding ``json.JSONEncoder`` and
266 ``JSONDecoder`` classes, are deprecated. JSON behavior can now be
267 overridden using the ``app.json`` provider interface. :pr:`4692`
268 - ``json.htmlsafe_dumps`` and ``json.htmlsafe_dump`` are deprecated,
269 the function is built-in to Jinja now. :pr:`4692`
270 - Refactor ``register_error_handler`` to consolidate error checking.
271 Rewrite some error messages to be more consistent. :issue:`4559`
272 - Use Blueprint decorators and functions intended for setup after
273 registering the blueprint will show a warning. In the next version,
CHANGES.rst 55

274 this will become an error just like the application setup methods.
275 :issue:`4571`
276 - ``before_first_request`` is deprecated. Run setup code when creating
277 the application instead. :issue:`4605`
278 - Added the ``View.init_every_request`` class attribute. If a view
279 subclass sets this to ``False``, the view will not create a new
280 instance on every request. :issue:`2520`.
281 - A ``flask.cli.FlaskGroup`` Click group can be nested as a
282 sub-command in a custom CLI. :issue:`3263`
283 - Add ``--app`` and ``--debug`` options to the ``flask`` CLI, instead
284 of requiring that they are set through environment variables.
285 :issue:`2836`
286 - Add ``--env-file`` option to the ``flask`` CLI. This allows
287 specifying a dotenv file to load in addition to ``.env`` and
288 ``.flaskenv``. :issue:`3108`
289 - It is no longer required to decorate custom CLI commands on
290 ``app.cli`` or ``blueprint.cli`` with ``@with_appcontext``, an app
291 context will already be active at that point. :issue:`2410`
292 - ``SessionInterface.get_expiration_time`` uses a timezone-aware
293 value. :pr:`4645`
294 - View functions can return generators directly instead of wrapping
295 them in a ``Response``. :pr:`4629`
296 - Add ``stream_template`` and ``stream_template_string`` functions to
297 render a template as a stream of pieces. :pr:`4629`
298 - A new implementation of context preservation during debugging and
299 testing. :pr:`4666`
300
301 - ``request``, ``g``, and other context-locals point to the
302 correct data when running code in the interactive debugger
303 console. :issue:`2836`
304 - Teardown functions are always run at the end of the request,
305 even if the context is preserved. They are also run after the
306 preserved context is popped.
307 - ``stream_with_context`` preserves context separately from a
308 ``with client`` block. It will be cleaned up when
309 ``response.get_data()`` or ``response.close()`` is called.
310
311 - Allow returning a list from a view function, to convert it to a
312 JSON response like a dict is. :issue:`4672`
313 - When type checking, allow ``TypedDict`` to be returned from view
314 functions. :pr:`4695`
315 - Remove the ``--eager-loading/--lazy-loading`` options from the
316 ``flask run`` command. The app is always eager loaded the first
317 time, then lazily loaded in the reloader. The reloader always prints
318 errors immediately but continues serving. Remove the internal
319 ``DispatchingApp`` middleware used by the previous implementation.
320 :issue:`4715`
321
322
323 Version 2.1.3
324 -------------
325
326 Released 2022-07-13
327
328 - Inline some optional imports that are only used for certain CLI
329 commands. :pr:`4606`
330 - Relax type annotation for ``after_request`` functions. :issue:`4600`
331 - ``instance_path`` for namespace packages uses the path closest to
332 the imported submodule. :issue:`4610`
333 - Clearer error message when ``render_template`` and
334 ``render_template_string`` are used outside an application context.
335 :pr:`4693`
336
337
338 Version 2.1.2
339 -------------
340
341 Released 2022-04-28
342
343 - Fix type annotation for ``json.loads``, it accepts str or bytes.
344 :issue:`4519`
CHANGES.rst 56

345 - The ``--cert`` and ``--key`` options on ``flask run`` can be given
346 in either order. :issue:`4459`
347
348
349 Version 2.1.1
350 -------------
351
352 Released on 2022-03-30
353
354 - Set the minimum required version of importlib_metadata to 3.6.0,
355 which is required on Python < 3.10. :issue:`4502`
356
357
358 Version 2.1.0
359 -------------
360
361 Released 2022-03-28
362
363 - Drop support for Python 3.6. :pr:`4335`
364 - Update Click dependency to >= 8.0. :pr:`4008`
365 - Remove previously deprecated code. :pr:`4337`
366
367 - The CLI does not pass ``script_info`` to app factory functions.
368 - ``config.from_json`` is replaced by
369 ``config.from_file(name, load=json.load)``.
370 - ``json`` functions no longer take an ``encoding`` parameter.
371 - ``safe_join`` is removed, use ``werkzeug.utils.safe_join``
372 instead.
373 - ``total_seconds`` is removed, use ``timedelta.total_seconds``
374 instead.
375 - The same blueprint cannot be registered with the same name. Use
376 ``name=`` when registering to specify a unique name.
377 - The test client's ``as_tuple`` parameter is removed. Use
378 ``response.request.environ`` instead. :pr:`4417`
379
380 - Some parameters in ``send_file`` and ``send_from_directory`` were
381 renamed in 2.0. The deprecation period for the old names is extended
382 to 2.2. Be sure to test with deprecation warnings visible.
383
384 - ``attachment_filename`` is renamed to ``download_name``.
385 - ``cache_timeout`` is renamed to ``max_age``.
386 - ``add_etags`` is renamed to ``etag``.
387 - ``filename`` is renamed to ``path``.
388
389 - The ``RequestContext.g`` property is deprecated. Use ``g`` directly
390 or ``AppContext.g`` instead. :issue:`3898`
391 - ``copy_current_request_context`` can decorate async functions.
392 :pr:`4303`
393 - The CLI uses ``importlib.metadata`` instead of ``pkg_resources`` to
394 load command entry points. :issue:`4419`
395 - Overriding ``FlaskClient.open`` will not cause an error on redirect.
396 :issue:`3396`
397 - Add an ``--exclude-patterns`` option to the ``flask run`` CLI
398 command to specify patterns that will be ignored by the reloader.
399 :issue:`4188`
400 - When using lazy loading (the default with the debugger), the Click
401 context from the ``flask run`` command remains available in the
402 loader thread. :issue:`4460`
403 - Deleting the session cookie uses the ``httponly`` flag.
404 :issue:`4485`
405 - Relax typing for ``errorhandler`` to allow the user to use more
406 precise types and decorate the same function multiple times.
407 :issue:`4095, 4295, 4297`
408 - Fix typing for ``__exit__`` methods for better compatibility with
409 ``ExitStack``. :issue:`4474`
410 - From Werkzeug, for redirect responses the ``Location`` header URL
411 will remain relative, and exclude the scheme and domain, by default.
412 :pr:`4496`
413 - Add ``Config.from_prefixed_env()`` to load config values from
414 environment variables that start with ``FLASK_`` or another prefix.
415 This parses values as JSON by default, and allows setting keys in
CHANGES.rst 57

416 nested dicts. :pr:`4479`


417
418
419 Version 2.0.3
420 -------------
421
422 Released 2022-02-14
423
424 - The test client's ``as_tuple`` parameter is deprecated and will be
425 removed in Werkzeug 2.1. It is now also deprecated in Flask, to be
426 removed in Flask 2.1, while remaining compatible with both in
427 2.0.x. Use ``response.request.environ`` instead. :pr:`4341`
428 - Fix type annotation for ``errorhandler`` decorator. :issue:`4295`
429 - Revert a change to the CLI that caused it to hide ``ImportError``
430 tracebacks when importing the application. :issue:`4307`
431 - ``app.json_encoder`` and ``json_decoder`` are only passed to
432 ``dumps`` and ``loads`` if they have custom behavior. This improves
433 performance, mainly on PyPy. :issue:`4349`
434 - Clearer error message when ``after_this_request`` is used outside a
435 request context. :issue:`4333`
436
437
438 Version 2.0.2
439 -------------
440
441 Released 2021-10-04
442
443 - Fix type annotation for ``teardown_*`` methods. :issue:`4093`
444 - Fix type annotation for ``before_request`` and ``before_app_request``
445 decorators. :issue:`4104`
446 - Fixed the issue where typing requires template global
447 decorators to accept functions with no arguments. :issue:`4098`
448 - Support View and MethodView instances with async handlers. :issue:`4112`
449 - Enhance typing of ``app.errorhandler`` decorator. :issue:`4095`
450 - Fix registering a blueprint twice with differing names. :issue:`4124`
451 - Fix the type of ``static_folder`` to accept ``pathlib.Path``.
452 :issue:`4150`
453 - ``jsonify`` handles ``decimal.Decimal`` by encoding to ``str``.
454 :issue:`4157`
455 - Correctly handle raising deferred errors in CLI lazy loading.
456 :issue:`4096`
457 - The CLI loader handles ``**kwargs`` in a ``create_app`` function.
458 :issue:`4170`
459 - Fix the order of ``before_request`` and other callbacks that trigger
460 before the view returns. They are called from the app down to the
461 closest nested blueprint. :issue:`4229`
462
463
464 Version 2.0.1
465 -------------
466
467 Released 2021-05-21
468
469 - Re-add the ``filename`` parameter in ``send_from_directory``. The
470 ``filename`` parameter has been renamed to ``path``, the old name
471 is deprecated. :pr:`4019`
472 - Mark top-level names as exported so type checking understands
473 imports in user projects. :issue:`4024`
474 - Fix type annotation for ``g`` and inform mypy that it is a namespace
475 object that has arbitrary attributes. :issue:`4020`
476 - Fix some types that weren't available in Python 3.6.0. :issue:`4040`
477 - Improve typing for ``send_file``, ``send_from_directory``, and
478 ``get_send_file_max_age``. :issue:`4044`, :pr:`4026`
479 - Show an error when a blueprint name contains a dot. The ``.`` has
480 special meaning, it is used to separate (nested) blueprint names and
481 the endpoint name. :issue:`4041`
482 - Combine URL prefixes when nesting blueprints that were created with
483 a ``url_prefix`` value. :issue:`4037`
484 - Revert a change to the order that URL matching was done. The
485 URL is again matched after the session is loaded, so the session is
486 available in custom URL converters. :issue:`4053`
CHANGES.rst 58

487 - Re-add deprecated ``Config.from_json``, which was accidentally


488 removed early. :issue:`4078`
489 - Improve typing for some functions using ``Callable`` in their type
490 signatures, focusing on decorator factories. :issue:`4060`
491 - Nested blueprints are registered with their dotted name. This allows
492 different blueprints with the same name to be nested at different
493 locations. :issue:`4069`
494 - ``register_blueprint`` takes a ``name`` option to change the
495 (pre-dotted) name the blueprint is registered with. This allows the
496 same blueprint to be registered multiple times with unique names for
497 ``url_for``. Registering the same blueprint with the same name
498 multiple times is deprecated. :issue:`1091`
499 - Improve typing for ``stream_with_context``. :issue:`4052`
500
501
502 Version 2.0.0
503 -------------
504
505 Released 2021-05-11
506
507 - Drop support for Python 2 and 3.5.
508 - Bump minimum versions of other Pallets projects: Werkzeug >= 2,
509 Jinja2 >= 3, MarkupSafe >= 2, ItsDangerous >= 2, Click >= 8. Be sure
510 to check the change logs for each project. For better compatibility
511 with other applications (e.g. Celery) that still require Click 7,
512 there is no hard dependency on Click 8 yet, but using Click 7 will
513 trigger a DeprecationWarning and Flask 2.1 will depend on Click 8.
514 - JSON support no longer uses simplejson. To use another JSON module,
515 override ``app.json_encoder`` and ``json_decoder``. :issue:`3555`
516 - The ``encoding`` option to JSON functions is deprecated. :pr:`3562`
517 - Passing ``script_info`` to app factory functions is deprecated. This
518 was not portable outside the ``flask`` command. Use
519 ``click.get_current_context().obj`` if it's needed. :issue:`3552`
520 - The CLI shows better error messages when the app failed to load
521 when looking up commands. :issue:`2741`
522 - Add ``SessionInterface.get_cookie_name`` to allow setting the
523 session cookie name dynamically. :pr:`3369`
524 - Add ``Config.from_file`` to load config using arbitrary file
525 loaders, such as ``toml.load`` or ``json.load``.
526 ``Config.from_json`` is deprecated in favor of this. :pr:`3398`
527 - The ``flask run`` command will only defer errors on reload. Errors
528 present during the initial call will cause the server to exit with
529 the traceback immediately. :issue:`3431`
530 - ``send_file`` raises a ``ValueError`` when passed an ``io`` object
531 in text mode. Previously, it would respond with 200 OK and an empty
532 file. :issue:`3358`
533 - When using ad-hoc certificates, check for the cryptography library
534 instead of PyOpenSSL. :pr:`3492`
535 - When specifying a factory function with ``FLASK_APP``, keyword
536 argument can be passed. :issue:`3553`
537 - When loading a ``.env`` or ``.flaskenv`` file, the current working
538 directory is no longer changed to the location of the file.
539 :pr:`3560`
540 - When returning a ``(response, headers)`` tuple from a view, the
541 headers replace rather than extend existing headers on the response.
542 For example, this allows setting the ``Content-Type`` for
543 ``jsonify()``. Use ``response.headers.extend()`` if extending is
544 desired. :issue:`3628`
545 - The ``Scaffold`` class provides a common API for the ``Flask`` and
546 ``Blueprint`` classes. ``Blueprint`` information is stored in
547 attributes just like ``Flask``, rather than opaque lambda functions.
548 This is intended to improve consistency and maintainability.
549 :issue:`3215`
550 - Include ``samesite`` and ``secure`` options when removing the
551 session cookie. :pr:`3726`
552 - Support passing a ``pathlib.Path`` to ``static_folder``. :pr:`3579`
553 - ``send_file`` and ``send_from_directory`` are wrappers around the
554 implementations in ``werkzeug.utils``. :pr:`3828`
555 - Some ``send_file`` parameters have been renamed, the old names are
556 deprecated. ``attachment_filename`` is renamed to ``download_name``.
557 ``cache_timeout`` is renamed to ``max_age``. ``add_etags`` is
CHANGES.rst 59

558 renamed to ``etag``. :pr:`3828, 3883`


559 - ``send_file`` passes ``download_name`` even if
560 ``as_attachment=False`` by using ``Content-Disposition: inline``.
561 :pr:`3828`
562 - ``send_file`` sets ``conditional=True`` and ``max_age=None`` by
563 default. ``Cache-Control`` is set to ``no-cache`` if ``max_age`` is
564 not set, otherwise ``public``. This tells browsers to validate
565 conditional requests instead of using a timed cache. :pr:`3828`
566 - ``helpers.safe_join`` is deprecated. Use
567 ``werkzeug.utils.safe_join`` instead. :pr:`3828`
568 - The request context does route matching before opening the session.
569 This could allow a session interface to change behavior based on
570 ``request.endpoint``. :issue:`3776`
571 - Use Jinja's implementation of the ``|tojson`` filter. :issue:`3881`
572 - Add route decorators for common HTTP methods. For example,
573 ``@app.post("/login")`` is a shortcut for
574 ``@app.route("/login", methods=["POST"])``. :pr:`3907`
575 - Support async views, error handlers, before and after request, and
576 teardown functions. :pr:`3412`
577 - Support nesting blueprints. :issue:`593, 1548`, :pr:`3923`
578 - Set the default encoding to "UTF-8" when loading ``.env`` and
579 ``.flaskenv`` files to allow to use non-ASCII characters. :issue:`3931`
580 - ``flask shell`` sets up tab and history completion like the default
581 ``python`` shell if ``readline`` is installed. :issue:`3941`
582 - ``helpers.total_seconds()`` is deprecated. Use
583 ``timedelta.total_seconds()`` instead. :pr:`3962`
584 - Add type hinting. :pr:`3973`.
585
586
587 Version 1.1.4
588 -------------
589
590 Released 2021-05-13
591
592 - Update ``static_folder`` to use ``_compat.fspath`` instead of
593 ``os.fspath`` to continue supporting Python < 3.6 :issue:`4050`
594
595
596 Version 1.1.3
597 -------------
598
599 Released 2021-05-13
600
601 - Set maximum versions of Werkzeug, Jinja, Click, and ItsDangerous.
602 :issue:`4043`
603 - Re-add support for passing a ``pathlib.Path`` for ``static_folder``.
604 :pr:`3579`
605
606
607 Version 1.1.2
608 -------------
609
610 Released 2020-04-03
611
612 - Work around an issue when running the ``flask`` command with an
613 external debugger on Windows. :issue:`3297`
614 - The static route will not catch all URLs if the ``Flask``
615 ``static_folder`` argument ends with a slash. :issue:`3452`
616
617
618 Version 1.1.1
619 -------------
620
621 Released 2019-07-08
622
623 - The ``flask.json_available`` flag was added back for compatibility
624 with some extensions. It will raise a deprecation warning when used,
625 and will be removed in version 2.0.0. :issue:`3288`
626
627
628 Version 1.1.0
CHANGES.rst 60

629 -------------
630
631 Released 2019-07-04
632
633 - Bump minimum Werkzeug version to >= 0.15.
634 - Drop support for Python 3.4.
635 - Error handlers for ``InternalServerError`` or ``500`` will always be
636 passed an instance of ``InternalServerError``. If they are invoked
637 due to an unhandled exception, that original exception is now
638 available as ``e.original_exception`` rather than being passed
639 directly to the handler. The same is true if the handler is for the
640 base ``HTTPException``. This makes error handler behavior more
641 consistent. :pr:`3266`
642
643 - ``Flask.finalize_request`` is called for all unhandled
644 exceptions even if there is no ``500`` error handler.
645
646 - ``Flask.logger`` takes the same name as ``Flask.name`` (the value
647 passed as ``Flask(import_name)``. This reverts 1.0's behavior of
648 always logging to ``"flask.app"``, in order to support multiple apps
649 in the same process. A warning will be shown if old configuration is
650 detected that needs to be moved. :issue:`2866`
651 - ``RequestContext.copy`` includes the current session object in the
652 request context copy. This prevents ``session`` pointing to an
653 out-of-date object. :issue:`2935`
654 - Using built-in RequestContext, unprintable Unicode characters in
655 Host header will result in a HTTP 400 response and not HTTP 500 as
656 previously. :pr:`2994`
657 - ``send_file`` supports ``PathLike`` objects as described in
658 :pep:`519`, to support ``pathlib`` in Python 3. :pr:`3059`
659 - ``send_file`` supports ``BytesIO`` partial content.
660 :issue:`2957`
661 - ``open_resource`` accepts the "rt" file mode. This still does the
662 same thing as "r". :issue:`3163`
663 - The ``MethodView.methods`` attribute set in a base class is used by
664 subclasses. :issue:`3138`
665 - ``Flask.jinja_options`` is a ``dict`` instead of an
666 ``ImmutableDict`` to allow easier configuration. Changes must still
667 be made before creating the environment. :pr:`3190`
668 - Flask's ``JSONMixin`` for the request and response wrappers was
669 moved into Werkzeug. Use Werkzeug's version with Flask-specific
670 support. This bumps the Werkzeug dependency to >= 0.15.
671 :issue:`3125`
672 - The ``flask`` command entry point is simplified to take advantage
673 of Werkzeug 0.15's better reloader support. This bumps the Werkzeug
674 dependency to >= 0.15. :issue:`3022`
675 - Support ``static_url_path`` that ends with a forward slash.
676 :issue:`3134`
677 - Support empty ``static_folder`` without requiring setting an empty
678 ``static_url_path`` as well. :pr:`3124`
679 - ``jsonify`` supports ``dataclass`` objects. :pr:`3195`
680 - Allow customizing the ``Flask.url_map_class`` used for routing.
681 :pr:`3069`
682 - The development server port can be set to 0, which tells the OS to
683 pick an available port. :issue:`2926`
684 - The return value from ``cli.load_dotenv`` is more consistent with
685 the documentation. It will return ``False`` if python-dotenv is not
686 installed, or if the given path isn't a file. :issue:`2937`
687 - Signaling support has a stub for the ``connect_via`` method when
688 the Blinker library is not installed. :pr:`3208`
689 - Add an ``--extra-files`` option to the ``flask run`` CLI command to
690 specify extra files that will trigger the reloader on change.
691 :issue:`2897`
692 - Allow returning a dictionary from a view function. Similar to how
693 returning a string will produce a ``text/html`` response, returning
694 a dict will call ``jsonify`` to produce a ``application/json``
695 response. :pr:`3111`
696 - Blueprints have a ``cli`` Click group like ``app.cli``. CLI commands
697 registered with a blueprint will be available as a group under the
698 ``flask`` command. :issue:`1357`.
699 - When using the test client as a context manager (``with client:``),
CHANGES.rst 61

700 all preserved request contexts are popped when the block exits,
701 ensuring nested contexts are cleaned up correctly. :pr:`3157`
702 - Show a better error message when the view return type is not
703 supported. :issue:`3214`
704 - ``flask.testing.make_test_environ_builder()`` has been deprecated in
705 favour of a new class ``flask.testing.EnvironBuilder``. :pr:`3232`
706 - The ``flask run`` command no longer fails if Python is not built
707 with SSL support. Using the ``--cert`` option will show an
708 appropriate error message. :issue:`3211`
709 - URL matching now occurs after the request context is pushed, rather
710 than when it's created. This allows custom URL converters to access
711 the app and request contexts, such as to query a database for an id.
712 :issue:`3088`
713
714
715 Version 1.0.4
716 -------------
717
718 Released 2019-07-04
719
720 - The key information for ``BadRequestKeyError`` is no longer cleared
721 outside debug mode, so error handlers can still access it. This
722 requires upgrading to Werkzeug 0.15.5. :issue:`3249`
723 - ``send_file`` url quotes the ":" and "/" characters for more
724 compatible UTF-8 filename support in some browsers. :issue:`3074`
725 - Fixes for :pep:`451` import loaders and pytest 5.x. :issue:`3275`
726 - Show message about dotenv on stderr instead of stdout. :issue:`3285`
727
728
729 Version 1.0.3
730 -------------
731
732 Released 2019-05-17
733
734 - ``send_file`` encodes filenames as ASCII instead of Latin-1
735 (ISO-8859-1). This fixes compatibility with Gunicorn, which is
736 stricter about header encodings than :pep:`3333`. :issue:`2766`
737 - Allow custom CLIs using ``FlaskGroup`` to set the debug flag without
738 it always being overwritten based on environment variables.
739 :pr:`2765`
740 - ``flask --version`` outputs Werkzeug's version and simplifies the
741 Python version. :pr:`2825`
742 - ``send_file`` handles an ``attachment_filename`` that is a native
743 Python 2 string (bytes) with UTF-8 coded bytes. :issue:`2933`
744 - A catch-all error handler registered for ``HTTPException`` will not
745 handle ``RoutingException``, which is used internally during
746 routing. This fixes the unexpected behavior that had been introduced
747 in 1.0. :pr:`2986`
748 - Passing the ``json`` argument to ``app.test_client`` does not
749 push/pop an extra app context. :issue:`2900`
750
751
752 Version 1.0.2
753 -------------
754
755 Released 2018-05-02
756
757 - Fix more backwards compatibility issues with merging slashes between
758 a blueprint prefix and route. :pr:`2748`
759 - Fix error with ``flask routes`` command when there are no routes.
760 :issue:`2751`
761
762
763 Version 1.0.1
764 -------------
765
766 Released 2018-04-29
767
768 - Fix registering partials (with no ``__name__``) as view functions.
769 :pr:`2730`
770 - Don't treat lists returned from view functions the same as tuples.
CHANGES.rst 62

771 Only tuples are interpreted as response data. :issue:`2736`


772 - Extra slashes between a blueprint's ``url_prefix`` and a route URL
773 are merged. This fixes some backwards compatibility issues with the
774 change in 1.0. :issue:`2731`, :issue:`2742`
775 - Only trap ``BadRequestKeyError`` errors in debug mode, not all
776 ``BadRequest`` errors. This allows ``abort(400)`` to continue
777 working as expected. :issue:`2735`
778 - The ``FLASK_SKIP_DOTENV`` environment variable can be set to ``1``
779 to skip automatically loading dotenv files. :issue:`2722`
780
781
782 Version 1.0
783 -----------
784
785 Released 2018-04-26
786
787 - Python 2.6 and 3.3 are no longer supported.
788 - Bump minimum dependency versions to the latest stable versions:
789 Werkzeug >= 0.14, Jinja >= 2.10, itsdangerous >= 0.24, Click >= 5.1.
790 :issue:`2586`
791 - Skip ``app.run`` when a Flask application is run from the command
792 line. This avoids some behavior that was confusing to debug.
793 - Change the default for ``JSONIFY_PRETTYPRINT_REGULAR`` to
794 ``False``. ``~json.jsonify`` returns a compact format by default,
795 and an indented format in debug mode. :pr:`2193`
796 - ``Flask.__init__`` accepts the ``host_matching`` argument and sets
797 it on ``Flask.url_map``. :issue:`1559`
798 - ``Flask.__init__`` accepts the ``static_host`` argument and passes
799 it as the ``host`` argument when defining the static route.
800 :issue:`1559`
801 - ``send_file`` supports Unicode in ``attachment_filename``.
802 :pr:`2223`
803 - Pass ``_scheme`` argument from ``url_for`` to
804 ``Flask.handle_url_build_error``. :pr:`2017`
805 - ``Flask.add_url_rule`` accepts the ``provide_automatic_options``
806 argument to disable adding the ``OPTIONS`` method. :pr:`1489`
807 - ``MethodView`` subclasses inherit method handlers from base classes.
808 :pr:`1936`
809 - Errors caused while opening the session at the beginning of the
810 request are handled by the app's error handlers. :pr:`2254`
811 - Blueprints gained ``Blueprint.json_encoder`` and
812 ``Blueprint.json_decoder`` attributes to override the app's
813 encoder and decoder. :pr:`1898`
814 - ``Flask.make_response`` raises ``TypeError`` instead of
815 ``ValueError`` for bad response types. The error messages have been
816 improved to describe why the type is invalid. :pr:`2256`
817 - Add ``routes`` CLI command to output routes registered on the
818 application. :pr:`2259`
819 - Show warning when session cookie domain is a bare hostname or an IP
820 address, as these may not behave properly in some browsers, such as
821 Chrome. :pr:`2282`
822 - Allow IP address as exact session cookie domain. :pr:`2282`
823 - ``SESSION_COOKIE_DOMAIN`` is set if it is detected through
824 ``SERVER_NAME``. :pr:`2282`
825 - Auto-detect zero-argument app factory called ``create_app`` or
826 ``make_app`` from ``FLASK_APP``. :pr:`2297`
827 - Factory functions are not required to take a ``script_info``
828 parameter to work with the ``flask`` command. If they take a single
829 parameter or a parameter named ``script_info``, the ``ScriptInfo``
830 object will be passed. :pr:`2319`
831 - ``FLASK_APP`` can be set to an app factory, with arguments if
832 needed, for example ``FLASK_APP=myproject.app:create_app('dev')``.
833 :pr:`2326`
834 - ``FLASK_APP`` can point to local packages that are not installed in
835 editable mode, although ``pip install -e`` is still preferred.
836 :pr:`2414`
837 - The ``View`` class attribute
838 ``View.provide_automatic_options`` is set in ``View.as_view``, to be
839 detected by ``Flask.add_url_rule``. :pr:`2316`
840 - Error handling will try handlers registered for ``blueprint, code``,
841 ``app, code``, ``blueprint, exception``, ``app, exception``.
CHANGES.rst 63

842 :pr:`2314`
843 - ``Cookie`` is added to the response's ``Vary`` header if the session
844 is accessed at all during the request (and not deleted). :pr:`2288`
845 - ``Flask.test_request_context`` accepts ``subdomain`` and
846 ``url_scheme`` arguments for use when building the base URL.
847 :pr:`1621`
848 - Set ``APPLICATION_ROOT`` to ``'/'`` by default. This was already the
849 implicit default when it was set to ``None``.
850 - ``TRAP_BAD_REQUEST_ERRORS`` is enabled by default in debug mode.
851 ``BadRequestKeyError`` has a message with the bad key in debug mode
852 instead of the generic bad request message. :pr:`2348`
853 - Allow registering new tags with ``TaggedJSONSerializer`` to support
854 storing other types in the session cookie. :pr:`2352`
855 - Only open the session if the request has not been pushed onto the
856 context stack yet. This allows ``stream_with_context`` generators to
857 access the same session that the containing view uses. :pr:`2354`
858 - Add ``json`` keyword argument for the test client request methods.
859 This will dump the given object as JSON and set the appropriate
860 content type. :pr:`2358`
861 - Extract JSON handling to a mixin applied to both the ``Request`` and
862 ``Response`` classes. This adds the ``Response.is_json`` and
863 ``Response.get_json`` methods to the response to make testing JSON
864 response much easier. :pr:`2358`
865 - Removed error handler caching because it caused unexpected results
866 for some exception inheritance hierarchies. Register handlers
867 explicitly for each exception if you want to avoid traversing the
868 MRO. :pr:`2362`
869 - Fix incorrect JSON encoding of aware, non-UTC datetimes. :pr:`2374`
870 - Template auto reloading will honor debug mode even if
871 ``Flask.jinja_env`` was already accessed. :pr:`2373`
872 - The following old deprecated code was removed. :issue:`2385`
873
874 - ``flask.ext`` - import extensions directly by their name instead
875 of through the ``flask.ext`` namespace. For example,
876 ``import flask.ext.sqlalchemy`` becomes
877 ``import flask_sqlalchemy``.
878 - ``Flask.init_jinja_globals`` - extend
879 ``Flask.create_jinja_environment`` instead.
880 - ``Flask.error_handlers`` - tracked by
881 ``Flask.error_handler_spec``, use ``Flask.errorhandler``
882 to register handlers.
883 - ``Flask.request_globals_class`` - use
884 ``Flask.app_ctx_globals_class`` instead.
885 - ``Flask.static_path`` - use ``Flask.static_url_path`` instead.
886 - ``Request.module`` - use ``Request.blueprint`` instead.
887
888 - The ``Request.json`` property is no longer deprecated. :issue:`1421`
889 - Support passing a ``EnvironBuilder`` or ``dict`` to
890 ``test_client.open``. :pr:`2412`
891 - The ``flask`` command and ``Flask.run`` will load environment
892 variables from ``.env`` and ``.flaskenv`` files if python-dotenv is
893 installed. :pr:`2416`
894 - When passing a full URL to the test client, the scheme in the URL is
895 used instead of ``PREFERRED_URL_SCHEME``. :pr:`2430`
896 - ``Flask.logger`` has been simplified. ``LOGGER_NAME`` and
897 ``LOGGER_HANDLER_POLICY`` config was removed. The logger is always
898 named ``flask.app``. The level is only set on first access, it
899 doesn't check ``Flask.debug`` each time. Only one format is used,
900 not different ones depending on ``Flask.debug``. No handlers are
901 removed, and a handler is only added if no handlers are already
902 configured. :pr:`2436`
903 - Blueprint view function names may not contain dots. :pr:`2450`
904 - Fix a ``ValueError`` caused by invalid ``Range`` requests in some
905 cases. :issue:`2526`
906 - The development server uses threads by default. :pr:`2529`
907 - Loading config files with ``silent=True`` will ignore ``ENOTDIR``
908 errors. :pr:`2581`
909 - Pass ``--cert`` and ``--key`` options to ``flask run`` to run the
910 development server over HTTPS. :pr:`2606`
911 - Added ``SESSION_COOKIE_SAMESITE`` to control the ``SameSite``
912 attribute on the session cookie. :pr:`2607`
CHANGES.rst 64

913 - Added ``Flask.test_cli_runner`` to create a Click runner that can


914 invoke Flask CLI commands for testing. :pr:`2636`
915 - Subdomain matching is disabled by default and setting
916 ``SERVER_NAME`` does not implicitly enable it. It can be enabled by
917 passing ``subdomain_matching=True`` to the ``Flask`` constructor.
918 :pr:`2635`
919 - A single trailing slash is stripped from the blueprint
920 ``url_prefix`` when it is registered with the app. :pr:`2629`
921 - ``Request.get_json`` doesn't cache the result if parsing fails when
922 ``silent`` is true. :issue:`2651`
923 - ``Request.get_json`` no longer accepts arbitrary encodings. Incoming
924 JSON should be encoded using UTF-8 per :rfc:`8259`, but Flask will
925 autodetect UTF-8, -16, or -32. :pr:`2691`
926 - Added ``MAX_COOKIE_SIZE`` and ``Response.max_cookie_size`` to
927 control when Werkzeug warns about large cookies that browsers may
928 ignore. :pr:`2693`
929 - Updated documentation theme to make docs look better in small
930 windows. :pr:`2709`
931 - Rewrote the tutorial docs and example project to take a more
932 structured approach to help new users avoid common pitfalls.
933 :pr:`2676`
934
935
936 Version 0.12.5
937 --------------
938
939 Released 2020-02-10
940
941 - Pin Werkzeug to < 1.0.0. :issue:`3497`
942
943
944 Version 0.12.4
945 --------------
946
947 Released 2018-04-29
948
949 - Repackage 0.12.3 to fix package layout issue. :issue:`2728`
950
951
952 Version 0.12.3
953 --------------
954
955 Released 2018-04-26
956
957 - ``Request.get_json`` no longer accepts arbitrary encodings.
958 Incoming JSON should be encoded using UTF-8 per :rfc:`8259`, but
959 Flask will autodetect UTF-8, -16, or -32. :issue:`2692`
960 - Fix a Python warning about imports when using ``python -m flask``.
961 :issue:`2666`
962 - Fix a ``ValueError`` caused by invalid ``Range`` requests in some
963 cases.
964
965
966 Version 0.12.2
967 --------------
968
969 Released 2017-05-16
970
971 - Fix a bug in ``safe_join`` on Windows.
972
973
974 Version 0.12.1
975 --------------
976
977 Released 2017-03-31
978
979 - Prevent ``flask run`` from showing a ``NoAppException`` when an
980 ``ImportError`` occurs within the imported application module.
981 - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3.
982 :issue:`2118`
983 - Use the ``SERVER_NAME`` config if it is present as default values
CHANGES.rst 65

984 for ``app.run``. :issue:`2109`, :pr:`2152`


985 - Call ``ctx.auto_pop`` with the exception object instead of ``None``,
986 in the event that a ``BaseException`` such as ``KeyboardInterrupt``
987 is raised in a request handler.
988
989
990 Version 0.12
991 ------------
992
993 Released 2016-12-21, codename Punsch
994
995 - The cli command now responds to ``--version``.
996 - Mimetype guessing and ETag generation for file-like objects in
997 ``send_file`` has been removed. :issue:`104`, :pr`1849`
998 - Mimetype guessing in ``send_file`` now fails loudly and doesn't fall
999 back to ``application/octet-stream``. :pr:`1988`
1000 - Make ``flask.safe_join`` able to join multiple paths like
1001 ``os.path.join`` :pr:`1730`
1002 - Revert a behavior change that made the dev server crash instead of
1003 returning an Internal Server Error. :pr:`2006`
1004 - Correctly invoke response handlers for both regular request
1005 dispatching as well as error handlers.
1006 - Disable logger propagation by default for the app logger.
1007 - Add support for range requests in ``send_file``.
1008 - ``app.test_client`` includes preset default environment, which can
1009 now be directly set, instead of per ``client.get``.
1010 - Fix crash when running under PyPy3. :pr:`1814`
1011
1012
1013 Version 0.11.1
1014 --------------
1015
1016 Released 2016-06-07
1017
1018 - Fixed a bug that prevented ``FLASK_APP=foobar/__init__.py`` from
1019 working. :pr:`1872`
1020
1021
1022 Version 0.11
1023 ------------
1024
1025 Released 2016-05-29, codename Absinthe
1026
1027 - Added support to serializing top-level arrays to ``jsonify``. This
1028 introduces a security risk in ancient browsers.
1029 - Added before_render_template signal.
1030 - Added ``**kwargs`` to ``Flask.test_client`` to support passing
1031 additional keyword arguments to the constructor of
1032 ``Flask.test_client_class``.
1033 - Added ``SESSION_REFRESH_EACH_REQUEST`` config key that controls the
1034 set-cookie behavior. If set to ``True`` a permanent session will be
1035 refreshed each request and get their lifetime extended, if set to
1036 ``False`` it will only be modified if the session actually modifies.
1037 Non permanent sessions are not affected by this and will always
1038 expire if the browser window closes.
1039 - Made Flask support custom JSON mimetypes for incoming data.
1040 - Added support for returning tuples in the form ``(response,
1041 headers)`` from a view function.
1042 - Added ``Config.from_json``.
1043 - Added ``Flask.config_class``.
1044 - Added ``Config.get_namespace``.
1045 - Templates are no longer automatically reloaded outside of debug
1046 mode. This can be configured with the new ``TEMPLATES_AUTO_RELOAD``
1047 config key.
1048 - Added a workaround for a limitation in Python 3.3's namespace
1049 loader.
1050 - Added support for explicit root paths when using Python 3.3's
1051 namespace packages.
1052 - Added ``flask`` and the ``flask.cli`` module to start the
1053 local debug server through the click CLI system. This is recommended
1054 over the old ``flask.run()`` method as it works faster and more
CHANGES.rst 66

1055 reliable due to a different design and also replaces


1056 ``Flask-Script``.
1057 - Error handlers that match specific classes are now checked first,
1058 thereby allowing catching exceptions that are subclasses of HTTP
1059 exceptions (in ``werkzeug.exceptions``). This makes it possible for
1060 an extension author to create exceptions that will by default result
1061 in the HTTP error of their choosing, but may be caught with a custom
1062 error handler if desired.
1063 - Added ``Config.from_mapping``.
1064 - Flask will now log by default even if debug is disabled. The log
1065 format is now hardcoded but the default log handling can be disabled
1066 through the ``LOGGER_HANDLER_POLICY`` configuration key.
1067 - Removed deprecated module functionality.
1068 - Added the ``EXPLAIN_TEMPLATE_LOADING`` config flag which when
1069 enabled will instruct Flask to explain how it locates templates.
1070 This should help users debug when the wrong templates are loaded.
1071 - Enforce blueprint handling in the order they were registered for
1072 template loading.
1073 - Ported test suite to py.test.
1074 - Deprecated ``request.json`` in favour of ``request.get_json()``.
1075 - Add "pretty" and "compressed" separators definitions in jsonify()
1076 method. Reduces JSON response size when
1077 ``JSONIFY_PRETTYPRINT_REGULAR=False`` by removing unnecessary white
1078 space included by default after separators.
1079 - JSON responses are now terminated with a newline character, because
1080 it is a convention that UNIX text files end with a newline and some
1081 clients don't deal well when this newline is missing. :pr:`1262`
1082 - The automatically provided ``OPTIONS`` method is now correctly
1083 disabled if the user registered an overriding rule with the
1084 lowercase-version ``options``. :issue:`1288`
1085 - ``flask.json.jsonify`` now supports the ``datetime.date`` type.
1086 :pr:`1326`
1087 - Don't leak exception info of already caught exceptions to context
1088 teardown handlers. :pr:`1393`
1089 - Allow custom Jinja environment subclasses. :pr:`1422`
1090 - Updated extension dev guidelines.
1091 - ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
1092 - Turn on autoescape for ``flask.templating.render_template_string``
1093 by default. :pr:`1515`
1094 - ``flask.ext`` is now deprecated. :pr:`1484`
1095 - ``send_from_directory`` now raises BadRequest if the filename is
1096 invalid on the server OS. :pr:`1763`
1097 - Added the ``JSONIFY_MIMETYPE`` configuration variable. :pr:`1728`
1098 - Exceptions during teardown handling will no longer leave bad
1099 application contexts lingering around.
1100 - Fixed broken ``test_appcontext_signals()`` test case.
1101 - Raise an ``AttributeError`` in ``helpers.find_package`` with a
1102 useful message explaining why it is raised when a :pep:`302` import
1103 hook is used without an ``is_package()`` method.
1104 - Fixed an issue causing exceptions raised before entering a request
1105 or app context to be passed to teardown handlers.
1106 - Fixed an issue with query parameters getting removed from requests
1107 in the test client when absolute URLs were requested.
1108 - Made ``@before_first_request`` into a decorator as intended.
1109 - Fixed an etags bug when sending a file streams with a name.
1110 - Fixed ``send_from_directory`` not expanding to the application root
1111 path correctly.
1112 - Changed logic of before first request handlers to flip the flag
1113 after invoking. This will allow some uses that are potentially
1114 dangerous but should probably be permitted.
1115 - Fixed Python 3 bug when a handler from
1116 ``app.url_build_error_handlers`` reraises the ``BuildError``.
1117
1118
1119 Version 0.10.1
1120 --------------
1121
1122 Released 2013-06-14
1123
1124 - Fixed an issue where ``|tojson`` was not quoting single quotes which
1125 made the filter not work properly in HTML attributes. Now it's
CHANGES.rst 67

1126 possible to use that filter in single quoted attributes. This should
1127 make using that filter with angular.js easier.
1128 - Added support for byte strings back to the session system. This
1129 broke compatibility with the common case of people putting binary
1130 data for token verification into the session.
1131 - Fixed an issue where registering the same method twice for the same
1132 endpoint would trigger an exception incorrectly.
1133
1134
1135 Version 0.10
1136 ------------
1137
1138 Released 2013-06-13, codename Limoncello
1139
1140 - Changed default cookie serialization format from pickle to JSON to
1141 limit the impact an attacker can do if the secret key leaks.
1142 - Added ``template_test`` methods in addition to the already existing
1143 ``template_filter`` method family.
1144 - Added ``template_global`` methods in addition to the already
1145 existing ``template_filter`` method family.
1146 - Set the content-length header for x-sendfile.
1147 - ``tojson`` filter now does not escape script blocks in HTML5
1148 parsers.
1149 - ``tojson`` used in templates is now safe by default. This was
1150 allowed due to the different escaping behavior.
1151 - Flask will now raise an error if you attempt to register a new
1152 function on an already used endpoint.
1153 - Added wrapper module around simplejson and added default
1154 serialization of datetime objects. This allows much easier
1155 customization of how JSON is handled by Flask or any Flask
1156 extension.
1157 - Removed deprecated internal ``flask.session`` module alias. Use
1158 ``flask.sessions`` instead to get the session module. This is not to
1159 be confused with ``flask.session`` the session proxy.
1160 - Templates can now be rendered without request context. The behavior
1161 is slightly different as the ``request``, ``session`` and ``g``
1162 objects will not be available and blueprint's context processors are
1163 not called.
1164 - The config object is now available to the template as a real global
1165 and not through a context processor which makes it available even in
1166 imported templates by default.
1167 - Added an option to generate non-ascii encoded JSON which should
1168 result in less bytes being transmitted over the network. It's
1169 disabled by default to not cause confusion with existing libraries
1170 that might expect ``flask.json.dumps`` to return bytes by default.
1171 - ``flask.g`` is now stored on the app context instead of the request
1172 context.
1173 - ``flask.g`` now gained a ``get()`` method for not erroring out on
1174 non existing items.
1175 - ``flask.g`` now can be used with the ``in`` operator to see what's
1176 defined and it now is iterable and will yield all attributes stored.
1177 - ``flask.Flask.request_globals_class`` got renamed to
1178 ``flask.Flask.app_ctx_globals_class`` which is a better name to what
1179 it does since 0.10.
1180 - ``request``, ``session`` and ``g`` are now also added as proxies to
1181 the template context which makes them available in imported
1182 templates. One has to be very careful with those though because
1183 usage outside of macros might cause caching.
1184 - Flask will no longer invoke the wrong error handlers if a proxy
1185 exception is passed through.
1186 - Added a workaround for chrome's cookies in localhost not working as
1187 intended with domain names.
1188 - Changed logic for picking defaults for cookie values from sessions
1189 to work better with Google Chrome.
1190 - Added ``message_flashed`` signal that simplifies flashing testing.
1191 - Added support for copying of request contexts for better working
1192 with greenlets.
1193 - Removed custom JSON HTTP exception subclasses. If you were relying
1194 on them you can reintroduce them again yourself trivially. Using
1195 them however is strongly discouraged as the interface was flawed.
1196 - Python requirements changed: requiring Python 2.6 or 2.7 now to
CHANGES.rst 68

1197 prepare for Python 3.3 port.


1198 - Changed how the teardown system is informed about exceptions. This
1199 is now more reliable in case something handles an exception halfway
1200 through the error handling process.
1201 - Request context preservation in debug mode now keeps the exception
1202 information around which means that teardown handlers are able to
1203 distinguish error from success cases.
1204 - Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable.
1205 - Flask now orders JSON keys by default to not trash HTTP caches due
1206 to different hash seeds between different workers.
1207 - Added ``appcontext_pushed`` and ``appcontext_popped`` signals.
1208 - The builtin run method now takes the ``SERVER_NAME`` into account
1209 when picking the default port to run on.
1210 - Added ``flask.request.get_json()`` as a replacement for the old
1211 ``flask.request.json`` property.
1212
1213
1214 Version 0.9
1215 -----------
1216
1217 Released 2012-07-01, codename Campari
1218
1219 - The ``Request.on_json_loading_failed`` now returns a JSON formatted
1220 response by default.
1221 - The ``url_for`` function now can generate anchors to the generated
1222 links.
1223 - The ``url_for`` function now can also explicitly generate URL rules
1224 specific to a given HTTP method.
1225 - Logger now only returns the debug log setting if it was not set
1226 explicitly.
1227 - Unregister a circular dependency between the WSGI environment and
1228 the request object when shutting down the request. This means that
1229 environ ``werkzeug.request`` will be ``None`` after the response was
1230 returned to the WSGI server but has the advantage that the garbage
1231 collector is not needed on CPython to tear down the request unless
1232 the user created circular dependencies themselves.
1233 - Session is now stored after callbacks so that if the session payload
1234 is stored in the session you can still modify it in an after request
1235 callback.
1236 - The ``Flask`` class will avoid importing the provided import name if
1237 it can (the required first parameter), to benefit tools which build
1238 Flask instances programmatically. The Flask class will fall back to
1239 using import on systems with custom module hooks, e.g. Google App
1240 Engine, or when the import name is inside a zip archive (usually an
1241 egg) prior to Python 2.7.
1242 - Blueprints now have a decorator to add custom template filters
1243 application wide, ``Blueprint.app_template_filter``.
1244 - The Flask and Blueprint classes now have a non-decorator method for
1245 adding custom template filters application wide,
1246 ``Flask.add_template_filter`` and
1247 ``Blueprint.add_app_template_filter``.
1248 - The ``get_flashed_messages`` function now allows rendering flashed
1249 message categories in separate blocks, through a ``category_filter``
1250 argument.
1251 - The ``Flask.run`` method now accepts ``None`` for ``host`` and
1252 ``port`` arguments, using default values when ``None``. This allows
1253 for calling run using configuration values, e.g.
1254 ``app.run(app.config.get('MYHOST'), app.config.get('MYPORT'))``,
1255 with proper behavior whether or not a config file is provided.
1256 - The ``render_template`` method now accepts a either an iterable of
1257 template names or a single template name. Previously, it only
1258 accepted a single template name. On an iterable, the first template
1259 found is rendered.
1260 - Added ``Flask.app_context`` which works very similar to the request
1261 context but only provides access to the current application. This
1262 also adds support for URL generation without an active request
1263 context.
1264 - View functions can now return a tuple with the first instance being
1265 an instance of ``Response``. This allows for returning
1266 ``jsonify(error="error msg"), 400`` from a view function.
1267 - ``Flask`` and ``Blueprint`` now provide a ``get_send_file_max_age``
CHANGES.rst 69

1268 hook for subclasses to override behavior of serving static files


1269 from Flask when using ``Flask.send_static_file`` (used for the
1270 default static file handler) and ``helpers.send_file``. This hook is
1271 provided a filename, which for example allows changing cache
1272 controls by file extension. The default max-age for ``send_file``
1273 and static files can be configured through a new
1274 ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, which is used
1275 in the default ``get_send_file_max_age`` implementation.
1276 - Fixed an assumption in sessions implementation which could break
1277 message flashing on sessions implementations which use external
1278 storage.
1279 - Changed the behavior of tuple return values from functions. They are
1280 no longer arguments to the response object, they now have a defined
1281 meaning.
1282 - Added ``Flask.request_globals_class`` to allow a specific class to
1283 be used on creation of the ``g`` instance of each request.
1284 - Added ``required_methods`` attribute to view functions to force-add
1285 methods on registration.
1286 - Added ``flask.after_this_request``.
1287 - Added ``flask.stream_with_context`` and the ability to push contexts
1288 multiple times without producing unexpected behavior.
1289
1290
1291 Version 0.8.1
1292 -------------
1293
1294 Released 2012-07-01
1295
1296 - Fixed an issue with the undocumented ``flask.session`` module to not
1297 work properly on Python 2.5. It should not be used but did cause
1298 some problems for package managers.
1299
1300
1301 Version 0.8
1302 -----------
1303
1304 Released 2011-09-29, codename Rakija
1305
1306 - Refactored session support into a session interface so that the
1307 implementation of the sessions can be changed without having to
1308 override the Flask class.
1309 - Empty session cookies are now deleted properly automatically.
1310 - View functions can now opt out of getting the automatic OPTIONS
1311 implementation.
1312 - HTTP exceptions and Bad Request errors can now be trapped so that
1313 they show up normally in the traceback.
1314 - Flask in debug mode is now detecting some common problems and tries
1315 to warn you about them.
1316 - Flask in debug mode will now complain with an assertion error if a
1317 view was attached after the first request was handled. This gives
1318 earlier feedback when users forget to import view code ahead of
1319 time.
1320 - Added the ability to register callbacks that are only triggered once
1321 at the beginning of the first request with
1322 ``Flask.before_first_request``.
1323 - Malformed JSON data will now trigger a bad request HTTP exception
1324 instead of a value error which usually would result in a 500
1325 internal server error if not handled. This is a backwards
1326 incompatible change.
1327 - Applications now not only have a root path where the resources and
1328 modules are located but also an instance path which is the
1329 designated place to drop files that are modified at runtime (uploads
1330 etc.). Also this is conceptually only instance depending and outside
1331 version control so it's the perfect place to put configuration files
1332 etc.
1333 - Added the ``APPLICATION_ROOT`` configuration variable.
1334 - Implemented ``TestClient.session_transaction`` to easily modify
1335 sessions from the test environment.
1336 - Refactored test client internally. The ``APPLICATION_ROOT``
1337 configuration variable as well as ``SERVER_NAME`` are now properly
1338 used by the test client as defaults.
CHANGES.rst 70

1339 - Added ``View.decorators`` to support simpler decorating of pluggable


1340 (class-based) views.
1341 - Fixed an issue where the test client if used with the "with"
1342 statement did not trigger the execution of the teardown handlers.
1343 - Added finer control over the session cookie parameters.
1344 - HEAD requests to a method view now automatically dispatch to the
1345 ``get`` method if no handler was implemented.
1346 - Implemented the virtual ``flask.ext`` package to import extensions
1347 from.
1348 - The context preservation on exceptions is now an integral component
1349 of Flask itself and no longer of the test client. This cleaned up
1350 some internal logic and lowers the odds of runaway request contexts
1351 in unittests.
1352 - Fixed the Jinja2 environment's ``list_templates`` method not
1353 returning the correct names when blueprints or modules were
1354 involved.
1355
1356
1357 Version 0.7.2
1358 -------------
1359
1360 Released 2011-07-06
1361
1362 - Fixed an issue with URL processors not properly working on
1363 blueprints.
1364
1365
1366 Version 0.7.1
1367 -------------
1368
1369 Released 2011-06-29
1370
1371 - Added missing future import that broke 2.5 compatibility.
1372 - Fixed an infinite redirect issue with blueprints.
1373
1374
1375 Version 0.7
1376 -----------
1377
1378 Released 2011-06-28, codename Grappa
1379
1380 - Added ``Flask.make_default_options_response`` which can be used by
1381 subclasses to alter the default behavior for ``OPTIONS`` responses.
1382 - Unbound locals now raise a proper ``RuntimeError`` instead of an
1383 ``AttributeError``.
1384 - Mimetype guessing and etag support based on file objects is now
1385 deprecated for ``send_file`` because it was unreliable. Pass
1386 filenames instead or attach your own etags and provide a proper
1387 mimetype by hand.
1388 - Static file handling for modules now requires the name of the static
1389 folder to be supplied explicitly. The previous autodetection was not
1390 reliable and caused issues on Google's App Engine. Until 1.0 the old
1391 behavior will continue to work but issue dependency warnings.
1392 - Fixed a problem for Flask to run on jython.
1393 - Added a ``PROPAGATE_EXCEPTIONS`` configuration variable that can be
1394 used to flip the setting of exception propagation which previously
1395 was linked to ``DEBUG`` alone and is now linked to either ``DEBUG``
1396 or ``TESTING``.
1397 - Flask no longer internally depends on rules being added through the
1398 ``add_url_rule`` function and can now also accept regular werkzeug
1399 rules added to the url map.
1400 - Added an ``endpoint`` method to the flask application object which
1401 allows one to register a callback to an arbitrary endpoint with a
1402 decorator.
1403 - Use Last-Modified for static file sending instead of Date which was
1404 incorrectly introduced in 0.6.
1405 - Added ``create_jinja_loader`` to override the loader creation
1406 process.
1407 - Implemented a silent flag for ``config.from_pyfile``.
1408 - Added ``teardown_request`` decorator, for functions that should run
1409 at the end of a request regardless of whether an exception occurred.
CHANGES.rst 71

1410 Also the behavior for ``after_request`` was changed. It's now no
1411 longer executed when an exception is raised.
1412 - Implemented ``has_request_context``.
1413 - Deprecated ``init_jinja_globals``. Override the
1414 ``Flask.create_jinja_environment`` method instead to achieve the
1415 same functionality.
1416 - Added ``safe_join``.
1417 - The automatic JSON request data unpacking now looks at the charset
1418 mimetype parameter.
1419 - Don't modify the session on ``get_flashed_messages`` if there are no
1420 messages in the session.
1421 - ``before_request`` handlers are now able to abort requests with
1422 errors.
1423 - It is not possible to define user exception handlers. That way you
1424 can provide custom error messages from a central hub for certain
1425 errors that might occur during request processing (for instance
1426 database connection errors, timeouts from remote resources etc.).
1427 - Blueprints can provide blueprint specific error handlers.
1428 - Implemented generic class-based views.
1429
1430
1431 Version 0.6.1
1432 -------------
1433
1434 Released 2010-12-31
1435
1436 - Fixed an issue where the default ``OPTIONS`` response was not
1437 exposing all valid methods in the ``Allow`` header.
1438 - Jinja2 template loading syntax now allows "./" in front of a
1439 template load path. Previously this caused issues with module
1440 setups.
1441 - Fixed an issue where the subdomain setting for modules was ignored
1442 for the static folder.
1443 - Fixed a security problem that allowed clients to download arbitrary
1444 files if the host server was a windows based operating system and
1445 the client uses backslashes to escape the directory the files where
1446 exposed from.
1447
1448
1449 Version 0.6
1450 -----------
1451
1452 Released 2010-07-27, codename Whisky
1453
1454 - After request functions are now called in reverse order of
1455 registration.
1456 - OPTIONS is now automatically implemented by Flask unless the
1457 application explicitly adds 'OPTIONS' as method to the URL rule. In
1458 this case no automatic OPTIONS handling kicks in.
1459 - Static rules are now even in place if there is no static folder for
1460 the module. This was implemented to aid GAE which will remove the
1461 static folder if it's part of a mapping in the .yml file.
1462 - ``Flask.config`` is now available in the templates as ``config``.
1463 - Context processors will no longer override values passed directly to
1464 the render function.
1465 - Added the ability to limit the incoming request data with the new
1466 ``MAX_CONTENT_LENGTH`` configuration value.
1467 - The endpoint for the ``Module.add_url_rule`` method is now optional
1468 to be consistent with the function of the same name on the
1469 application object.
1470 - Added a ``make_response`` function that simplifies creating response
1471 object instances in views.
1472 - Added signalling support based on blinker. This feature is currently
1473 optional and supposed to be used by extensions and applications. If
1474 you want to use it, make sure to have ``blinker`` installed.
1475 - Refactored the way URL adapters are created. This process is now
1476 fully customizable with the ``Flask.create_url_adapter`` method.
1477 - Modules can now register for a subdomain instead of just an URL
1478 prefix. This makes it possible to bind a whole module to a
1479 configurable subdomain.
1480
CHANGES.rst 72

1481
1482 Version 0.5.2
1483 -------------
1484
1485 Released 2010-07-15
1486
1487 - Fixed another issue with loading templates from directories when
1488 modules were used.
1489
1490
1491 Version 0.5.1
1492 -------------
1493
1494 Released 2010-07-06
1495
1496 - Fixes an issue with template loading from directories when modules
1497 where used.
1498
1499
1500 Version 0.5
1501 -----------
1502
1503 Released 2010-07-06, codename Calvados
1504
1505 - Fixed a bug with subdomains that was caused by the inability to
1506 specify the server name. The server name can now be set with the
1507 ``SERVER_NAME`` config key. This key is now also used to set the
1508 session cookie cross-subdomain wide.
1509 - Autoescaping is no longer active for all templates. Instead it is
1510 only active for ``.html``, ``.htm``, ``.xml`` and ``.xhtml``. Inside
1511 templates this behavior can be changed with the ``autoescape`` tag.
1512 - Refactored Flask internally. It now consists of more than a single
1513 file.
1514 - ``send_file`` now emits etags and has the ability to do conditional
1515 responses builtin.
1516 - (temporarily) dropped support for zipped applications. This was a
1517 rarely used feature and led to some confusing behavior.
1518 - Added support for per-package template and static-file directories.
1519 - Removed support for ``create_jinja_loader`` which is no longer used
1520 in 0.5 due to the improved module support.
1521 - Added a helper function to expose files from any directory.
1522
1523
1524 Version 0.4
1525 -----------
1526
1527 Released 2010-06-18, codename Rakia
1528
1529 - Added the ability to register application wide error handlers from
1530 modules.
1531 - ``Flask.after_request`` handlers are now also invoked if the request
1532 dies with an exception and an error handling page kicks in.
1533 - Test client has not the ability to preserve the request context for
1534 a little longer. This can also be used to trigger custom requests
1535 that do not pop the request stack for testing.
1536 - Because the Python standard library caches loggers, the name of the
1537 logger is configurable now to better support unittests.
1538 - Added ``TESTING`` switch that can activate unittesting helpers.
1539 - The logger switches to ``DEBUG`` mode now if debug is enabled.
1540
1541
1542 Version 0.3.1
1543 -------------
1544
1545 Released 2010-05-28
1546
1547 - Fixed a error reporting bug with ``Config.from_envvar``.
1548 - Removed some unused code.
1549 - Release does no longer include development leftover files (.git
1550 folder for themes, built documentation in zip and pdf file and some
1551 .pyc files)
73

1552
1553
1554 Version 0.3
1555 -----------
1556
1557 Released 2010-05-28, codename Schnaps
1558
1559 - Added support for categories for flashed messages.
1560 - The application now configures a ``logging.Handler`` and will log
1561 request handling exceptions to that logger when not in debug mode.
1562 This makes it possible to receive mails on server errors for
1563 example.
1564 - Added support for context binding that does not require the use of
1565 the with statement for playing in the console.
1566 - The request context is now available within the with statement
1567 making it possible to further push the request context or pop it.
1568 - Added support for configurations.
1569
1570
1571 Version 0.2
1572 -----------
1573
1574 Released 2010-05-12, codename J?germeister
1575
1576 - Various bugfixes
1577 - Integrated JSON support
1578 - Added ``get_template_attribute`` helper function.
1579 - ``Flask.add_url_rule`` can now also register a view function.
1580 - Refactored internal request dispatching.
1581 - Server listens on 127.0.0.1 by default now to fix issues with
1582 chrome.
1583 - Added external URL support.
1584 - Added support for ``send_file``.
1585 - Module support and internal request handling refactoring to better
1586 support pluggable applications.
1587 - Sessions can be set to be permanent now on a per-session basis.
1588 - Better error reporting on missing secret keys.
1589 - Added support for Google Appengine.
1590
1591
1592 Version 0.1
1593 -----------
1594
1595 Released 2010-04-16
1596
1597 - First public preview release.

tests/static/index.html
1 <h1>Hello World!</h1>

tests/static/config.toml
1 TEST_KEY="foo"
2 SECRET_KEY="config"

tests/static/config.json
1 {
2 "TEST_KEY": "foo",
3 "SECRET_KEY": "config"
4 }
74

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()

tests/templates/nested/nested.txt
1 I'm nested

tests/templates/mail.txt
1 {{ foo}} Mail

tests/templates/simple_template.html
1 <h1>{{ whiskey }}</h1>

tests/templates/template_filter.html
1 {{ value|super_reverse }}

tests/templates/context_template.html
1 <p>{{ value }}|{{ injected_value }}

tests/templates/template_test.html
1 {% if value is boolean %}
2 Success!
3 {% endif %}

tests/templates/_macro.html
1 {% macro hello(name) %}Hello {{ name }}!{% endmacro %}

tests/templates/escaping_template.html
1 {{ text }}
2 {{ html }}
75

3 {% autoescape false %}{{ text }}


4 {{ html }}{% endautoescape %}
5 {% autoescape true %}{{ text }}
6 {{ html }}{% endautoescape %}

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 }}

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"

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
76

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

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("/<list:args>")
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("/<ctx:name>")
39 def index(name):
40 return name
41
42 assert client.get("/admin").data == b"admin"

tests/test_apps/.flaskenv
1 FOO=flaskenv
2 BAR=bar
3 EGGS=0
77

tests/test_apps/.env
1 FOO=env
2 SPAM=1
3 EGGS=2
4 HAM=��

tests/test_apps/subdomaintestmodule/static/hello.txt
1 Hello Subdomain

tests/test_apps/subdomaintestmodule/__init__.py
1 from flask import Module
2
3 mod = Module(__name__, "foo", subdomain="foo")

tests/test_apps/helloworld/wsgi.py
1 from hello import app # noqa: F401

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!"

tests/test_apps/cliapp/__init__.py
1

tests/test_apps/cliapp/message.txt
1 So long, and thanks for all the fish.

tests/test_apps/cliapp/app.py
1 from flask import Flask
2
3 testapp = Flask("testapp")

tests/test_apps/cliapp/multiapp.py
1 from flask import Flask
2
3 app1 = Flask("app1")
4 app2 = Flask("app2")
78

tests/test_apps/cliapp/importerrorapp.py
1 from flask import Flask
2
3 raise ImportError()
4
5 testapp = Flask("testapp")

tests/test_apps/cliapp/inner1/inner2/__init__.py
1

tests/test_apps/cliapp/inner1/inner2/flask.py
1 from flask import Flask
2
3 app = Flask(__name__)

tests/test_apps/cliapp/inner1/__init__.py
1 from flask import Flask
2
3 application = Flask(__name__)

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

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)

tests/test_apps/blueprintapp/apps/__init__.py
1

tests/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html
1 Hello from the Frontend
79

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")

tests/test_apps/blueprintapp/apps/admin/templates/admin/index.html
1 Hello from the Admin

tests/test_apps/blueprintapp/apps/admin/static/test.txt
1 Admin File

tests/test_apps/blueprintapp/apps/admin/static/css/test.css
1 /* nested file */

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")

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
80

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

tests/test_json_tag.py
1 from datetime import datetime
2 from datetime import timezone
3 from uuid import uuid4
4
5 import pytest
tests/test_json_tag.py 81

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("<html>"),
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
82

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)

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")
83

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

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)
tests/test_async.py 84

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():
85

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

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"
tests/test_instance_config.py 86

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, limit_loader
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 limit_loader, 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")
87

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 limit_loader, 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 )

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: ...

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
88

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 ""

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 "<p>Hello, World!</p>"
19
20
21 @app.route("/bytes")
22 def hello_bytes() -> bytes:
23 return b"<p>Hello, World!</p>"
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
89

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/<int:code>")
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/<name>")
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 )
tests/conftest.py 90

tests/conftest.py
1 import os
2 import pkgutil
3 import sys
4
5 import pytest
6 from _pytest import monkeypatch
7
8 from flask import Flask
9 from flask.globals import request_ctx
10
11
12 @pytest.fixture(scope="session", autouse=True)
13 def _standard_os_environ():
14 """Set up ``os.environ`` at the start of the test session to have
15 standard values. Returns a list of operations that is used by
16 :func:`._reset_os_environ` after each test.
17 """
18 mp = monkeypatch.MonkeyPatch()
19 out = (
20 (os.environ, "FLASK_ENV_FILE", monkeypatch.notset),
21 (os.environ, "FLASK_APP", monkeypatch.notset),
22 (os.environ, "FLASK_DEBUG", monkeypatch.notset),
23 (os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset),
24 (os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset),
25 )
26
27 for _, key, value in out:
28 if value is monkeypatch.notset:
29 mp.delenv(key, False)
30 else:
31 mp.setenv(key, value)
32
33 yield out
34 mp.undo()
35
36
37 @pytest.fixture(autouse=True)
38 def _reset_os_environ(monkeypatch, _standard_os_environ):
39 """Reset ``os.environ`` to the standard environ after each test,
40 in case a test changed something without cleaning up.
41 """
42 monkeypatch._setitem.extend(_standard_os_environ)
43
44
45 @pytest.fixture
46 def app():
47 app = Flask("flask_test", root_path=os.path.dirname(__file__))
48 app.config.update(
49 TESTING=True,
50 SECRET_KEY="test key",
51 )
52 return app
53
54
55 @pytest.fixture
56 def app_ctx(app):
57 with app.app_context() as ctx:
58 yield ctx
59
60
61 @pytest.fixture
62 def req_ctx(app):
63 with app.test_request_context() as ctx:
64 yield ctx
65
66
67 @pytest.fixture
68 def client(app):
69 return app.test_client()
tests/conftest.py 91

70
71
72 @pytest.fixture
73 def test_apps(monkeypatch):
74 monkeypatch.syspath_prepend(os.path.join(os.path.dirname(__file__), "test_apps"))
75 original_modules = set(sys.modules.keys())
76
77 yield
78
79 # Remove any imports cached during the test. Otherwise "import app"
80 # will work in the next test even though it's no longer on the path.
81 for key in sys.modules.keys() - original_modules:
82 sys.modules.pop(key)
83
84
85 @pytest.fixture(autouse=True)
86 def leak_detector():
87 yield
88
89 # make sure we're not leaking a request context since we are
90 # testing flask internally in debug mode in a few cases
91 leaks = []
92 while request_ctx:
93 leaks.append(request_ctx._get_current_object())
94 request_ctx.pop()
95
96 assert leaks == []
97
98
99 @pytest.fixture(params=(True, False))
100 def limit_loader(request, monkeypatch):
101 """Patch pkgutil.get_loader to give loader without get_filename or archive.
102
103 This provides for tests where a system has custom loaders, e.g. Google App
104 Engine's HardenedModulesHook, which have neither the `get_filename` method
105 nor the `archive` attribute.
106
107 This fixture will run the testcase twice, once with and once without the
108 limitation/mock.
109 """
110 if not request.param:
111 return
112
113 class LimitedLoader:
114 def __init__(self, loader):
115 self.loader = loader
116
117 def __getattr__(self, name):
118 if name in {"archive", "get_filename"}:
119 raise AttributeError(f"Mocking a loader which does not have {name!r}.")
120 return getattr(self.loader, name)
121
122 old_get_loader = pkgutil.get_loader
123
124 def get_loader(*args, **kwargs):
125 return LimitedLoader(old_get_loader(*args, **kwargs))
126
127 monkeypatch.setattr(pkgutil, "get_loader", get_loader)
128
129
130 @pytest.fixture
131 def modules_tmp_path(tmp_path, monkeypatch):
132 """A temporary directory added to sys.path."""
133 rv = tmp_path / "modules_tmp"
134 rv.mkdir()
135 monkeypatch.syspath_prepend(os.fspath(rv))
136 return rv
137
138
139 @pytest.fixture
140 def modules_tmp_path_prefix(modules_tmp_path, monkeypatch):
92

141 monkeypatch.setattr(sys, "prefix", os.fspath(modules_tmp_path))


142 return modules_tmp_path
143
144
145 @pytest.fixture
146 def site_packages(modules_tmp_path, monkeypatch):
147 """Create a fake site-packages."""
148 py_dir = f"python{sys.version_info.major}.{sys.version_info.minor}"
149 rv = modules_tmp_path / "lib" / py_dir / "site-packages"
150 rv.mkdir(parents=True)
151 monkeypatch.syspath_prepend(os.fspath(rv))
152 return rv
153
154
155 @pytest.fixture
156 def purge_module(request):
157 def inner(name):
158 request.addfinalizer(lambda: sys.modules.pop(name, None))
159
160 return inner

tests/test_signals.py
1 import flask
2
3
4 def test_template_rendered(app, client):
5 @app.route("/")
6 def index():
7 return flask.render_template("simple_template.html", whiskey=42)
8
9 recorded = []
10
11 def record(sender, template, context):
12 recorded.append((template, context))
13
14 flask.template_rendered.connect(record, app)
15 try:
16 client.get("/")
17 assert len(recorded) == 1
18 template, context = recorded[0]
19 assert template.name == "simple_template.html"
20 assert context["whiskey"] == 42
21 finally:
22 flask.template_rendered.disconnect(record, app)
23
24
25 def test_before_render_template():
26 app = flask.Flask(__name__)
27
28 @app.route("/")
29 def index():
30 return flask.render_template("simple_template.html", whiskey=42)
31
32 recorded = []
33
34 def record(sender, template, context):
35 context["whiskey"] = 43
36 recorded.append((template, context))
37
38 flask.before_render_template.connect(record, app)
39 try:
40 rv = app.test_client().get("/")
41 assert len(recorded) == 1
42 template, context = recorded[0]
43 assert template.name == "simple_template.html"
44 assert context["whiskey"] == 43
45 assert rv.data == b"<h1>43</h1>"
46 finally:
47 flask.before_render_template.disconnect(record, app)
tests/test_signals.py 93

48
49
50 def test_request_signals():
51 app = flask.Flask(__name__)
52 calls = []
53
54 def before_request_signal(sender):
55 calls.append("before-signal")
56
57 def after_request_signal(sender, response):
58 assert response.data == b"stuff"
59 calls.append("after-signal")
60
61 @app.before_request
62 def before_request_handler():
63 calls.append("before-handler")
64
65 @app.after_request
66 def after_request_handler(response):
67 calls.append("after-handler")
68 response.data = "stuff"
69 return response
70
71 @app.route("/")
72 def index():
73 calls.append("handler")
74 return "ignored anyway"
75
76 flask.request_started.connect(before_request_signal, app)
77 flask.request_finished.connect(after_request_signal, app)
78
79 try:
80 rv = app.test_client().get("/")
81 assert rv.data == b"stuff"
82
83 assert calls == [
84 "before-signal",
85 "before-handler",
86 "handler",
87 "after-handler",
88 "after-signal",
89 ]
90 finally:
91 flask.request_started.disconnect(before_request_signal, app)
92 flask.request_finished.disconnect(after_request_signal, app)
93
94
95 def test_request_exception_signal():
96 app = flask.Flask(__name__)
97 recorded = []
98
99 @app.route("/")
100 def index():
101 raise ZeroDivisionError
102
103 def record(sender, exception):
104 recorded.append(exception)
105
106 flask.got_request_exception.connect(record, app)
107 try:
108 assert app.test_client().get("/").status_code == 500
109 assert len(recorded) == 1
110 assert isinstance(recorded[0], ZeroDivisionError)
111 finally:
112 flask.got_request_exception.disconnect(record, app)
113
114
115 def test_appcontext_signals(app, client):
116 recorded = []
117
118 def record_push(sender, **kwargs):
94

119 recorded.append("push")
120
121 def record_pop(sender, **kwargs):
122 recorded.append("pop")
123
124 @app.route("/")
125 def index():
126 return "Hello"
127
128 flask.appcontext_pushed.connect(record_push, app)
129 flask.appcontext_popped.connect(record_pop, app)
130 try:
131 rv = client.get("/")
132 assert rv.data == b"Hello"
133 assert recorded == ["push", "pop"]
134 finally:
135 flask.appcontext_pushed.disconnect(record_push, app)
136 flask.appcontext_popped.disconnect(record_pop, app)
137
138
139 def test_flash_signal(app):
140 @app.route("/")
141 def index():
142 flask.flash("This is a flash message", category="notice")
143 return flask.redirect("/other")
144
145 recorded = []
146
147 def record(sender, message, category):
148 recorded.append((message, category))
149
150 flask.message_flashed.connect(record, app)
151 try:
152 client = app.test_client()
153 with client.session_transaction():
154 client.get("/")
155 assert len(recorded) == 1
156 message, category = recorded[0]
157 assert message == "This is a flash message"
158 assert category == "notice"
159 finally:
160 flask.message_flashed.disconnect(record, app)
161
162
163 def test_appcontext_tearing_down_signal(app, client):
164 app.testing = False
165 recorded = []
166
167 def record_teardown(sender, exc):
168 recorded.append(exc)
169
170 @app.route("/")
171 def index():
172 raise ZeroDivisionError
173
174 flask.appcontext_tearing_down.connect(record_teardown, app)
175 try:
176 rv = client.get("/")
177 assert rv.status_code == 500
178 assert len(recorded) == 1
179 assert isinstance(recorded[0], ZeroDivisionError)
180 finally:
181 flask.appcontext_tearing_down.disconnect(record_teardown, app)

tests/test_appctx.py
1 import pytest
2
3 import flask
4 from flask.globals import app_ctx
tests/test_appctx.py 95

5 from flask.globals import request_ctx


6
7
8 def test_basic_url_generation(app):
9 app.config["SERVER_NAME"] = "localhost"
10 app.config["PREFERRED_URL_SCHEME"] = "https"
11
12 @app.route("/")
13 def index():
14 pass
15
16 with app.app_context():
17 rv = flask.url_for("index")
18 assert rv == "https://localhost/"
19
20
21 def test_url_generation_requires_server_name(app):
22 with app.app_context():
23 with pytest.raises(RuntimeError):
24 flask.url_for("index")
25
26
27 def test_url_generation_without_context_fails():
28 with pytest.raises(RuntimeError):
29 flask.url_for("index")
30
31
32 def test_request_context_means_app_context(app):
33 with app.test_request_context():
34 assert flask.current_app._get_current_object() is app
35 assert not flask.current_app
36
37
38 def test_app_context_provides_current_app(app):
39 with app.app_context():
40 assert flask.current_app._get_current_object() is app
41 assert not flask.current_app
42
43
44 def test_app_tearing_down(app):
45 cleanup_stuff = []
46
47 @app.teardown_appcontext
48 def cleanup(exception):
49 cleanup_stuff.append(exception)
50
51 with app.app_context():
52 pass
53
54 assert cleanup_stuff == [None]
55
56
57 def test_app_tearing_down_with_previous_exception(app):
58 cleanup_stuff = []
59
60 @app.teardown_appcontext
61 def cleanup(exception):
62 cleanup_stuff.append(exception)
63
64 try:
65 raise Exception("dummy")
66 except Exception:
67 pass
68
69 with app.app_context():
70 pass
71
72 assert cleanup_stuff == [None]
73
74
75 def test_app_tearing_down_with_handled_exception_by_except_block(app):
tests/test_appctx.py 96

76 cleanup_stuff = []
77
78 @app.teardown_appcontext
79 def cleanup(exception):
80 cleanup_stuff.append(exception)
81
82 with app.app_context():
83 try:
84 raise Exception("dummy")
85 except Exception:
86 pass
87
88 assert cleanup_stuff == [None]
89
90
91 def test_app_tearing_down_with_handled_exception_by_app_handler(app, client):
92 app.config["PROPAGATE_EXCEPTIONS"] = True
93 cleanup_stuff = []
94
95 @app.teardown_appcontext
96 def cleanup(exception):
97 cleanup_stuff.append(exception)
98
99 @app.route("/")
100 def index():
101 raise Exception("dummy")
102
103 @app.errorhandler(Exception)
104 def handler(f):
105 return flask.jsonify(str(f))
106
107 with app.app_context():
108 client.get("/")
109
110 assert cleanup_stuff == [None]
111
112
113 def test_app_tearing_down_with_unhandled_exception(app, client):
114 app.config["PROPAGATE_EXCEPTIONS"] = True
115 cleanup_stuff = []
116
117 @app.teardown_appcontext
118 def cleanup(exception):
119 cleanup_stuff.append(exception)
120
121 @app.route("/")
122 def index():
123 raise ValueError("dummy")
124
125 with pytest.raises(ValueError, match="dummy"):
126 with app.app_context():
127 client.get("/")
128
129 assert len(cleanup_stuff) == 1
130 assert isinstance(cleanup_stuff[0], ValueError)
131 assert str(cleanup_stuff[0]) == "dummy"
132
133
134 def test_app_ctx_globals_methods(app, app_ctx):
135 # get
136 assert flask.g.get("foo") is None
137 assert flask.g.get("foo", "bar") == "bar"
138 # __contains__
139 assert "foo" not in flask.g
140 flask.g.foo = "bar"
141 assert "foo" in flask.g
142 # setdefault
143 flask.g.setdefault("bar", "the cake is a lie")
144 flask.g.setdefault("bar", "hello world")
145 assert flask.g.bar == "the cake is a lie"
146 # pop
97

147 assert flask.g.pop("bar") == "the cake is a lie"


148 with pytest.raises(KeyError):
149 flask.g.pop("bar")
150 assert flask.g.pop("bar", "more cake") == "more cake"
151 # __iter__
152 assert list(flask.g) == ["foo"]
153 # __repr__
154 assert repr(flask.g) == "<flask.g of 'flask_test'>"
155
156
157 def test_custom_app_ctx_globals_class(app):
158 class CustomRequestGlobals:
159 def __init__(self):
160 self.spam = "eggs"
161
162 app.app_ctx_globals_class = CustomRequestGlobals
163 with app.app_context():
164 assert flask.render_template_string("{{ g.spam }}") == "eggs"
165
166
167 def test_context_refcounts(app, client):
168 called = []
169
170 @app.teardown_request
171 def teardown_req(error=None):
172 called.append("request")
173
174 @app.teardown_appcontext
175 def teardown_app(error=None):
176 called.append("app")
177
178 @app.route("/")
179 def index():
180 with app_ctx:
181 with request_ctx:
182 pass
183
184 assert flask.request.environ["werkzeug.request"] is not None
185 return ""
186
187 res = client.get("/")
188 assert res.status_code == 200
189 assert res.data == b""
190 assert called == ["request", "app"]
191
192
193 def test_clean_pop(app):
194 app.testing = False
195 called = []
196
197 @app.teardown_request
198 def teardown_req(error=None):
199 raise ZeroDivisionError
200
201 @app.teardown_appcontext
202 def teardown_app(error=None):
203 called.append("TEARDOWN")
204
205 with app.app_context():
206 called.append(flask.current_app.name)
207
208 assert called == ["flask_test", "TEARDOWN"]
209 assert not flask.current_app

tests/test_views.py
1 import pytest
2 from werkzeug.http import parse_set_header
3
4 import flask.views
tests/test_views.py 98

5
6
7 def common_test(app):
8 c = app.test_client()
9
10 assert c.get("/").data == b"GET"
11 assert c.post("/").data == b"POST"
12 assert c.put("/").status_code == 405
13 meths = parse_set_header(c.open("/", method="OPTIONS").headers["Allow"])
14 assert sorted(meths) == ["GET", "HEAD", "OPTIONS", "POST"]
15
16
17 def test_basic_view(app):
18 class Index(flask.views.View):
19 methods = ["GET", "POST"]
20
21 def dispatch_request(self):
22 return flask.request.method
23
24 app.add_url_rule("/", view_func=Index.as_view("index"))
25 common_test(app)
26
27
28 def test_method_based_view(app):
29 class Index(flask.views.MethodView):
30 def get(self):
31 return "GET"
32
33 def post(self):
34 return "POST"
35
36 app.add_url_rule("/", view_func=Index.as_view("index"))
37
38 common_test(app)
39
40
41 def test_view_patching(app):
42 class Index(flask.views.MethodView):
43 def get(self):
44 raise ZeroDivisionError
45
46 def post(self):
47 raise ZeroDivisionError
48
49 class Other(Index):
50 def get(self):
51 return "GET"
52
53 def post(self):
54 return "POST"
55
56 view = Index.as_view("index")
57 view.view_class = Other
58 app.add_url_rule("/", view_func=view)
59 common_test(app)
60
61
62 def test_view_inheritance(app, client):
63 class Index(flask.views.MethodView):
64 def get(self):
65 return "GET"
66
67 def post(self):
68 return "POST"
69
70 class BetterIndex(Index):
71 def delete(self):
72 return "DELETE"
73
74 app.add_url_rule("/", view_func=BetterIndex.as_view("index"))
75
tests/test_views.py 99

76 meths = parse_set_header(client.open("/", method="OPTIONS").headers["Allow"])


77 assert sorted(meths) == ["DELETE", "GET", "HEAD", "OPTIONS", "POST"]
78
79
80 def test_view_decorators(app, client):
81 def add_x_parachute(f):
82 def new_function(*args, **kwargs):
83 resp = flask.make_response(f(*args, **kwargs))
84 resp.headers["X-Parachute"] = "awesome"
85 return resp
86
87 return new_function
88
89 class Index(flask.views.View):
90 decorators = [add_x_parachute]
91
92 def dispatch_request(self):
93 return "Awesome"
94
95 app.add_url_rule("/", view_func=Index.as_view("index"))
96 rv = client.get("/")
97 assert rv.headers["X-Parachute"] == "awesome"
98 assert rv.data == b"Awesome"
99
100
101 def test_view_provide_automatic_options_attr():
102 app = flask.Flask(__name__)
103
104 class Index1(flask.views.View):
105 provide_automatic_options = False
106
107 def dispatch_request(self):
108 return "Hello World!"
109
110 app.add_url_rule("/", view_func=Index1.as_view("index"))
111 c = app.test_client()
112 rv = c.open("/", method="OPTIONS")
113 assert rv.status_code == 405
114
115 app = flask.Flask(__name__)
116
117 class Index2(flask.views.View):
118 methods = ["OPTIONS"]
119 provide_automatic_options = True
120
121 def dispatch_request(self):
122 return "Hello World!"
123
124 app.add_url_rule("/", view_func=Index2.as_view("index"))
125 c = app.test_client()
126 rv = c.open("/", method="OPTIONS")
127 assert sorted(rv.allow) == ["OPTIONS"]
128
129 app = flask.Flask(__name__)
130
131 class Index3(flask.views.View):
132 def dispatch_request(self):
133 return "Hello World!"
134
135 app.add_url_rule("/", view_func=Index3.as_view("index"))
136 c = app.test_client()
137 rv = c.open("/", method="OPTIONS")
138 assert "OPTIONS" in rv.allow
139
140
141 def test_implicit_head(app, client):
142 class Index(flask.views.MethodView):
143 def get(self):
144 return flask.Response("Blub", headers={"X-Method": flask.request.method})
145
146 app.add_url_rule("/", view_func=Index.as_view("index"))
tests/test_views.py 100

147 rv = client.get("/")
148 assert rv.data == b"Blub"
149 assert rv.headers["X-Method"] == "GET"
150 rv = client.head("/")
151 assert rv.data == b""
152 assert rv.headers["X-Method"] == "HEAD"
153
154
155 def test_explicit_head(app, client):
156 class Index(flask.views.MethodView):
157 def get(self):
158 return "GET"
159
160 def head(self):
161 return flask.Response("", headers={"X-Method": "HEAD"})
162
163 app.add_url_rule("/", view_func=Index.as_view("index"))
164 rv = client.get("/")
165 assert rv.data == b"GET"
166 rv = client.head("/")
167 assert rv.data == b""
168 assert rv.headers["X-Method"] == "HEAD"
169
170
171 def test_endpoint_override(app):
172 app.debug = True
173
174 class Index(flask.views.View):
175 methods = ["GET", "POST"]
176
177 def dispatch_request(self):
178 return flask.request.method
179
180 app.add_url_rule("/", view_func=Index.as_view("index"))
181
182 with pytest.raises(AssertionError):
183 app.add_url_rule("/", view_func=Index.as_view("index"))
184
185 # But these tests should still pass. We just log a warning.
186 common_test(app)
187
188
189 def test_methods_var_inheritance(app, client):
190 class BaseView(flask.views.MethodView):
191 methods = ["GET", "PROPFIND"]
192
193 class ChildView(BaseView):
194 def get(self):
195 return "GET"
196
197 def propfind(self):
198 return "PROPFIND"
199
200 app.add_url_rule("/", view_func=ChildView.as_view("index"))
201
202 assert client.get("/").data == b"GET"
203 assert client.open("/", method="PROPFIND").data == b"PROPFIND"
204 assert ChildView.methods == {"PROPFIND", "GET"}
205
206
207 def test_multiple_inheritance(app, client):
208 class GetView(flask.views.MethodView):
209 def get(self):
210 return "GET"
211
212 class DeleteView(flask.views.MethodView):
213 def delete(self):
214 return "DELETE"
215
216 class GetDeleteView(GetView, DeleteView):
217 pass
101

218
219 app.add_url_rule("/", view_func=GetDeleteView.as_view("index"))
220
221 assert client.get("/").data == b"GET"
222 assert client.delete("/").data == b"DELETE"
223 assert sorted(GetDeleteView.methods) == ["DELETE", "GET"]
224
225
226 def test_remove_method_from_parent(app, client):
227 class GetView(flask.views.MethodView):
228 def get(self):
229 return "GET"
230
231 class OtherView(flask.views.MethodView):
232 def post(self):
233 return "POST"
234
235 class View(GetView, OtherView):
236 methods = ["GET"]
237
238 app.add_url_rule("/", view_func=View.as_view("index"))
239
240 assert client.get("/").data == b"GET"
241 assert client.post("/").status_code == 405
242 assert sorted(View.methods) == ["GET"]
243
244
245 def test_init_once(app, client):
246 n = 0
247
248 class CountInit(flask.views.View):
249 init_every_request = False
250
251 def __init__(self):
252 nonlocal n
253 n += 1
254
255 def dispatch_request(self):
256 return str(n)
257
258 app.add_url_rule("/", view_func=CountInit.as_view("index"))
259 assert client.get("/").data == b"1"
260 assert client.get("/").data == b"1"

tests/test_config.py
1 import json
2 import os
3
4 import pytest
5
6 import flask
7
8 # config keys used for the TestConfig
9 TEST_KEY = "foo"
10 SECRET_KEY = "config"
11
12
13 def common_object_test(app):
14 assert app.secret_key == "config"
15 assert app.config["TEST_KEY"] == "foo"
16 assert "TestConfig" not in app.config
17
18
19 def test_config_from_pyfile():
20 app = flask.Flask(__name__)
21 app.config.from_pyfile(f"{__file__.rsplit('.', 1)[0]}.py")
22 common_object_test(app)
23
24
tests/test_config.py 102

25 def test_config_from_object():
26 app = flask.Flask(__name__)
27 app.config.from_object(__name__)
28 common_object_test(app)
29
30
31 def test_config_from_file_json():
32 app = flask.Flask(__name__)
33 current_dir = os.path.dirname(os.path.abspath(__file__))
34 app.config.from_file(os.path.join(current_dir, "static", "config.json"), json.load)
35 common_object_test(app)
36
37
38 def test_config_from_file_toml():
39 tomllib = pytest.importorskip("tomllib", reason="tomllib added in 3.11")
40 app = flask.Flask(__name__)
41 current_dir = os.path.dirname(os.path.abspath(__file__))
42 app.config.from_file(
43 os.path.join(current_dir, "static", "config.toml"), tomllib.load, text=False
44 )
45 common_object_test(app)
46
47
48 def test_from_prefixed_env(monkeypatch):
49 monkeypatch.setenv("FLASK_STRING", "value")
50 monkeypatch.setenv("FLASK_BOOL", "true")
51 monkeypatch.setenv("FLASK_INT", "1")
52 monkeypatch.setenv("FLASK_FLOAT", "1.2")
53 monkeypatch.setenv("FLASK_LIST", "[1, 2]")
54 monkeypatch.setenv("FLASK_DICT", '{"k": "v"}')
55 monkeypatch.setenv("NOT_FLASK_OTHER", "other")
56
57 app = flask.Flask(__name__)
58 app.config.from_prefixed_env()
59
60 assert app.config["STRING"] == "value"
61 assert app.config["BOOL"] is True
62 assert app.config["INT"] == 1
63 assert app.config["FLOAT"] == 1.2
64 assert app.config["LIST"] == [1, 2]
65 assert app.config["DICT"] == {"k": "v"}
66 assert "OTHER" not in app.config
67
68
69 def test_from_prefixed_env_custom_prefix(monkeypatch):
70 monkeypatch.setenv("FLASK_A", "a")
71 monkeypatch.setenv("NOT_FLASK_A", "b")
72
73 app = flask.Flask(__name__)
74 app.config.from_prefixed_env("NOT_FLASK")
75
76 assert app.config["A"] == "b"
77
78
79 def test_from_prefixed_env_nested(monkeypatch):
80 monkeypatch.setenv("FLASK_EXIST__ok", "other")
81 monkeypatch.setenv("FLASK_EXIST__inner__ik", "2")
82 monkeypatch.setenv("FLASK_EXIST__new__more", '{"k": false}')
83 monkeypatch.setenv("FLASK_NEW__K", "v")
84
85 app = flask.Flask(__name__)
86 app.config["EXIST"] = {"ok": "value", "flag": True, "inner": {"ik": 1}}
87 app.config.from_prefixed_env()
88
89 if os.name != "nt":
90 assert app.config["EXIST"] == {
91 "ok": "other",
92 "flag": True,
93 "inner": {"ik": 2},
94 "new": {"more": {"k": False}},
95 }
tests/test_config.py 103

96 else:
97 # Windows env var keys are always uppercase.
98 assert app.config["EXIST"] == {
99 "ok": "value",
100 "OK": "other",
101 "flag": True,
102 "inner": {"ik": 1},
103 "INNER": {"IK": 2},
104 "NEW": {"MORE": {"k": False}},
105 }
106
107 assert app.config["NEW"] == {"K": "v"}
108
109
110 def test_config_from_mapping():
111 app = flask.Flask(__name__)
112 app.config.from_mapping({"SECRET_KEY": "config", "TEST_KEY": "foo"})
113 common_object_test(app)
114
115 app = flask.Flask(__name__)
116 app.config.from_mapping([("SECRET_KEY", "config"), ("TEST_KEY", "foo")])
117 common_object_test(app)
118
119 app = flask.Flask(__name__)
120 app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo")
121 common_object_test(app)
122
123 app = flask.Flask(__name__)
124 app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo", skip_key="skip")
125 common_object_test(app)
126
127 app = flask.Flask(__name__)
128 with pytest.raises(TypeError):
129 app.config.from_mapping({}, {})
130
131
132 def test_config_from_class():
133 class Base:
134 TEST_KEY = "foo"
135
136 class Test(Base):
137 SECRET_KEY = "config"
138
139 app = flask.Flask(__name__)
140 app.config.from_object(Test)
141 common_object_test(app)
142
143
144 def test_config_from_envvar(monkeypatch):
145 monkeypatch.setattr("os.environ", {})
146 app = flask.Flask(__name__)
147
148 with pytest.raises(RuntimeError) as e:
149 app.config.from_envvar("FOO_SETTINGS")
150
151 assert "'FOO_SETTINGS' is not set" in str(e.value)
152 assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
153
154 monkeypatch.setattr(
155 "os.environ", {"FOO_SETTINGS": f"{__file__.rsplit('.', 1)[0]}.py"}
156 )
157 assert app.config.from_envvar("FOO_SETTINGS")
158 common_object_test(app)
159
160
161 def test_config_from_envvar_missing(monkeypatch):
162 monkeypatch.setattr("os.environ", {"FOO_SETTINGS": "missing.cfg"})
163 app = flask.Flask(__name__)
164 with pytest.raises(IOError) as e:
165 app.config.from_envvar("FOO_SETTINGS")
166 msg = str(e.value)
tests/test_config.py 104

167 assert msg.startswith(


168 "[Errno 2] Unable to load configuration file (No such file or directory):"
169 )
170 assert msg.endswith("missing.cfg'")
171 assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
172
173
174 def test_config_missing():
175 app = flask.Flask(__name__)
176 with pytest.raises(IOError) as e:
177 app.config.from_pyfile("missing.cfg")
178 msg = str(e.value)
179 assert msg.startswith(
180 "[Errno 2] Unable to load configuration file (No such file or directory):"
181 )
182 assert msg.endswith("missing.cfg'")
183 assert not app.config.from_pyfile("missing.cfg", silent=True)
184
185
186 def test_config_missing_file():
187 app = flask.Flask(__name__)
188 with pytest.raises(IOError) as e:
189 app.config.from_file("missing.json", load=json.load)
190 msg = str(e.value)
191 assert msg.startswith(
192 "[Errno 2] Unable to load configuration file (No such file or directory):"
193 )
194 assert msg.endswith("missing.json'")
195 assert not app.config.from_file("missing.json", load=json.load, silent=True)
196
197
198 def test_custom_config_class():
199 class Config(flask.Config):
200 pass
201
202 class Flask(flask.Flask):
203 config_class = Config
204
205 app = Flask(__name__)
206 assert isinstance(app.config, Config)
207 app.config.from_object(__name__)
208 common_object_test(app)
209
210
211 def test_session_lifetime():
212 app = flask.Flask(__name__)
213 app.config["PERMANENT_SESSION_LIFETIME"] = 42
214 assert app.permanent_session_lifetime.seconds == 42
215
216
217 def test_get_namespace():
218 app = flask.Flask(__name__)
219 app.config["FOO_OPTION_1"] = "foo option 1"
220 app.config["FOO_OPTION_2"] = "foo option 2"
221 app.config["BAR_STUFF_1"] = "bar stuff 1"
222 app.config["BAR_STUFF_2"] = "bar stuff 2"
223 foo_options = app.config.get_namespace("FOO_")
224 assert 2 == len(foo_options)
225 assert "foo option 1" == foo_options["option_1"]
226 assert "foo option 2" == foo_options["option_2"]
227 bar_options = app.config.get_namespace("BAR_", lowercase=False)
228 assert 2 == len(bar_options)
229 assert "bar stuff 1" == bar_options["STUFF_1"]
230 assert "bar stuff 2" == bar_options["STUFF_2"]
231 foo_options = app.config.get_namespace("FOO_", trim_namespace=False)
232 assert 2 == len(foo_options)
233 assert "foo option 1" == foo_options["foo_option_1"]
234 assert "foo option 2" == foo_options["foo_option_2"]
235 bar_options = app.config.get_namespace(
236 "BAR_", lowercase=False, trim_namespace=False
237 )
105

238 assert 2 == len(bar_options)


239 assert "bar stuff 1" == bar_options["BAR_STUFF_1"]
240 assert "bar stuff 2" == bar_options["BAR_STUFF_2"]
241
242
243 @pytest.mark.parametrize("encoding", ["utf-8", "iso-8859-15", "latin-1"])
244 def test_from_pyfile_weird_encoding(tmp_path, encoding):
245 f = tmp_path / "my_config.py"
246 f.write_text(f'# -*- coding: {encoding} -*-\nTEST_VALUE = "föö"\n', encoding)
247 app = flask.Flask(__name__)
248 app.config.from_pyfile(os.fspath(f))
249 value = app.config["TEST_VALUE"]
250 assert value == "föö"

tests/test_user_error_handler.py
1 import pytest
2 from werkzeug.exceptions import Forbidden
3 from werkzeug.exceptions import HTTPException
4 from werkzeug.exceptions import InternalServerError
5 from werkzeug.exceptions import NotFound
6
7 import flask
8
9
10 def test_error_handler_no_match(app, client):
11 class CustomException(Exception):
12 pass
13
14 @app.errorhandler(CustomException)
15 def custom_exception_handler(e):
16 assert isinstance(e, CustomException)
17 return "custom"
18
19 with pytest.raises(TypeError) as exc_info:
20 app.register_error_handler(CustomException(), None)
21
22 assert "CustomException() is an instance, not a class." in str(exc_info.value)
23
24 with pytest.raises(ValueError) as exc_info:
25 app.register_error_handler(list, None)
26
27 assert "'list' is not a subclass of Exception." in str(exc_info.value)
28
29 @app.errorhandler(500)
30 def handle_500(e):
31 assert isinstance(e, InternalServerError)
32
33 if e.original_exception is not None:
34 return f"wrapped {type(e.original_exception).__name__}"
35
36 return "direct"
37
38 with pytest.raises(ValueError) as exc_info:
39 app.register_error_handler(999, None)
40
41 assert "Use a subclass of HTTPException" in str(exc_info.value)
42
43 @app.route("/custom")
44 def custom_test():
45 raise CustomException()
46
47 @app.route("/keyerror")
48 def key_error():
49 raise KeyError()
50
51 @app.route("/abort")
52 def do_abort():
53 flask.abort(500)
54
tests/test_user_error_handler.py 106

55 app.testing = False
56 assert client.get("/custom").data == b"custom"
57 assert client.get("/keyerror").data == b"wrapped KeyError"
58 assert client.get("/abort").data == b"direct"
59
60
61 def test_error_handler_subclass(app):
62 class ParentException(Exception):
63 pass
64
65 class ChildExceptionUnregistered(ParentException):
66 pass
67
68 class ChildExceptionRegistered(ParentException):
69 pass
70
71 @app.errorhandler(ParentException)
72 def parent_exception_handler(e):
73 assert isinstance(e, ParentException)
74 return "parent"
75
76 @app.errorhandler(ChildExceptionRegistered)
77 def child_exception_handler(e):
78 assert isinstance(e, ChildExceptionRegistered)
79 return "child-registered"
80
81 @app.route("/parent")
82 def parent_test():
83 raise ParentException()
84
85 @app.route("/child-unregistered")
86 def unregistered_test():
87 raise ChildExceptionUnregistered()
88
89 @app.route("/child-registered")
90 def registered_test():
91 raise ChildExceptionRegistered()
92
93 c = app.test_client()
94
95 assert c.get("/parent").data == b"parent"
96 assert c.get("/child-unregistered").data == b"parent"
97 assert c.get("/child-registered").data == b"child-registered"
98
99
100 def test_error_handler_http_subclass(app):
101 class ForbiddenSubclassRegistered(Forbidden):
102 pass
103
104 class ForbiddenSubclassUnregistered(Forbidden):
105 pass
106
107 @app.errorhandler(403)
108 def code_exception_handler(e):
109 assert isinstance(e, Forbidden)
110 return "forbidden"
111
112 @app.errorhandler(ForbiddenSubclassRegistered)
113 def subclass_exception_handler(e):
114 assert isinstance(e, ForbiddenSubclassRegistered)
115 return "forbidden-registered"
116
117 @app.route("/forbidden")
118 def forbidden_test():
119 raise Forbidden()
120
121 @app.route("/forbidden-registered")
122 def registered_test():
123 raise ForbiddenSubclassRegistered()
124
125 @app.route("/forbidden-unregistered")
tests/test_user_error_handler.py 107

126 def unregistered_test():


127 raise ForbiddenSubclassUnregistered()
128
129 c = app.test_client()
130
131 assert c.get("/forbidden").data == b"forbidden"
132 assert c.get("/forbidden-unregistered").data == b"forbidden"
133 assert c.get("/forbidden-registered").data == b"forbidden-registered"
134
135
136 def test_error_handler_blueprint(app):
137 bp = flask.Blueprint("bp", __name__)
138
139 @bp.errorhandler(500)
140 def bp_exception_handler(e):
141 return "bp-error"
142
143 @bp.route("/error")
144 def bp_test():
145 raise InternalServerError()
146
147 @app.errorhandler(500)
148 def app_exception_handler(e):
149 return "app-error"
150
151 @app.route("/error")
152 def app_test():
153 raise InternalServerError()
154
155 app.register_blueprint(bp, url_prefix="/bp")
156
157 c = app.test_client()
158
159 assert c.get("/error").data == b"app-error"
160 assert c.get("/bp/error").data == b"bp-error"
161
162
163 def test_default_error_handler():
164 bp = flask.Blueprint("bp", __name__)
165
166 @bp.errorhandler(HTTPException)
167 def bp_exception_handler(e):
168 assert isinstance(e, HTTPException)
169 assert isinstance(e, NotFound)
170 return "bp-default"
171
172 @bp.errorhandler(Forbidden)
173 def bp_forbidden_handler(e):
174 assert isinstance(e, Forbidden)
175 return "bp-forbidden"
176
177 @bp.route("/undefined")
178 def bp_registered_test():
179 raise NotFound()
180
181 @bp.route("/forbidden")
182 def bp_forbidden_test():
183 raise Forbidden()
184
185 app = flask.Flask(__name__)
186
187 @app.errorhandler(HTTPException)
188 def catchall_exception_handler(e):
189 assert isinstance(e, HTTPException)
190 assert isinstance(e, NotFound)
191 return "default"
192
193 @app.errorhandler(Forbidden)
194 def catchall_forbidden_handler(e):
195 assert isinstance(e, Forbidden)
196 return "forbidden"
tests/test_user_error_handler.py 108

197
198 @app.route("/forbidden")
199 def forbidden():
200 raise Forbidden()
201
202 @app.route("/slash/")
203 def slash():
204 return "slash"
205
206 app.register_blueprint(bp, url_prefix="/bp")
207
208 c = app.test_client()
209 assert c.get("/bp/undefined").data == b"bp-default"
210 assert c.get("/bp/forbidden").data == b"bp-forbidden"
211 assert c.get("/undefined").data == b"default"
212 assert c.get("/forbidden").data == b"forbidden"
213 # Don't handle RequestRedirect raised when adding slash.
214 assert c.get("/slash", follow_redirects=True).data == b"slash"
215
216
217 class TestGenericHandlers:
218 """Test how very generic handlers are dispatched to."""
219
220 class Custom(Exception):
221 pass
222
223 @pytest.fixture()
224 def app(self, app):
225 @app.route("/custom")
226 def do_custom():
227 raise self.Custom()
228
229 @app.route("/error")
230 def do_error():
231 raise KeyError()
232
233 @app.route("/abort")
234 def do_abort():
235 flask.abort(500)
236
237 @app.route("/raise")
238 def do_raise():
239 raise InternalServerError()
240
241 app.config["PROPAGATE_EXCEPTIONS"] = False
242 return app
243
244 def report_error(self, e):
245 original = getattr(e, "original_exception", None)
246
247 if original is not None:
248 return f"wrapped {type(original).__name__}"
249
250 return f"direct {type(e).__name__}"
251
252 @pytest.mark.parametrize("to_handle", (InternalServerError, 500))
253 def test_handle_class_or_code(self, app, client, to_handle):
254 """``InternalServerError`` and ``500`` are aliases, they should
255 have the same behavior. Both should only receive
256 ``InternalServerError``, which might wrap another error.
257 """
258
259 @app.errorhandler(to_handle)
260 def handle_500(e):
261 assert isinstance(e, InternalServerError)
262 return self.report_error(e)
263
264 assert client.get("/custom").data == b"wrapped Custom"
265 assert client.get("/error").data == b"wrapped KeyError"
266 assert client.get("/abort").data == b"direct InternalServerError"
267 assert client.get("/raise").data == b"direct InternalServerError"
109

268
269 def test_handle_generic_http(self, app, client):
270 """``HTTPException`` should only receive ``HTTPException``
271 subclasses. It will receive ``404`` routing exceptions.
272 """
273
274 @app.errorhandler(HTTPException)
275 def handle_http(e):
276 assert isinstance(e, HTTPException)
277 return str(e.code)
278
279 assert client.get("/error").data == b"500"
280 assert client.get("/abort").data == b"500"
281 assert client.get("/not-found").data == b"404"
282
283 def test_handle_generic(self, app, client):
284 """Generic ``Exception`` will handle all exceptions directly,
285 including ``HTTPExceptions``.
286 """
287
288 @app.errorhandler(Exception)
289 def handle_exception(e):
290 return self.report_error(e)
291
292 assert client.get("/custom").data == b"direct Custom"
293 assert client.get("/error").data == b"direct KeyError"
294 assert client.get("/abort").data == b"direct InternalServerError"
295 assert client.get("/not-found").data == b"direct NotFound"

tests/test_reqctx.py
1 import warnings
2
3 import pytest
4
5 import flask
6 from flask.globals import request_ctx
7 from flask.sessions import SecureCookieSessionInterface
8 from flask.sessions import SessionInterface
9
10 try:
11 from greenlet import greenlet
12 except ImportError:
13 greenlet = None
14
15
16 def test_teardown_on_pop(app):
17 buffer = []
18
19 @app.teardown_request
20 def end_of_request(exception):
21 buffer.append(exception)
22
23 ctx = app.test_request_context()
24 ctx.push()
25 assert buffer == []
26 ctx.pop()
27 assert buffer == [None]
28
29
30 def test_teardown_with_previous_exception(app):
31 buffer = []
32
33 @app.teardown_request
34 def end_of_request(exception):
35 buffer.append(exception)
36
37 try:
38 raise Exception("dummy")
39 except Exception:
tests/test_reqctx.py 110

40 pass
41
42 with app.test_request_context():
43 assert buffer == []
44 assert buffer == [None]
45
46
47 def test_teardown_with_handled_exception(app):
48 buffer = []
49
50 @app.teardown_request
51 def end_of_request(exception):
52 buffer.append(exception)
53
54 with app.test_request_context():
55 assert buffer == []
56 try:
57 raise Exception("dummy")
58 except Exception:
59 pass
60 assert buffer == [None]
61
62
63 def test_proper_test_request_context(app):
64 app.config.update(SERVER_NAME="localhost.localdomain:5000")
65
66 @app.route("/")
67 def index():
68 return None
69
70 @app.route("/", subdomain="foo")
71 def sub():
72 return None
73
74 with app.test_request_context("/"):
75 assert (
76 flask.url_for("index", _external=True)
77 == "http://localhost.localdomain:5000/"
78 )
79
80 with app.test_request_context("/"):
81 assert (
82 flask.url_for("sub", _external=True)
83 == "http://foo.localhost.localdomain:5000/"
84 )
85
86 # suppress Werkzeug 0.15 warning about name mismatch
87 with warnings.catch_warnings():
88 warnings.filterwarnings(
89 "ignore", "Current server name", UserWarning, "flask.app"
90 )
91 with app.test_request_context(
92 "/", environ_overrides={"HTTP_HOST": "localhost"}
93 ):
94 pass
95
96 app.config.update(SERVER_NAME="localhost")
97 with app.test_request_context("/", environ_overrides={"SERVER_NAME": "localhost"}):
98 pass
99
100 app.config.update(SERVER_NAME="localhost:80")
101 with app.test_request_context(
102 "/", environ_overrides={"SERVER_NAME": "localhost:80"}
103 ):
104 pass
105
106
107 def test_context_binding(app):
108 @app.route("/")
109 def index():
110 return f"Hello {flask.request.args['name']}!"
tests/test_reqctx.py 111

111
112 @app.route("/meh")
113 def meh():
114 return flask.request.url
115
116 with app.test_request_context("/?name=World"):
117 assert index() == "Hello World!"
118 with app.test_request_context("/meh"):
119 assert meh() == "http://localhost/meh"
120 assert not flask.request
121
122
123 def test_context_test(app):
124 assert not flask.request
125 assert not flask.has_request_context()
126 ctx = app.test_request_context()
127 ctx.push()
128 try:
129 assert flask.request
130 assert flask.has_request_context()
131 finally:
132 ctx.pop()
133
134
135 def test_manual_context_binding(app):
136 @app.route("/")
137 def index():
138 return f"Hello {flask.request.args['name']}!"
139
140 ctx = app.test_request_context("/?name=World")
141 ctx.push()
142 assert index() == "Hello World!"
143 ctx.pop()
144 with pytest.raises(RuntimeError):
145 index()
146
147
148 @pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
149 class TestGreenletContextCopying:
150 def test_greenlet_context_copying(self, app, client):
151 greenlets = []
152
153 @app.route("/")
154 def index():
155 flask.session["fizz"] = "buzz"
156 reqctx = request_ctx.copy()
157
158 def g():
159 assert not flask.request
160 assert not flask.current_app
161 with reqctx:
162 assert flask.request
163 assert flask.current_app == app
164 assert flask.request.path == "/"
165 assert flask.request.args["foo"] == "bar"
166 assert flask.session.get("fizz") == "buzz"
167 assert not flask.request
168 return 42
169
170 greenlets.append(greenlet(g))
171 return "Hello World!"
172
173 rv = client.get("/?foo=bar")
174 assert rv.data == b"Hello World!"
175
176 result = greenlets[0].run()
177 assert result == 42
178
179 def test_greenlet_context_copying_api(self, app, client):
180 greenlets = []
181
tests/test_reqctx.py 112

182 @app.route("/")
183 def index():
184 flask.session["fizz"] = "buzz"
185
186 @flask.copy_current_request_context
187 def g():
188 assert flask.request
189 assert flask.current_app == app
190 assert flask.request.path == "/"
191 assert flask.request.args["foo"] == "bar"
192 assert flask.session.get("fizz") == "buzz"
193 return 42
194
195 greenlets.append(greenlet(g))
196 return "Hello World!"
197
198 rv = client.get("/?foo=bar")
199 assert rv.data == b"Hello World!"
200
201 result = greenlets[0].run()
202 assert result == 42
203
204
205 def test_session_error_pops_context():
206 class SessionError(Exception):
207 pass
208
209 class FailingSessionInterface(SessionInterface):
210 def open_session(self, app, request):
211 raise SessionError()
212
213 class CustomFlask(flask.Flask):
214 session_interface = FailingSessionInterface()
215
216 app = CustomFlask(__name__)
217
218 @app.route("/")
219 def index():
220 # shouldn't get here
221 AssertionError()
222
223 response = app.test_client().get("/")
224 assert response.status_code == 500
225 assert not flask.request
226 assert not flask.current_app
227
228
229 def test_session_dynamic_cookie_name():
230 # This session interface will use a cookie with a different name if the
231 # requested url ends with the string "dynamic_cookie"
232 class PathAwareSessionInterface(SecureCookieSessionInterface):
233 def get_cookie_name(self, app):
234 if flask.request.url.endswith("dynamic_cookie"):
235 return "dynamic_cookie_name"
236 else:
237 return super().get_cookie_name(app)
238
239 class CustomFlask(flask.Flask):
240 session_interface = PathAwareSessionInterface()
241
242 app = CustomFlask(__name__)
243 app.secret_key = "secret_key"
244
245 @app.route("/set", methods=["POST"])
246 def set():
247 flask.session["value"] = flask.request.form["value"]
248 return "value set"
249
250 @app.route("/get")
251 def get():
252 v = flask.session.get("value", "None")
tests/test_reqctx.py 113

253 return v
254
255 @app.route("/set_dynamic_cookie", methods=["POST"])
256 def set_dynamic_cookie():
257 flask.session["value"] = flask.request.form["value"]
258 return "value set"
259
260 @app.route("/get_dynamic_cookie")
261 def get_dynamic_cookie():
262 v = flask.session.get("value", "None")
263 return v
264
265 test_client = app.test_client()
266
267 # first set the cookie in both /set urls but each with a different value
268 assert test_client.post("/set", data={"value": "42"}).data == b"value set"
269 assert (
270 test_client.post("/set_dynamic_cookie", data={"value": "616"}).data
271 == b"value set"
272 )
273
274 # now check that the relevant values come back - meaning that different
275 # cookies are being used for the urls that end with "dynamic cookie"
276 assert test_client.get("/get").data == b"42"
277 assert test_client.get("/get_dynamic_cookie").data == b"616"
278
279
280 def test_bad_environ_raises_bad_request():
281 app = flask.Flask(__name__)
282
283 from flask.testing import EnvironBuilder
284
285 builder = EnvironBuilder(app)
286 environ = builder.get_environ()
287
288 # use a non-printable character in the Host - this is key to this test
289 environ["HTTP_HOST"] = "\x8a"
290
291 with app.request_context(environ):
292 response = app.full_dispatch_request()
293 assert response.status_code == 400
294
295
296 def test_environ_for_valid_idna_completes():
297 app = flask.Flask(__name__)
298
299 @app.route("/")
300 def index():
301 return "Hello World!"
302
303 from flask.testing import EnvironBuilder
304
305 builder = EnvironBuilder(app)
306 environ = builder.get_environ()
307
308 # these characters are all IDNA-compatible
309 environ["HTTP_HOST"] = "ąśźäü�Šß�.com"
310
311 with app.request_context(environ):
312 response = app.full_dispatch_request()
313
314 assert response.status_code == 200
315
316
317 def test_normal_environ_completes():
318 app = flask.Flask(__name__)
319
320 @app.route("/")
321 def index():
322 return "Hello World!"
323
114

324 response = app.test_client().get("/", headers={"host": "xn--on-0ia.com"})


325 assert response.status_code == 200

tests/test_json.py
1 import datetime
2 import decimal
3 import io
4 import uuid
5
6 import pytest
7 from werkzeug.http import http_date
8
9 import flask
10 from flask import json
11 from flask.json.provider import DefaultJSONProvider
12
13
14 @pytest.mark.parametrize("debug", (True, False))
15 def test_bad_request_debug_message(app, client, debug):
16 app.config["DEBUG"] = debug
17 app.config["TRAP_BAD_REQUEST_ERRORS"] = False
18
19 @app.route("/json", methods=["POST"])
20 def post_json():
21 flask.request.get_json()
22 return None
23
24 rv = client.post("/json", data=None, content_type="application/json")
25 assert rv.status_code == 400
26 contains = b"Failed to decode JSON object" in rv.data
27 assert contains == debug
28
29
30 def test_json_bad_requests(app, client):
31 @app.route("/json", methods=["POST"])
32 def return_json():
33 return flask.jsonify(foo=str(flask.request.get_json()))
34
35 rv = client.post("/json", data="malformed", content_type="application/json")
36 assert rv.status_code == 400
37
38
39 def test_json_custom_mimetypes(app, client):
40 @app.route("/json", methods=["POST"])
41 def return_json():
42 return flask.request.get_json()
43
44 rv = client.post("/json", data='"foo"', content_type="application/x+json")
45 assert rv.data == b"foo"
46
47
48 @pytest.mark.parametrize(
49 "test_value,expected", [(True, '"\\u2603"'), (False, '"\u2603"')]
50 )
51 def test_json_as_unicode(test_value, expected, app, app_ctx):
52 app.json.ensure_ascii = test_value
53 rv = app.json.dumps("\N{SNOWMAN}")
54 assert rv == expected
55
56
57 def test_json_dump_to_file(app, app_ctx):
58 test_data = {"name": "Flask"}
59 out = io.StringIO()
60
61 flask.json.dump(test_data, out)
62 out.seek(0)
63 rv = flask.json.load(out)
64 assert rv == test_data
65
tests/test_json.py 115

66
67 @pytest.mark.parametrize(
68 "test_value", [0, -1, 1, 23, 3.14, "s", "longer string", True, False, None]
69 )
70 def test_jsonify_basic_types(test_value, app, client):
71 url = "/jsonify_basic_types"
72 app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x))
73 rv = client.get(url)
74 assert rv.mimetype == "application/json"
75 assert flask.json.loads(rv.data) == test_value
76
77
78 def test_jsonify_dicts(app, client):
79 d = {
80 "a": 0,
81 "b": 23,
82 "c": 3.14,
83 "d": "t",
84 "e": "Hi",
85 "f": True,
86 "g": False,
87 "h": ["test list", 10, False],
88 "i": {"test": "dict"},
89 }
90
91 @app.route("/kw")
92 def return_kwargs():
93 return flask.jsonify(**d)
94
95 @app.route("/dict")
96 def return_dict():
97 return flask.jsonify(d)
98
99 for url in "/kw", "/dict":
100 rv = client.get(url)
101 assert rv.mimetype == "application/json"
102 assert flask.json.loads(rv.data) == d
103
104
105 def test_jsonify_arrays(app, client):
106 """Test jsonify of lists and args unpacking."""
107 a_list = [
108 0,
109 42,
110 3.14,
111 "t",
112 "hello",
113 True,
114 False,
115 ["test list", 2, False],
116 {"test": "dict"},
117 ]
118
119 @app.route("/args_unpack")
120 def return_args_unpack():
121 return flask.jsonify(*a_list)
122
123 @app.route("/array")
124 def return_array():
125 return flask.jsonify(a_list)
126
127 for url in "/args_unpack", "/array":
128 rv = client.get(url)
129 assert rv.mimetype == "application/json"
130 assert flask.json.loads(rv.data) == a_list
131
132
133 @pytest.mark.parametrize(
134 "value", [datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.date(1975, 1, 5)]
135 )
136 def test_jsonify_datetime(app, client, value):
tests/test_json.py 116

137 @app.route("/")
138 def index():
139 return flask.jsonify(value=value)
140
141 r = client.get()
142 assert r.json["value"] == http_date(value)
143
144
145 class FixedOffset(datetime.tzinfo):
146 """Fixed offset in hours east from UTC.
147
148 This is a slight adaptation of the ``FixedOffset`` example found in
149 https://docs.python.org/2.7/library/datetime.html.
150 """
151
152 def __init__(self, hours, name):
153 self.__offset = datetime.timedelta(hours=hours)
154 self.__name = name
155
156 def utcoffset(self, dt):
157 return self.__offset
158
159 def tzname(self, dt):
160 return self.__name
161
162 def dst(self, dt):
163 return datetime.timedelta()
164
165
166 @pytest.mark.parametrize("tz", (("UTC", 0), ("PST", -8), ("KST", 9)))
167 def test_jsonify_aware_datetimes(tz):
168 """Test if aware datetime.datetime objects are converted into GMT."""
169 tzinfo = FixedOffset(hours=tz[1], name=tz[0])
170 dt = datetime.datetime(2017, 1, 1, 12, 34, 56, tzinfo=tzinfo)
171 gmt = FixedOffset(hours=0, name="GMT")
172 expected = dt.astimezone(gmt).strftime('"%a, %d %b %Y %H:%M:%S %Z"')
173 assert flask.json.dumps(dt) == expected
174
175
176 def test_jsonify_uuid_types(app, client):
177 """Test jsonify with uuid.UUID types"""
178
179 test_uuid = uuid.UUID(bytes=b"\xde\xad\xbe\xef" * 4)
180 url = "/uuid_test"
181 app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid))
182
183 rv = client.get(url)
184
185 rv_x = flask.json.loads(rv.data)["x"]
186 assert rv_x == str(test_uuid)
187 rv_uuid = uuid.UUID(rv_x)
188 assert rv_uuid == test_uuid
189
190
191 def test_json_decimal():
192 rv = flask.json.dumps(decimal.Decimal("0.003"))
193 assert rv == '"0.003"'
194
195
196 def test_json_attr(app, client):
197 @app.route("/add", methods=["POST"])
198 def add():
199 json = flask.request.get_json()
200 return str(json["a"] + json["b"])
201
202 rv = client.post(
203 "/add",
204 data=flask.json.dumps({"a": 1, "b": 2}),
205 content_type="application/json",
206 )
207 assert rv.data == b"3"
tests/test_json.py 117

208
209
210 def test_tojson_filter(app, req_ctx):
211 # The tojson filter is tested in Jinja, this confirms that it's
212 # using Flask's dumps.
213 rv = flask.render_template_string(
214 "const data = {{ data|tojson }};",
215 data={"name": "</script>", "time": datetime.datetime(2021, 2, 1, 7, 15)},
216 )
217 assert rv == (
218 'const data = {"name": "\\u003c/script\\u003e",'
219 ' "time": "Mon, 01 Feb 2021 07:15:00 GMT"};'
220 )
221
222
223 def test_json_customization(app, client):
224 class X: # noqa: B903, for Python2 compatibility
225 def __init__(self, val):
226 self.val = val
227
228 def default(o):
229 if isinstance(o, X):
230 return f"<{o.val}>"
231
232 return DefaultJSONProvider.default(o)
233
234 class CustomProvider(DefaultJSONProvider):
235 def object_hook(self, obj):
236 if len(obj) == 1 and "_foo" in obj:
237 return X(obj["_foo"])
238
239 return obj
240
241 def loads(self, s, **kwargs):
242 kwargs.setdefault("object_hook", self.object_hook)
243 return super().loads(s, **kwargs)
244
245 app.json = CustomProvider(app)
246 app.json.default = default
247
248 @app.route("/", methods=["POST"])
249 def index():
250 return flask.json.dumps(flask.request.get_json()["x"])
251
252 rv = client.post(
253 "/",
254 data=flask.json.dumps({"x": {"_foo": 42}}),
255 content_type="application/json",
256 )
257 assert rv.data == b'"<42>"'
258
259
260 def _has_encoding(name):
261 try:
262 import codecs
263
264 codecs.lookup(name)
265 return True
266 except LookupError:
267 return False
268
269
270 def test_json_key_sorting(app, client):
271 app.debug = True
272 assert app.json.sort_keys
273 d = dict.fromkeys(range(20), "foo")
274
275 @app.route("/")
276 def index():
277 return flask.jsonify(values=d)
278
118

279 rv = client.get("/")
280 lines = [x.strip() for x in rv.data.strip().decode("utf-8").splitlines()]
281 sorted_by_str = [
282 "{",
283 '"values": {',
284 '"0": "foo",',
285 '"1": "foo",',
286 '"10": "foo",',
287 '"11": "foo",',
288 '"12": "foo",',
289 '"13": "foo",',
290 '"14": "foo",',
291 '"15": "foo",',
292 '"16": "foo",',
293 '"17": "foo",',
294 '"18": "foo",',
295 '"19": "foo",',
296 '"2": "foo",',
297 '"3": "foo",',
298 '"4": "foo",',
299 '"5": "foo",',
300 '"6": "foo",',
301 '"7": "foo",',
302 '"8": "foo",',
303 '"9": "foo"',
304 "}",
305 "}",
306 ]
307 sorted_by_int = [
308 "{",
309 '"values": {',
310 '"0": "foo",',
311 '"1": "foo",',
312 '"2": "foo",',
313 '"3": "foo",',
314 '"4": "foo",',
315 '"5": "foo",',
316 '"6": "foo",',
317 '"7": "foo",',
318 '"8": "foo",',
319 '"9": "foo",',
320 '"10": "foo",',
321 '"11": "foo",',
322 '"12": "foo",',
323 '"13": "foo",',
324 '"14": "foo",',
325 '"15": "foo",',
326 '"16": "foo",',
327 '"17": "foo",',
328 '"18": "foo",',
329 '"19": "foo"',
330 "}",
331 "}",
332 ]
333
334 try:
335 assert lines == sorted_by_int
336 except AssertionError:
337 assert lines == sorted_by_str
338
339
340 def test_html_method():
341 class ObjectWithHTML:
342 def __html__(self):
343 return "<p>test</p>"
344
345 result = json.dumps(ObjectWithHTML())
346 assert result == '"<p>test</p>"'
tests/test_helpers.py 119

tests/test_helpers.py
1 import io
2 import os
3
4 import pytest
5 import werkzeug.exceptions
6
7 import flask
8 from flask.helpers import get_debug_flag
9
10
11 class FakePath:
12 """Fake object to represent a ``PathLike object``.
13
14 This represents a ``pathlib.Path`` object in python 3.
15 See: https://www.python.org/dev/peps/pep-0519/
16 """
17
18 def __init__(self, path):
19 self.path = path
20
21 def __fspath__(self):
22 return self.path
23
24
25 class PyBytesIO:
26 def __init__(self, *args, **kwargs):
27 self._io = io.BytesIO(*args, **kwargs)
28
29 def __getattr__(self, name):
30 return getattr(self._io, name)
31
32
33 class TestSendfile:
34 def test_send_file(self, app, req_ctx):
35 rv = flask.send_file("static/index.html")
36 assert rv.direct_passthrough
37 assert rv.mimetype == "text/html"
38
39 with app.open_resource("static/index.html") as f:
40 rv.direct_passthrough = False
41 assert rv.data == f.read()
42
43 rv.close()
44
45 def test_static_file(self, app, req_ctx):
46 # Default max_age is None.
47
48 # Test with static file handler.
49 rv = app.send_static_file("index.html")
50 assert rv.cache_control.max_age is None
51 rv.close()
52
53 # Test with direct use of send_file.
54 rv = flask.send_file("static/index.html")
55 assert rv.cache_control.max_age is None
56 rv.close()
57
58 app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 3600
59
60 # Test with static file handler.
61 rv = app.send_static_file("index.html")
62 assert rv.cache_control.max_age == 3600
63 rv.close()
64
65 # Test with direct use of send_file.
66 rv = flask.send_file("static/index.html")
67 assert rv.cache_control.max_age == 3600
68 rv.close()
69
tests/test_helpers.py 120

70 # Test with pathlib.Path.


71 rv = app.send_static_file(FakePath("index.html"))
72 assert rv.cache_control.max_age == 3600
73 rv.close()
74
75 class StaticFileApp(flask.Flask):
76 def get_send_file_max_age(self, filename):
77 return 10
78
79 app = StaticFileApp(__name__)
80
81 with app.test_request_context():
82 # Test with static file handler.
83 rv = app.send_static_file("index.html")
84 assert rv.cache_control.max_age == 10
85 rv.close()
86
87 # Test with direct use of send_file.
88 rv = flask.send_file("static/index.html")
89 assert rv.cache_control.max_age == 10
90 rv.close()
91
92 def test_send_from_directory(self, app, req_ctx):
93 app.root_path = os.path.join(
94 os.path.dirname(__file__), "test_apps", "subdomaintestmodule"
95 )
96 rv = flask.send_from_directory("static", "hello.txt")
97 rv.direct_passthrough = False
98 assert rv.data.strip() == b"Hello Subdomain"
99 rv.close()
100
101
102 class TestUrlFor:
103 def test_url_for_with_anchor(self, app, req_ctx):
104 @app.route("/")
105 def index():
106 return "42"
107
108 assert flask.url_for("index", _anchor="x y") == "/#x%20y"
109
110 def test_url_for_with_scheme(self, app, req_ctx):
111 @app.route("/")
112 def index():
113 return "42"
114
115 assert (
116 flask.url_for("index", _external=True, _scheme="https")
117 == "https://localhost/"
118 )
119
120 def test_url_for_with_scheme_not_external(self, app, req_ctx):
121 app.add_url_rule("/", endpoint="index")
122
123 # Implicit external with scheme.
124 url = flask.url_for("index", _scheme="https")
125 assert url == "https://localhost/"
126
127 # Error when external=False with scheme
128 with pytest.raises(ValueError):
129 flask.url_for("index", _scheme="https", _external=False)
130
131 def test_url_for_with_alternating_schemes(self, app, req_ctx):
132 @app.route("/")
133 def index():
134 return "42"
135
136 assert flask.url_for("index", _external=True) == "http://localhost/"
137 assert (
138 flask.url_for("index", _external=True, _scheme="https")
139 == "https://localhost/"
140 )
tests/test_helpers.py 121

141 assert flask.url_for("index", _external=True) == "http://localhost/"


142
143 def test_url_with_method(self, app, req_ctx):
144 from flask.views import MethodView
145
146 class MyView(MethodView):
147 def get(self, id=None):
148 if id is None:
149 return "List"
150 return f"Get {id:d}"
151
152 def post(self):
153 return "Create"
154
155 myview = MyView.as_view("myview")
156 app.add_url_rule("/myview/", methods=["GET"], view_func=myview)
157 app.add_url_rule("/myview/<int:id>", methods=["GET"], view_func=myview)
158 app.add_url_rule("/myview/create", methods=["POST"], view_func=myview)
159
160 assert flask.url_for("myview", _method="GET") == "/myview/"
161 assert flask.url_for("myview", id=42, _method="GET") == "/myview/42"
162 assert flask.url_for("myview", _method="POST") == "/myview/create"
163
164 def test_url_for_with_self(self, app, req_ctx):
165 @app.route("/<self>")
166 def index(self):
167 return "42"
168
169 assert flask.url_for("index", self="2") == "/2"
170
171
172 def test_redirect_no_app():
173 response = flask.redirect("https://localhost", 307)
174 assert response.location == "https://localhost"
175 assert response.status_code == 307
176
177
178 def test_redirect_with_app(app):
179 def redirect(location, code=302):
180 raise ValueError
181
182 app.redirect = redirect
183
184 with app.app_context(), pytest.raises(ValueError):
185 flask.redirect("other")
186
187
188 def test_abort_no_app():
189 with pytest.raises(werkzeug.exceptions.Unauthorized):
190 flask.abort(401)
191
192 with pytest.raises(LookupError):
193 flask.abort(900)
194
195
196 def test_app_aborter_class():
197 class MyAborter(werkzeug.exceptions.Aborter):
198 pass
199
200 class MyFlask(flask.Flask):
201 aborter_class = MyAborter
202
203 app = MyFlask(__name__)
204 assert isinstance(app.aborter, MyAborter)
205
206
207 def test_abort_with_app(app):
208 class My900Error(werkzeug.exceptions.HTTPException):
209 code = 900
210
211 app.aborter.mapping[900] = My900Error
tests/test_helpers.py 122

212
213 with app.app_context(), pytest.raises(My900Error):
214 flask.abort(900)
215
216
217 class TestNoImports:
218 """Test Flasks are created without import.
219
220 Avoiding ``__import__`` helps create Flask instances where there are errors
221 at import time. Those runtime errors will be apparent to the user soon
222 enough, but tools which build Flask instances meta-programmatically benefit
223 from a Flask which does not ``__import__``. Instead of importing to
224 retrieve file paths or metadata on a module or package, use the pkgutil and
225 imp modules in the Python standard library.
226 """
227
228 def test_name_with_import_error(self, modules_tmp_path):
229 (modules_tmp_path / "importerror.py").write_text("raise NotImplementedError()")
230 try:
231 flask.Flask("importerror")
232 except NotImplementedError:
233 AssertionError("Flask(import_name) is importing import_name.")
234
235
236 class TestStreaming:
237 def test_streaming_with_context(self, app, client):
238 @app.route("/")
239 def index():
240 def generate():
241 yield "Hello "
242 yield flask.request.args["name"]
243 yield "!"
244
245 return flask.Response(flask.stream_with_context(generate()))
246
247 rv = client.get("/?name=World")
248 assert rv.data == b"Hello World!"
249
250 def test_streaming_with_context_as_decorator(self, app, client):
251 @app.route("/")
252 def index():
253 @flask.stream_with_context
254 def generate(hello):
255 yield hello
256 yield flask.request.args["name"]
257 yield "!"
258
259 return flask.Response(generate("Hello "))
260
261 rv = client.get("/?name=World")
262 assert rv.data == b"Hello World!"
263
264 def test_streaming_with_context_and_custom_close(self, app, client):
265 called = []
266
267 class Wrapper:
268 def __init__(self, gen):
269 self._gen = gen
270
271 def __iter__(self):
272 return self
273
274 def close(self):
275 called.append(42)
276
277 def __next__(self):
278 return next(self._gen)
279
280 next = __next__
281
282 @app.route("/")
tests/test_helpers.py 123

283 def index():


284 def generate():
285 yield "Hello "
286 yield flask.request.args["name"]
287 yield "!"
288
289 return flask.Response(flask.stream_with_context(Wrapper(generate())))
290
291 rv = client.get("/?name=World")
292 assert rv.data == b"Hello World!"
293 assert called == [42]
294
295 def test_stream_keeps_session(self, app, client):
296 @app.route("/")
297 def index():
298 flask.session["test"] = "flask"
299
300 @flask.stream_with_context
301 def gen():
302 yield flask.session["test"]
303
304 return flask.Response(gen())
305
306 rv = client.get("/")
307 assert rv.data == b"flask"
308
309
310 class TestHelpers:
311 @pytest.mark.parametrize(
312 ("debug", "expect"),
313 [
314 ("", False),
315 ("0", False),
316 ("False", False),
317 ("No", False),
318 ("True", True),
319 ],
320 )
321 def test_get_debug_flag(self, monkeypatch, debug, expect):
322 monkeypatch.setenv("FLASK_DEBUG", debug)
323 assert get_debug_flag() == expect
324
325 def test_make_response(self):
326 app = flask.Flask(__name__)
327 with app.test_request_context():
328 rv = flask.helpers.make_response()
329 assert rv.status_code == 200
330 assert rv.mimetype == "text/html"
331
332 rv = flask.helpers.make_response("Hello")
333 assert rv.status_code == 200
334 assert rv.data == b"Hello"
335 assert rv.mimetype == "text/html"
336
337
338 @pytest.mark.parametrize("mode", ("r", "rb", "rt"))
339 def test_open_resource(mode):
340 app = flask.Flask(__name__)
341
342 with app.open_resource("static/index.html", mode) as f:
343 assert "<h1>Hello World!</h1>" in str(f.read())
344
345
346 @pytest.mark.parametrize("mode", ("w", "x", "a", "r+"))
347 def test_open_resource_exceptions(mode):
348 app = flask.Flask(__name__)
349
350 with pytest.raises(ValueError):
351 app.open_resource("static/index.html", mode)
352
353
124

354 @pytest.mark.parametrize("encoding", ("utf-8", "utf-16-le"))


355 def test_open_resource_with_encoding(tmp_path, encoding):
356 app = flask.Flask(__name__, root_path=os.fspath(tmp_path))
357 (tmp_path / "test").write_text("test", encoding=encoding)
358
359 with app.open_resource("test", mode="rt", encoding=encoding) as f:
360 assert f.read() == "test"

tests/test_testing.py
1 import importlib.metadata
2
3 import click
4 import pytest
5
6 import flask
7 from flask import appcontext_popped
8 from flask.cli import ScriptInfo
9 from flask.globals import _cv_request
10 from flask.json import jsonify
11 from flask.testing import EnvironBuilder
12 from flask.testing import FlaskCliRunner
13
14
15 def test_environ_defaults_from_config(app, client):
16 app.config["SERVER_NAME"] = "example.com:1234"
17 app.config["APPLICATION_ROOT"] = "/foo"
18
19 @app.route("/")
20 def index():
21 return flask.request.url
22
23 ctx = app.test_request_context()
24 assert ctx.request.url == "http://example.com:1234/foo/"
25
26 rv = client.get("/")
27 assert rv.data == b"http://example.com:1234/foo/"
28
29
30 def test_environ_defaults(app, client, app_ctx, req_ctx):
31 @app.route("/")
32 def index():
33 return flask.request.url
34
35 ctx = app.test_request_context()
36 assert ctx.request.url == "http://localhost/"
37 with client:
38 rv = client.get("/")
39 assert rv.data == b"http://localhost/"
40
41
42 def test_environ_base_default(app, client):
43 @app.route("/")
44 def index():
45 flask.g.remote_addr = flask.request.remote_addr
46 flask.g.user_agent = flask.request.user_agent.string
47 return ""
48
49 with client:
50 client.get("/")
51 assert flask.g.remote_addr == "127.0.0.1"
52 assert flask.g.user_agent == (
53 f"Werkzeug/{importlib.metadata.version('werkzeug')}"
54 )
55
56
57 def test_environ_base_modified(app, client):
58 @app.route("/")
59 def index():
60 flask.g.remote_addr = flask.request.remote_addr
tests/test_testing.py 125

61 flask.g.user_agent = flask.request.user_agent.string
62 return ""
63
64 client.environ_base["REMOTE_ADDR"] = "192.168.0.22"
65 client.environ_base["HTTP_USER_AGENT"] = "Foo"
66
67 with client:
68 client.get("/")
69 assert flask.g.remote_addr == "192.168.0.22"
70 assert flask.g.user_agent == "Foo"
71
72
73 def test_client_open_environ(app, client, request):
74 @app.route("/index")
75 def index():
76 return flask.request.remote_addr
77
78 builder = EnvironBuilder(app, path="/index", method="GET")
79 request.addfinalizer(builder.close)
80
81 rv = client.open(builder)
82 assert rv.data == b"127.0.0.1"
83
84 environ = builder.get_environ()
85 client.environ_base["REMOTE_ADDR"] = "127.0.0.2"
86 rv = client.open(environ)
87 assert rv.data == b"127.0.0.2"
88
89
90 def test_specify_url_scheme(app, client):
91 @app.route("/")
92 def index():
93 return flask.request.url
94
95 ctx = app.test_request_context(url_scheme="https")
96 assert ctx.request.url == "https://localhost/"
97
98 rv = client.get("/", url_scheme="https")
99 assert rv.data == b"https://localhost/"
100
101
102 def test_path_is_url(app):
103 eb = EnvironBuilder(app, "https://example.com/")
104 assert eb.url_scheme == "https"
105 assert eb.host == "example.com"
106 assert eb.script_root == ""
107 assert eb.path == "/"
108
109
110 def test_environbuilder_json_dumps(app):
111 """EnvironBuilder.json_dumps() takes settings from the app."""
112 app.json.ensure_ascii = False
113 eb = EnvironBuilder(app, json="\u20ac")
114 assert eb.input_stream.read().decode("utf8") == '"\u20ac"'
115
116
117 def test_blueprint_with_subdomain():
118 app = flask.Flask(__name__, subdomain_matching=True)
119 app.config["SERVER_NAME"] = "example.com:1234"
120 app.config["APPLICATION_ROOT"] = "/foo"
121 client = app.test_client()
122
123 bp = flask.Blueprint("company", __name__, subdomain="xxx")
124
125 @bp.route("/")
126 def index():
127 return flask.request.url
128
129 app.register_blueprint(bp)
130
131 ctx = app.test_request_context("/", subdomain="xxx")
tests/test_testing.py 126

132 assert ctx.request.url == "http://xxx.example.com:1234/foo/"


133
134 with ctx:
135 assert ctx.request.blueprint == bp.name
136
137 rv = client.get("/", subdomain="xxx")
138 assert rv.data == b"http://xxx.example.com:1234/foo/"
139
140
141 def test_redirect_keep_session(app, client, app_ctx):
142 @app.route("/", methods=["GET", "POST"])
143 def index():
144 if flask.request.method == "POST":
145 return flask.redirect("/getsession")
146 flask.session["data"] = "foo"
147 return "index"
148
149 @app.route("/getsession")
150 def get_session():
151 return flask.session.get("data", "<missing>")
152
153 with client:
154 rv = client.get("/getsession")
155 assert rv.data == b"<missing>"
156
157 rv = client.get("/")
158 assert rv.data == b"index"
159 assert flask.session.get("data") == "foo"
160
161 rv = client.post("/", data={}, follow_redirects=True)
162 assert rv.data == b"foo"
163 assert flask.session.get("data") == "foo"
164
165 rv = client.get("/getsession")
166 assert rv.data == b"foo"
167
168
169 def test_session_transactions(app, client):
170 @app.route("/")
171 def index():
172 return str(flask.session["foo"])
173
174 with client:
175 with client.session_transaction() as sess:
176 assert len(sess) == 0
177 sess["foo"] = [42]
178 assert len(sess) == 1
179 rv = client.get("/")
180 assert rv.data == b"[42]"
181 with client.session_transaction() as sess:
182 assert len(sess) == 1
183 assert sess["foo"] == [42]
184
185
186 def test_session_transactions_no_null_sessions():
187 app = flask.Flask(__name__)
188
189 with app.test_client() as c:
190 with pytest.raises(RuntimeError) as e:
191 with c.session_transaction():
192 pass
193 assert "Session backend did not open a session" in str(e.value)
194
195
196 def test_session_transactions_keep_context(app, client, req_ctx):
197 client.get("/")
198 req = flask.request._get_current_object()
199 assert req is not None
200 with client.session_transaction():
201 assert req is flask.request._get_current_object()
202
tests/test_testing.py 127

203
204 def test_session_transaction_needs_cookies(app):
205 c = app.test_client(use_cookies=False)
206
207 with pytest.raises(TypeError, match="Cookies are disabled."):
208 with c.session_transaction():
209 pass
210
211
212 def test_test_client_context_binding(app, client):
213 app.testing = False
214
215 @app.route("/")
216 def index():
217 flask.g.value = 42
218 return "Hello World!"
219
220 @app.route("/other")
221 def other():
222 raise ZeroDivisionError
223
224 with client:
225 resp = client.get("/")
226 assert flask.g.value == 42
227 assert resp.data == b"Hello World!"
228 assert resp.status_code == 200
229
230 with client:
231 resp = client.get("/other")
232 assert not hasattr(flask.g, "value")
233 assert b"Internal Server Error" in resp.data
234 assert resp.status_code == 500
235 flask.g.value = 23
236
237 with pytest.raises(RuntimeError):
238 flask.g.value # noqa: B018
239
240
241 def test_reuse_client(client):
242 c = client
243
244 with c:
245 assert client.get("/").status_code == 404
246
247 with c:
248 assert client.get("/").status_code == 404
249
250
251 def test_full_url_request(app, client):
252 @app.route("/action", methods=["POST"])
253 def action():
254 return "x"
255
256 with client:
257 rv = client.post("http://domain.com/action?vodka=42", data={"gin": 43})
258 assert rv.status_code == 200
259 assert "gin" in flask.request.form
260 assert "vodka" in flask.request.args
261
262
263 def test_json_request_and_response(app, client):
264 @app.route("/echo", methods=["POST"])
265 def echo():
266 return jsonify(flask.request.get_json())
267
268 with client:
269 json_data = {"drink": {"gin": 1, "tonic": True}, "price": 10}
270 rv = client.post("/echo", json=json_data)
271
272 # Request should be in JSON
273 assert flask.request.is_json
tests/test_testing.py 128

274 assert flask.request.get_json() == json_data


275
276 # Response should be in JSON
277 assert rv.status_code == 200
278 assert rv.is_json
279 assert rv.get_json() == json_data
280
281
282 def test_client_json_no_app_context(app, client):
283 @app.route("/hello", methods=["POST"])
284 def hello():
285 return f"Hello, {flask.request.json['name']}!"
286
287 class Namespace:
288 count = 0
289
290 def add(self, app):
291 self.count += 1
292
293 ns = Namespace()
294
295 with appcontext_popped.connected_to(ns.add, app):
296 rv = client.post("/hello", json={"name": "Flask"})
297
298 assert rv.get_data(as_text=True) == "Hello, Flask!"
299 assert ns.count == 1
300
301
302 def test_subdomain():
303 app = flask.Flask(__name__, subdomain_matching=True)
304 app.config["SERVER_NAME"] = "example.com"
305 client = app.test_client()
306
307 @app.route("/", subdomain="<company_id>")
308 def view(company_id):
309 return company_id
310
311 with app.test_request_context():
312 url = flask.url_for("view", company_id="xxx")
313
314 with client:
315 response = client.get(url)
316
317 assert 200 == response.status_code
318 assert b"xxx" == response.data
319
320
321 def test_nosubdomain(app, client):
322 app.config["SERVER_NAME"] = "example.com"
323
324 @app.route("/<company_id>")
325 def view(company_id):
326 return company_id
327
328 with app.test_request_context():
329 url = flask.url_for("view", company_id="xxx")
330
331 with client:
332 response = client.get(url)
333
334 assert 200 == response.status_code
335 assert b"xxx" == response.data
336
337
338 def test_cli_runner_class(app):
339 runner = app.test_cli_runner()
340 assert isinstance(runner, FlaskCliRunner)
341
342 class SubRunner(FlaskCliRunner):
343 pass
344
129

345 app.test_cli_runner_class = SubRunner


346 runner = app.test_cli_runner()
347 assert isinstance(runner, SubRunner)
348
349
350 def test_cli_invoke(app):
351 @app.cli.command("hello")
352 def hello_command():
353 click.echo("Hello, World!")
354
355 runner = app.test_cli_runner()
356 # invoke with command name
357 result = runner.invoke(args=["hello"])
358 assert "Hello" in result.output
359 # invoke with command object
360 result = runner.invoke(hello_command)
361 assert "Hello" in result.output
362
363
364 def test_cli_custom_obj(app):
365 class NS:
366 called = False
367
368 def create_app():
369 NS.called = True
370 return app
371
372 @app.cli.command("hello")
373 def hello_command():
374 click.echo("Hello, World!")
375
376 script_info = ScriptInfo(create_app=create_app)
377 runner = app.test_cli_runner()
378 runner.invoke(hello_command, obj=script_info)
379 assert NS.called
380
381
382 def test_client_pop_all_preserved(app, req_ctx, client):
383 @app.route("/")
384 def index():
385 # stream_with_context pushes a third context, preserved by response
386 return flask.stream_with_context("hello")
387
388 # req_ctx fixture pushed an initial context
389 with client:
390 # request pushes a second request context, preserved by client
391 rv = client.get("/")
392
393 # close the response, releasing the context held by stream_with_context
394 rv.close()
395 # only req_ctx fixture should still be pushed
396 assert _cv_request.get(None) is req_ctx

tests/test_templating.py
1 import logging
2
3 import pytest
4 import werkzeug.serving
5 from jinja2 import TemplateNotFound
6 from markupsafe import Markup
7
8 import flask
9
10
11 def test_context_processing(app, client):
12 @app.context_processor
13 def context_processor():
14 return {"injected_value": 42}
15
tests/test_templating.py 130

16 @app.route("/")
17 def index():
18 return flask.render_template("context_template.html", value=23)
19
20 rv = client.get("/")
21 assert rv.data == b"<p>23|42"
22
23
24 def test_original_win(app, client):
25 @app.route("/")
26 def index():
27 return flask.render_template_string("{{ config }}", config=42)
28
29 rv = client.get("/")
30 assert rv.data == b"42"
31
32
33 def test_simple_stream(app, client):
34 @app.route("/")
35 def index():
36 return flask.stream_template_string("{{ config }}", config=42)
37
38 rv = client.get("/")
39 assert rv.data == b"42"
40
41
42 def test_request_less_rendering(app, app_ctx):
43 app.config["WORLD_NAME"] = "Special World"
44
45 @app.context_processor
46 def context_processor():
47 return dict(foo=42)
48
49 rv = flask.render_template_string("Hello {{ config.WORLD_NAME }} {{ foo }}")
50 assert rv == "Hello Special World 42"
51
52
53 def test_standard_context(app, client):
54 @app.route("/")
55 def index():
56 flask.g.foo = 23
57 flask.session["test"] = "aha"
58 return flask.render_template_string(
59 """
60 {{ request.args.foo }}
61 {{ g.foo }}
62 {{ config.DEBUG }}
63 {{ session.test }}
64 """
65 )
66
67 rv = client.get("/?foo=42")
68 assert rv.data.split() == [b"42", b"23", b"False", b"aha"]
69
70
71 def test_escaping(app, client):
72 text = "<p>Hello World!"
73
74 @app.route("/")
75 def index():
76 return flask.render_template(
77 "escaping_template.html", text=text, html=Markup(text)
78 )
79
80 lines = client.get("/").data.splitlines()
81 assert lines == [
82 b"&lt;p&gt;Hello World!",
83 b"<p>Hello World!",
84 b"<p>Hello World!",
85 b"<p>Hello World!",
86 b"&lt;p&gt;Hello World!",
tests/test_templating.py 131

87 b"<p>Hello World!",
88 ]
89
90
91 def test_no_escaping(app, client):
92 text = "<p>Hello World!"
93
94 @app.route("/")
95 def index():
96 return flask.render_template(
97 "non_escaping_template.txt", text=text, html=Markup(text)
98 )
99
100 lines = client.get("/").data.splitlines()
101 assert lines == [
102 b"<p>Hello World!",
103 b"<p>Hello World!",
104 b"<p>Hello World!",
105 b"<p>Hello World!",
106 b"&lt;p&gt;Hello World!",
107 b"<p>Hello World!",
108 b"<p>Hello World!",
109 b"<p>Hello World!",
110 ]
111
112
113 def test_escaping_without_template_filename(app, client, req_ctx):
114 assert flask.render_template_string("{{ foo }}", foo="<test>") == "&lt;test&gt;"
115 assert flask.render_template("mail.txt", foo="<test>") == "<test> Mail"
116
117
118 def test_macros(app, req_ctx):
119 macro = flask.get_template_attribute("_macro.html", "hello")
120 assert macro("World") == "Hello World!"
121
122
123 def test_template_filter(app):
124 @app.template_filter()
125 def my_reverse(s):
126 return s[::-1]
127
128 assert "my_reverse" in app.jinja_env.filters.keys()
129 assert app.jinja_env.filters["my_reverse"] == my_reverse
130 assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
131
132
133 def test_add_template_filter(app):
134 def my_reverse(s):
135 return s[::-1]
136
137 app.add_template_filter(my_reverse)
138 assert "my_reverse" in app.jinja_env.filters.keys()
139 assert app.jinja_env.filters["my_reverse"] == my_reverse
140 assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
141
142
143 def test_template_filter_with_name(app):
144 @app.template_filter("strrev")
145 def my_reverse(s):
146 return s[::-1]
147
148 assert "strrev" in app.jinja_env.filters.keys()
149 assert app.jinja_env.filters["strrev"] == my_reverse
150 assert app.jinja_env.filters["strrev"]("abcd") == "dcba"
151
152
153 def test_add_template_filter_with_name(app):
154 def my_reverse(s):
155 return s[::-1]
156
157 app.add_template_filter(my_reverse, "strrev")
tests/test_templating.py 132

158 assert "strrev" in app.jinja_env.filters.keys()


159 assert app.jinja_env.filters["strrev"] == my_reverse
160 assert app.jinja_env.filters["strrev"]("abcd") == "dcba"
161
162
163 def test_template_filter_with_template(app, client):
164 @app.template_filter()
165 def super_reverse(s):
166 return s[::-1]
167
168 @app.route("/")
169 def index():
170 return flask.render_template("template_filter.html", value="abcd")
171
172 rv = client.get("/")
173 assert rv.data == b"dcba"
174
175
176 def test_add_template_filter_with_template(app, client):
177 def super_reverse(s):
178 return s[::-1]
179
180 app.add_template_filter(super_reverse)
181
182 @app.route("/")
183 def index():
184 return flask.render_template("template_filter.html", value="abcd")
185
186 rv = client.get("/")
187 assert rv.data == b"dcba"
188
189
190 def test_template_filter_with_name_and_template(app, client):
191 @app.template_filter("super_reverse")
192 def my_reverse(s):
193 return s[::-1]
194
195 @app.route("/")
196 def index():
197 return flask.render_template("template_filter.html", value="abcd")
198
199 rv = client.get("/")
200 assert rv.data == b"dcba"
201
202
203 def test_add_template_filter_with_name_and_template(app, client):
204 def my_reverse(s):
205 return s[::-1]
206
207 app.add_template_filter(my_reverse, "super_reverse")
208
209 @app.route("/")
210 def index():
211 return flask.render_template("template_filter.html", value="abcd")
212
213 rv = client.get("/")
214 assert rv.data == b"dcba"
215
216
217 def test_template_test(app):
218 @app.template_test()
219 def boolean(value):
220 return isinstance(value, bool)
221
222 assert "boolean" in app.jinja_env.tests.keys()
223 assert app.jinja_env.tests["boolean"] == boolean
224 assert app.jinja_env.tests["boolean"](False)
225
226
227 def test_add_template_test(app):
228 def boolean(value):
tests/test_templating.py 133

229 return isinstance(value, bool)


230
231 app.add_template_test(boolean)
232 assert "boolean" in app.jinja_env.tests.keys()
233 assert app.jinja_env.tests["boolean"] == boolean
234 assert app.jinja_env.tests["boolean"](False)
235
236
237 def test_template_test_with_name(app):
238 @app.template_test("boolean")
239 def is_boolean(value):
240 return isinstance(value, bool)
241
242 assert "boolean" in app.jinja_env.tests.keys()
243 assert app.jinja_env.tests["boolean"] == is_boolean
244 assert app.jinja_env.tests["boolean"](False)
245
246
247 def test_add_template_test_with_name(app):
248 def is_boolean(value):
249 return isinstance(value, bool)
250
251 app.add_template_test(is_boolean, "boolean")
252 assert "boolean" in app.jinja_env.tests.keys()
253 assert app.jinja_env.tests["boolean"] == is_boolean
254 assert app.jinja_env.tests["boolean"](False)
255
256
257 def test_template_test_with_template(app, client):
258 @app.template_test()
259 def boolean(value):
260 return isinstance(value, bool)
261
262 @app.route("/")
263 def index():
264 return flask.render_template("template_test.html", value=False)
265
266 rv = client.get("/")
267 assert b"Success!" in rv.data
268
269
270 def test_add_template_test_with_template(app, client):
271 def boolean(value):
272 return isinstance(value, bool)
273
274 app.add_template_test(boolean)
275
276 @app.route("/")
277 def index():
278 return flask.render_template("template_test.html", value=False)
279
280 rv = client.get("/")
281 assert b"Success!" in rv.data
282
283
284 def test_template_test_with_name_and_template(app, client):
285 @app.template_test("boolean")
286 def is_boolean(value):
287 return isinstance(value, bool)
288
289 @app.route("/")
290 def index():
291 return flask.render_template("template_test.html", value=False)
292
293 rv = client.get("/")
294 assert b"Success!" in rv.data
295
296
297 def test_add_template_test_with_name_and_template(app, client):
298 def is_boolean(value):
299 return isinstance(value, bool)
tests/test_templating.py 134

300
301 app.add_template_test(is_boolean, "boolean")
302
303 @app.route("/")
304 def index():
305 return flask.render_template("template_test.html", value=False)
306
307 rv = client.get("/")
308 assert b"Success!" in rv.data
309
310
311 def test_add_template_global(app, app_ctx):
312 @app.template_global()
313 def get_stuff():
314 return 42
315
316 assert "get_stuff" in app.jinja_env.globals.keys()
317 assert app.jinja_env.globals["get_stuff"] == get_stuff
318 assert app.jinja_env.globals["get_stuff"](), 42
319
320 rv = flask.render_template_string("{{ get_stuff() }}")
321 assert rv == "42"
322
323
324 def test_custom_template_loader(client):
325 class MyFlask(flask.Flask):
326 def create_global_jinja_loader(self):
327 from jinja2 import DictLoader
328
329 return DictLoader({"index.html": "Hello Custom World!"})
330
331 app = MyFlask(__name__)
332
333 @app.route("/")
334 def index():
335 return flask.render_template("index.html")
336
337 c = app.test_client()
338 rv = c.get("/")
339 assert rv.data == b"Hello Custom World!"
340
341
342 def test_iterable_loader(app, client):
343 @app.context_processor
344 def context_processor():
345 return {"whiskey": "Jameson"}
346
347 @app.route("/")
348 def index():
349 return flask.render_template(
350 [
351 "no_template.xml", # should skip this one
352 "simple_template.html", # should render this
353 "context_template.html",
354 ],
355 value=23,
356 )
357
358 rv = client.get("/")
359 assert rv.data == b"<h1>Jameson</h1>"
360
361
362 def test_templates_auto_reload(app):
363 # debug is False, config option is None
364 assert app.debug is False
365 assert app.config["TEMPLATES_AUTO_RELOAD"] is None
366 assert app.jinja_env.auto_reload is False
367 # debug is False, config option is False
368 app = flask.Flask(__name__)
369 app.config["TEMPLATES_AUTO_RELOAD"] = False
370 assert app.debug is False
tests/test_templating.py 135

371 assert app.jinja_env.auto_reload is False


372 # debug is False, config option is True
373 app = flask.Flask(__name__)
374 app.config["TEMPLATES_AUTO_RELOAD"] = True
375 assert app.debug is False
376 assert app.jinja_env.auto_reload is True
377 # debug is True, config option is None
378 app = flask.Flask(__name__)
379 app.config["DEBUG"] = True
380 assert app.config["TEMPLATES_AUTO_RELOAD"] is None
381 assert app.jinja_env.auto_reload is True
382 # debug is True, config option is False
383 app = flask.Flask(__name__)
384 app.config["DEBUG"] = True
385 app.config["TEMPLATES_AUTO_RELOAD"] = False
386 assert app.jinja_env.auto_reload is False
387 # debug is True, config option is True
388 app = flask.Flask(__name__)
389 app.config["DEBUG"] = True
390 app.config["TEMPLATES_AUTO_RELOAD"] = True
391 assert app.jinja_env.auto_reload is True
392
393
394 def test_templates_auto_reload_debug_run(app, monkeypatch):
395 def run_simple_mock(*args, **kwargs):
396 pass
397
398 monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
399
400 app.run()
401 assert not app.jinja_env.auto_reload
402
403 app.run(debug=True)
404 assert app.jinja_env.auto_reload
405
406
407 def test_template_loader_debugging(test_apps, monkeypatch):
408 from blueprintapp import app
409
410 called = []
411
412 class _TestHandler(logging.Handler):
413 def handle(self, record):
414 called.append(True)
415 text = str(record.msg)
416 assert "1: trying loader of application 'blueprintapp'" in text
417 assert (
418 "2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)"
419 ) in text
420 assert (
421 "trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)"
422 ) in text
423 assert "Error: the template could not be found" in text
424 assert (
425 "looked up from an endpoint that belongs to the blueprint 'frontend'"
426 ) in text
427 assert "See https://flask.palletsprojects.com/blueprints/#templates" in text
428
429 with app.test_client() as c:
430 monkeypatch.setitem(app.config, "EXPLAIN_TEMPLATE_LOADING", True)
431 monkeypatch.setattr(
432 logging.getLogger("blueprintapp"), "handlers", [_TestHandler()]
433 )
434
435 with pytest.raises(TemplateNotFound) as excinfo:
436 c.get("/missing")
437
438 assert "missing_template.html" in str(excinfo.value)
439
440 assert len(called) == 1
441
136

442
443 def test_custom_jinja_env():
444 class CustomEnvironment(flask.templating.Environment):
445 pass
446
447 class CustomFlask(flask.Flask):
448 jinja_environment = CustomEnvironment
449
450 app = CustomFlask(__name__)
451 assert isinstance(app.jinja_env, CustomEnvironment)

tests/test_cli.py
1 # This file was part of Flask-CLI and was modified under the terms of
2 # its Revised BSD License. Copyright © 2015 CERN.
3 import importlib.metadata
4 import os
5 import platform
6 import ssl
7 import sys
8 import types
9 from functools import partial
10 from pathlib import Path
11
12 import click
13 import pytest
14 from _pytest.monkeypatch import notset
15 from click.testing import CliRunner
16
17 from flask import Blueprint
18 from flask import current_app
19 from flask import Flask
20 from flask.cli import AppGroup
21 from flask.cli import find_best_app
22 from flask.cli import FlaskGroup
23 from flask.cli import get_version
24 from flask.cli import load_dotenv
25 from flask.cli import locate_app
26 from flask.cli import NoAppException
27 from flask.cli import prepare_import
28 from flask.cli import run_command
29 from flask.cli import ScriptInfo
30 from flask.cli import with_appcontext
31
32 cwd = Path.cwd()
33 test_path = (Path(__file__) / ".." / "test_apps").resolve()
34
35
36 @pytest.fixture
37 def runner():
38 return CliRunner()
39
40
41 def test_cli_name(test_apps):
42 """Make sure the CLI object's name is the app's name and not the app itself"""
43 from cliapp.app import testapp
44
45 assert testapp.cli.name == testapp.name
46
47
48 def test_find_best_app(test_apps):
49 class Module:
50 app = Flask("appname")
51
52 assert find_best_app(Module) == Module.app
53
54 class Module:
55 application = Flask("appname")
56
57 assert find_best_app(Module) == Module.application
tests/test_cli.py 137

58
59 class Module:
60 myapp = Flask("appname")
61
62 assert find_best_app(Module) == Module.myapp
63
64 class Module:
65 @staticmethod
66 def create_app():
67 return Flask("appname")
68
69 app = find_best_app(Module)
70 assert isinstance(app, Flask)
71 assert app.name == "appname"
72
73 class Module:
74 @staticmethod
75 def create_app(**kwargs):
76 return Flask("appname")
77
78 app = find_best_app(Module)
79 assert isinstance(app, Flask)
80 assert app.name == "appname"
81
82 class Module:
83 @staticmethod
84 def make_app():
85 return Flask("appname")
86
87 app = find_best_app(Module)
88 assert isinstance(app, Flask)
89 assert app.name == "appname"
90
91 class Module:
92 myapp = Flask("appname1")
93
94 @staticmethod
95 def create_app():
96 return Flask("appname2")
97
98 assert find_best_app(Module) == Module.myapp
99
100 class Module:
101 myapp = Flask("appname1")
102
103 @staticmethod
104 def create_app():
105 return Flask("appname2")
106
107 assert find_best_app(Module) == Module.myapp
108
109 class Module:
110 pass
111
112 pytest.raises(NoAppException, find_best_app, Module)
113
114 class Module:
115 myapp1 = Flask("appname1")
116 myapp2 = Flask("appname2")
117
118 pytest.raises(NoAppException, find_best_app, Module)
119
120 class Module:
121 @staticmethod
122 def create_app(foo, bar):
123 return Flask("appname2")
124
125 pytest.raises(NoAppException, find_best_app, Module)
126
127 class Module:
128 @staticmethod
tests/test_cli.py 138

129 def create_app():


130 raise TypeError("bad bad factory!")
131
132 pytest.raises(TypeError, find_best_app, Module)
133
134
135 @pytest.mark.parametrize(
136 "value,path,result",
137 (
138 ("test", cwd, "test"),
139 ("test.py", cwd, "test"),
140 ("a/test", cwd / "a", "test"),
141 ("test/__init__.py", cwd, "test"),
142 ("test/__init__", cwd, "test"),
143 # nested package
144 (
145 test_path / "cliapp" / "inner1" / "__init__",
146 test_path,
147 "cliapp.inner1",
148 ),
149 (
150 test_path / "cliapp" / "inner1" / "inner2",
151 test_path,
152 "cliapp.inner1.inner2",
153 ),
154 # dotted name
155 ("test.a.b", cwd, "test.a.b"),
156 (test_path / "cliapp.app", test_path, "cliapp.app"),
157 # not a Python file, will be caught during import
158 (test_path / "cliapp" / "message.txt", test_path, "cliapp.message.txt"),
159 ),
160 )
161 def test_prepare_import(request, value, path, result):
162 """Expect the correct path to be set and the correct import and app names
163 to be returned.
164
165 :func:`prepare_exec_for_file` has a side effect where the parent directory
166 of the given import is added to :data:`sys.path`. This is reset after the
167 test runs.
168 """
169 original_path = sys.path[:]
170
171 def reset_path():
172 sys.path[:] = original_path
173
174 request.addfinalizer(reset_path)
175
176 assert prepare_import(value) == result
177 assert sys.path[0] == str(path)
178
179
180 @pytest.mark.parametrize(
181 "iname,aname,result",
182 (
183 ("cliapp.app", None, "testapp"),
184 ("cliapp.app", "testapp", "testapp"),
185 ("cliapp.factory", None, "app"),
186 ("cliapp.factory", "create_app", "app"),
187 ("cliapp.factory", "create_app()", "app"),
188 ("cliapp.factory", 'create_app2("foo", "bar")', "app2_foo_bar"),
189 # trailing comma space
190 ("cliapp.factory", 'create_app2("foo", "bar", )', "app2_foo_bar"),
191 # strip whitespace
192 ("cliapp.factory", " create_app () ", "app"),
193 ),
194 )
195 def test_locate_app(test_apps, iname, aname, result):
196 assert locate_app(iname, aname).name == result
197
198
199 @pytest.mark.parametrize(
tests/test_cli.py 139

200 "iname,aname",
201 (
202 ("notanapp.py", None),
203 ("cliapp/app", None),
204 ("cliapp.app", "notanapp"),
205 # not enough arguments
206 ("cliapp.factory", 'create_app2("foo")'),
207 # invalid identifier
208 ("cliapp.factory", "create_app("),
209 # no app returned
210 ("cliapp.factory", "no_app"),
211 # nested import error
212 ("cliapp.importerrorapp", None),
213 # not a Python file
214 ("cliapp.message.txt", None),
215 ),
216 )
217 def test_locate_app_raises(test_apps, iname, aname):
218 with pytest.raises(NoAppException):
219 locate_app(iname, aname)
220
221
222 def test_locate_app_suppress_raise(test_apps):
223 app = locate_app("notanapp.py", None, raise_if_not_found=False)
224 assert app is None
225
226 # only direct import error is suppressed
227 with pytest.raises(NoAppException):
228 locate_app("cliapp.importerrorapp", None, raise_if_not_found=False)
229
230
231 def test_get_version(test_apps, capsys):
232 class MockCtx:
233 resilient_parsing = False
234 color = None
235
236 def exit(self):
237 return
238
239 ctx = MockCtx()
240 get_version(ctx, None, "test")
241 out, err = capsys.readouterr()
242 assert f"Python {platform.python_version()}" in out
243 assert f"Flask {importlib.metadata.version('flask')}" in out
244 assert f"Werkzeug {importlib.metadata.version('werkzeug')}" in out
245
246
247 def test_scriptinfo(test_apps, monkeypatch):
248 obj = ScriptInfo(app_import_path="cliapp.app:testapp")
249 app = obj.load_app()
250 assert app.name == "testapp"
251 assert obj.load_app() is app
252
253 # import app with module's absolute path
254 cli_app_path = str(test_path / "cliapp" / "app.py")
255
256 obj = ScriptInfo(app_import_path=cli_app_path)
257 app = obj.load_app()
258 assert app.name == "testapp"
259 assert obj.load_app() is app
260 obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp")
261 app = obj.load_app()
262 assert app.name == "testapp"
263 assert obj.load_app() is app
264
265 def create_app():
266 return Flask("createapp")
267
268 obj = ScriptInfo(create_app=create_app)
269 app = obj.load_app()
270 assert app.name == "createapp"
tests/test_cli.py 140

271 assert obj.load_app() is app


272
273 obj = ScriptInfo()
274 pytest.raises(NoAppException, obj.load_app)
275
276 # import app from wsgi.py in current directory
277 monkeypatch.chdir(test_path / "helloworld")
278 obj = ScriptInfo()
279 app = obj.load_app()
280 assert app.name == "hello"
281
282 # import app from app.py in current directory
283 monkeypatch.chdir(test_path / "cliapp")
284 obj = ScriptInfo()
285 app = obj.load_app()
286 assert app.name == "testapp"
287
288
289 def test_app_cli_has_app_context(app, runner):
290 def _param_cb(ctx, param, value):
291 # current_app should be available in parameter callbacks
292 return bool(current_app)
293
294 @app.cli.command()
295 @click.argument("value", callback=_param_cb)
296 def check(value):
297 app = click.get_current_context().obj.load_app()
298 # the loaded app should be the same as current_app
299 same_app = current_app._get_current_object() is app
300 return same_app, value
301
302 cli = FlaskGroup(create_app=lambda: app)
303 result = runner.invoke(cli, ["check", "x"], standalone_mode=False)
304 assert result.return_value == (True, True)
305
306
307 def test_with_appcontext(runner):
308 @click.command()
309 @with_appcontext
310 def testcmd():
311 click.echo(current_app.name)
312
313 obj = ScriptInfo(create_app=lambda: Flask("testapp"))
314
315 result = runner.invoke(testcmd, obj=obj)
316 assert result.exit_code == 0
317 assert result.output == "testapp\n"
318
319
320 def test_appgroup_app_context(runner):
321 @click.group(cls=AppGroup)
322 def cli():
323 pass
324
325 @cli.command()
326 def test():
327 click.echo(current_app.name)
328
329 @cli.group()
330 def subgroup():
331 pass
332
333 @subgroup.command()
334 def test2():
335 click.echo(current_app.name)
336
337 obj = ScriptInfo(create_app=lambda: Flask("testappgroup"))
338
339 result = runner.invoke(cli, ["test"], obj=obj)
340 assert result.exit_code == 0
341 assert result.output == "testappgroup\n"
tests/test_cli.py 141

342
343 result = runner.invoke(cli, ["subgroup", "test2"], obj=obj)
344 assert result.exit_code == 0
345 assert result.output == "testappgroup\n"
346
347
348 def test_flaskgroup_app_context(runner):
349 def create_app():
350 return Flask("flaskgroup")
351
352 @click.group(cls=FlaskGroup, create_app=create_app)
353 def cli(**params):
354 pass
355
356 @cli.command()
357 def test():
358 click.echo(current_app.name)
359
360 result = runner.invoke(cli, ["test"])
361 assert result.exit_code == 0
362 assert result.output == "flaskgroup\n"
363
364
365 @pytest.mark.parametrize("set_debug_flag", (True, False))
366 def test_flaskgroup_debug(runner, set_debug_flag):
367 def create_app():
368 app = Flask("flaskgroup")
369 app.debug = True
370 return app
371
372 @click.group(cls=FlaskGroup, create_app=create_app, set_debug_flag=set_debug_flag)
373 def cli(**params):
374 pass
375
376 @cli.command()
377 def test():
378 click.echo(str(current_app.debug))
379
380 result = runner.invoke(cli, ["test"])
381 assert result.exit_code == 0
382 assert result.output == f"{not set_debug_flag}\n"
383
384
385 def test_flaskgroup_nested(app, runner):
386 cli = click.Group("cli")
387 flask_group = FlaskGroup(name="flask", create_app=lambda: app)
388 cli.add_command(flask_group)
389
390 @flask_group.command()
391 def show():
392 click.echo(current_app.name)
393
394 result = runner.invoke(cli, ["flask", "show"])
395 assert result.output == "flask_test\n"
396
397
398 def test_no_command_echo_loading_error():
399 from flask.cli import cli
400
401 try:
402 runner = CliRunner(mix_stderr=False)
403 except (DeprecationWarning, TypeError):
404 # Click >= 8.2
405 runner = CliRunner()
406
407 result = runner.invoke(cli, ["missing"])
408 assert result.exit_code == 2
409 assert "FLASK_APP" in result.stderr
410 assert "Usage:" in result.stderr
411
412
tests/test_cli.py 142

413 def test_help_echo_loading_error():


414 from flask.cli import cli
415
416 try:
417 runner = CliRunner(mix_stderr=False)
418 except (DeprecationWarning, TypeError):
419 # Click >= 8.2
420 runner = CliRunner()
421
422 result = runner.invoke(cli, ["--help"])
423 assert result.exit_code == 0
424 assert "FLASK_APP" in result.stderr
425 assert "Usage:" in result.stdout
426
427
428 def test_help_echo_exception():
429 def create_app():
430 raise Exception("oh no")
431
432 cli = FlaskGroup(create_app=create_app)
433
434 try:
435 runner = CliRunner(mix_stderr=False)
436 except (DeprecationWarning, TypeError):
437 # Click >= 8.2
438 runner = CliRunner()
439
440 result = runner.invoke(cli, ["--help"])
441 assert result.exit_code == 0
442 assert "Exception: oh no" in result.stderr
443 assert "Usage:" in result.stdout
444
445
446 class TestRoutes:
447 @pytest.fixture
448 def app(self):
449 app = Flask(__name__)
450 app.add_url_rule(
451 "/get_post/<int:x>/<int:y>",
452 methods=["GET", "POST"],
453 endpoint="yyy_get_post",
454 )
455 app.add_url_rule("/zzz_post", methods=["POST"], endpoint="aaa_post")
456 return app
457
458 @pytest.fixture
459 def invoke(self, app, runner):
460 cli = FlaskGroup(create_app=lambda: app)
461 return partial(runner.invoke, cli)
462
463 def expect_order(self, order, output):
464 # skip the header and match the start of each row
465 for expect, line in zip(order, output.splitlines()[2:]):
466 # do this instead of startswith for nicer pytest output
467 assert line[: len(expect)] == expect
468
469 def test_simple(self, invoke):
470 result = invoke(["routes"])
471 assert result.exit_code == 0
472 self.expect_order(["aaa_post", "static", "yyy_get_post"], result.output)
473
474 def test_sort(self, app, invoke):
475 default_output = invoke(["routes"]).output
476 endpoint_output = invoke(["routes", "-s", "endpoint"]).output
477 assert default_output == endpoint_output
478 self.expect_order(
479 ["static", "yyy_get_post", "aaa_post"],
480 invoke(["routes", "-s", "methods"]).output,
481 )
482 self.expect_order(
483 ["yyy_get_post", "static", "aaa_post"],
tests/test_cli.py 143

484 invoke(["routes", "-s", "rule"]).output,


485 )
486 match_order = [r.endpoint for r in app.url_map.iter_rules()]
487 self.expect_order(match_order, invoke(["routes", "-s", "match"]).output)
488
489 def test_all_methods(self, invoke):
490 output = invoke(["routes"]).output
491 assert "GET, HEAD, OPTIONS, POST" not in output
492 output = invoke(["routes", "--all-methods"]).output
493 assert "GET, HEAD, OPTIONS, POST" in output
494
495 def test_no_routes(self, runner):
496 app = Flask(__name__, static_folder=None)
497 cli = FlaskGroup(create_app=lambda: app)
498 result = runner.invoke(cli, ["routes"])
499 assert result.exit_code == 0
500 assert "No routes were registered." in result.output
501
502 def test_subdomain(self, runner):
503 app = Flask(__name__, static_folder=None)
504 app.add_url_rule("/a", subdomain="a", endpoint="a")
505 app.add_url_rule("/b", subdomain="b", endpoint="b")
506 cli = FlaskGroup(create_app=lambda: app)
507 result = runner.invoke(cli, ["routes"])
508 assert result.exit_code == 0
509 assert "Subdomain" in result.output
510
511 def test_host(self, runner):
512 app = Flask(__name__, static_folder=None, host_matching=True)
513 app.add_url_rule("/a", host="a", endpoint="a")
514 app.add_url_rule("/b", host="b", endpoint="b")
515 cli = FlaskGroup(create_app=lambda: app)
516 result = runner.invoke(cli, ["routes"])
517 assert result.exit_code == 0
518 assert "Host" in result.output
519
520
521 def dotenv_not_available():
522 try:
523 import dotenv # noqa: F401
524 except ImportError:
525 return True
526
527 return False
528
529
530 need_dotenv = pytest.mark.skipif(
531 dotenv_not_available(), reason="dotenv is not installed"
532 )
533
534
535 @need_dotenv
536 def test_load_dotenv(monkeypatch):
537 # can't use monkeypatch.delitem since the keys don't exist yet
538 for item in ("FOO", "BAR", "SPAM", "HAM"):
539 monkeypatch._setitem.append((os.environ, item, notset))
540
541 monkeypatch.setenv("EGGS", "3")
542 monkeypatch.chdir(test_path)
543 assert load_dotenv()
544 assert Path.cwd() == test_path
545 # .flaskenv doesn't overwrite .env
546 assert os.environ["FOO"] == "env"
547 # set only in .flaskenv
548 assert os.environ["BAR"] == "bar"
549 # set only in .env
550 assert os.environ["SPAM"] == "1"
551 # set manually, files don't overwrite
552 assert os.environ["EGGS"] == "3"
553 # test env file encoding
554 assert os.environ["HAM"] == "��"
tests/test_cli.py 144

555 # Non existent file should not load


556 assert not load_dotenv("non-existent-file", load_defaults=False)
557
558
559 @need_dotenv
560 def test_dotenv_path(monkeypatch):
561 for item in ("FOO", "BAR", "EGGS"):
562 monkeypatch._setitem.append((os.environ, item, notset))
563
564 load_dotenv(test_path / ".flaskenv")
565 assert Path.cwd() == cwd
566 assert "FOO" in os.environ
567
568
569 def test_dotenv_optional(monkeypatch):
570 monkeypatch.setitem(sys.modules, "dotenv", None)
571 monkeypatch.chdir(test_path)
572 load_dotenv()
573 assert "FOO" not in os.environ
574
575
576 @need_dotenv
577 def test_disable_dotenv_from_env(monkeypatch, runner):
578 monkeypatch.chdir(test_path)
579 monkeypatch.setitem(os.environ, "FLASK_SKIP_DOTENV", "1")
580 runner.invoke(FlaskGroup())
581 assert "FOO" not in os.environ
582
583
584 def test_run_cert_path():
585 # no key
586 with pytest.raises(click.BadParameter):
587 run_command.make_context("run", ["--cert", __file__])
588
589 # no cert
590 with pytest.raises(click.BadParameter):
591 run_command.make_context("run", ["--key", __file__])
592
593 # cert specified first
594 ctx = run_command.make_context("run", ["--cert", __file__, "--key", __file__])
595 assert ctx.params["cert"] == (__file__, __file__)
596
597 # key specified first
598 ctx = run_command.make_context("run", ["--key", __file__, "--cert", __file__])
599 assert ctx.params["cert"] == (__file__, __file__)
600
601
602 def test_run_cert_adhoc(monkeypatch):
603 monkeypatch.setitem(sys.modules, "cryptography", None)
604
605 # cryptography not installed
606 with pytest.raises(click.BadParameter):
607 run_command.make_context("run", ["--cert", "adhoc"])
608
609 # cryptography installed
610 monkeypatch.setitem(sys.modules, "cryptography", types.ModuleType("cryptography"))
611 ctx = run_command.make_context("run", ["--cert", "adhoc"])
612 assert ctx.params["cert"] == "adhoc"
613
614 # no key with adhoc
615 with pytest.raises(click.BadParameter):
616 run_command.make_context("run", ["--cert", "adhoc", "--key", __file__])
617
618
619 def test_run_cert_import(monkeypatch):
620 monkeypatch.setitem(sys.modules, "not_here", None)
621
622 # ImportError
623 with pytest.raises(click.BadParameter):
624 run_command.make_context("run", ["--cert", "not_here"])
625
tests/test_cli.py 145

626 with pytest.raises(click.BadParameter):


627 run_command.make_context("run", ["--cert", "flask"])
628
629 # SSLContext
630 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
631
632 monkeypatch.setitem(sys.modules, "ssl_context", ssl_context)
633 ctx = run_command.make_context("run", ["--cert", "ssl_context"])
634 assert ctx.params["cert"] is ssl_context
635
636 # no --key with SSLContext
637 with pytest.raises(click.BadParameter):
638 run_command.make_context("run", ["--cert", "ssl_context", "--key", __file__])
639
640
641 def test_run_cert_no_ssl(monkeypatch):
642 monkeypatch.setitem(sys.modules, "ssl", None)
643
644 with pytest.raises(click.BadParameter):
645 run_command.make_context("run", ["--cert", "not_here"])
646
647
648 def test_cli_blueprints(app):
649 """Test blueprint commands register correctly to the application"""
650 custom = Blueprint("custom", __name__, cli_group="customized")
651 nested = Blueprint("nested", __name__)
652 merged = Blueprint("merged", __name__, cli_group=None)
653 late = Blueprint("late", __name__)
654
655 @custom.cli.command("custom")
656 def custom_command():
657 click.echo("custom_result")
658
659 @nested.cli.command("nested")
660 def nested_command():
661 click.echo("nested_result")
662
663 @merged.cli.command("merged")
664 def merged_command():
665 click.echo("merged_result")
666
667 @late.cli.command("late")
668 def late_command():
669 click.echo("late_result")
670
671 app.register_blueprint(custom)
672 app.register_blueprint(nested)
673 app.register_blueprint(merged)
674 app.register_blueprint(late, cli_group="late_registration")
675
676 app_runner = app.test_cli_runner()
677
678 result = app_runner.invoke(args=["customized", "custom"])
679 assert "custom_result" in result.output
680
681 result = app_runner.invoke(args=["nested", "nested"])
682 assert "nested_result" in result.output
683
684 result = app_runner.invoke(args=["merged"])
685 assert "merged_result" in result.output
686
687 result = app_runner.invoke(args=["late_registration", "late"])
688 assert "late_result" in result.output
689
690
691 def test_cli_empty(app):
692 """If a Blueprint's CLI group is empty, do not register it."""
693 bp = Blueprint("blue", __name__, cli_group="blue")
694 app.register_blueprint(bp)
695
696 result = app.test_cli_runner().invoke(args=["blue", "--help"])
146

697 assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}"


698
699
700 def test_run_exclude_patterns():
701 ctx = run_command.make_context("run", ["--exclude-patterns", __file__])
702 assert ctx.params["exclude_patterns"] == [__file__]

tests/test_blueprints.py
1 import pytest
2 from jinja2 import TemplateNotFound
3 from werkzeug.http import parse_cache_control_header
4
5 import flask
6
7
8 def test_blueprint_specific_error_handling(app, client):
9 frontend = flask.Blueprint("frontend", __name__)
10 backend = flask.Blueprint("backend", __name__)
11 sideend = flask.Blueprint("sideend", __name__)
12
13 @frontend.errorhandler(403)
14 def frontend_forbidden(e):
15 return "frontend says no", 403
16
17 @frontend.route("/frontend-no")
18 def frontend_no():
19 flask.abort(403)
20
21 @backend.errorhandler(403)
22 def backend_forbidden(e):
23 return "backend says no", 403
24
25 @backend.route("/backend-no")
26 def backend_no():
27 flask.abort(403)
28
29 @sideend.route("/what-is-a-sideend")
30 def sideend_no():
31 flask.abort(403)
32
33 app.register_blueprint(frontend)
34 app.register_blueprint(backend)
35 app.register_blueprint(sideend)
36
37 @app.errorhandler(403)
38 def app_forbidden(e):
39 return "application itself says no", 403
40
41 assert client.get("/frontend-no").data == b"frontend says no"
42 assert client.get("/backend-no").data == b"backend says no"
43 assert client.get("/what-is-a-sideend").data == b"application itself says no"
44
45
46 def test_blueprint_specific_user_error_handling(app, client):
47 class MyDecoratorException(Exception):
48 pass
49
50 class MyFunctionException(Exception):
51 pass
52
53 blue = flask.Blueprint("blue", __name__)
54
55 @blue.errorhandler(MyDecoratorException)
56 def my_decorator_exception_handler(e):
57 assert isinstance(e, MyDecoratorException)
58 return "boom"
59
60 def my_function_exception_handler(e):
61 assert isinstance(e, MyFunctionException)
tests/test_blueprints.py 147

62 return "bam"
63
64 blue.register_error_handler(MyFunctionException, my_function_exception_handler)
65
66 @blue.route("/decorator")
67 def blue_deco_test():
68 raise MyDecoratorException()
69
70 @blue.route("/function")
71 def blue_func_test():
72 raise MyFunctionException()
73
74 app.register_blueprint(blue)
75
76 assert client.get("/decorator").data == b"boom"
77 assert client.get("/function").data == b"bam"
78
79
80 def test_blueprint_app_error_handling(app, client):
81 errors = flask.Blueprint("errors", __name__)
82
83 @errors.app_errorhandler(403)
84 def forbidden_handler(e):
85 return "you shall not pass", 403
86
87 @app.route("/forbidden")
88 def app_forbidden():
89 flask.abort(403)
90
91 forbidden_bp = flask.Blueprint("forbidden_bp", __name__)
92
93 @forbidden_bp.route("/nope")
94 def bp_forbidden():
95 flask.abort(403)
96
97 app.register_blueprint(errors)
98 app.register_blueprint(forbidden_bp)
99
100 assert client.get("/forbidden").data == b"you shall not pass"
101 assert client.get("/nope").data == b"you shall not pass"
102
103
104 @pytest.mark.parametrize(
105 ("prefix", "rule", "url"),
106 (
107 ("", "/", "/"),
108 ("/", "", "/"),
109 ("/", "/", "/"),
110 ("/foo", "", "/foo"),
111 ("/foo/", "", "/foo/"),
112 ("", "/bar", "/bar"),
113 ("/foo/", "/bar", "/foo/bar"),
114 ("/foo/", "bar", "/foo/bar"),
115 ("/foo", "/bar", "/foo/bar"),
116 ("/foo/", "//bar", "/foo/bar"),
117 ("/foo//", "/bar", "/foo/bar"),
118 ),
119 )
120 def test_blueprint_prefix_slash(app, client, prefix, rule, url):
121 bp = flask.Blueprint("test", __name__, url_prefix=prefix)
122
123 @bp.route(rule)
124 def index():
125 return "", 204
126
127 app.register_blueprint(bp)
128 assert client.get(url).status_code == 204
129
130
131 def test_blueprint_url_defaults(app, client):
132 bp = flask.Blueprint("test", __name__)
tests/test_blueprints.py 148

133
134 @bp.route("/foo", defaults={"baz": 42})
135 def foo(bar, baz):
136 return f"{bar}/{baz:d}"
137
138 @bp.route("/bar")
139 def bar(bar):
140 return str(bar)
141
142 app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
143 app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19})
144
145 assert client.get("/1/foo").data == b"23/42"
146 assert client.get("/2/foo").data == b"19/42"
147 assert client.get("/1/bar").data == b"23"
148 assert client.get("/2/bar").data == b"19"
149
150
151 def test_blueprint_url_processors(app, client):
152 bp = flask.Blueprint("frontend", __name__, url_prefix="/<lang_code>")
153
154 @bp.url_defaults
155 def add_language_code(endpoint, values):
156 values.setdefault("lang_code", flask.g.lang_code)
157
158 @bp.url_value_preprocessor
159 def pull_lang_code(endpoint, values):
160 flask.g.lang_code = values.pop("lang_code")
161
162 @bp.route("/")
163 def index():
164 return flask.url_for(".about")
165
166 @bp.route("/about")
167 def about():
168 return flask.url_for(".index")
169
170 app.register_blueprint(bp)
171
172 assert client.get("/de/").data == b"/de/about"
173 assert client.get("/de/about").data == b"/de/"
174
175
176 def test_templates_and_static(test_apps):
177 from blueprintapp import app
178
179 client = app.test_client()
180
181 rv = client.get("/")
182 assert rv.data == b"Hello from the Frontend"
183 rv = client.get("/admin/")
184 assert rv.data == b"Hello from the Admin"
185 rv = client.get("/admin/index2")
186 assert rv.data == b"Hello from the Admin"
187 rv = client.get("/admin/static/test.txt")
188 assert rv.data.strip() == b"Admin File"
189 rv.close()
190 rv = client.get("/admin/static/css/test.css")
191 assert rv.data.strip() == b"/* nested file */"
192 rv.close()
193
194 # try/finally, in case other tests use this app for Blueprint tests.
195 max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"]
196 try:
197 expected_max_age = 3600
198 if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == expected_max_age:
199 expected_max_age = 7200
200 app.config["SEND_FILE_MAX_AGE_DEFAULT"] = expected_max_age
201 rv = client.get("/admin/static/css/test.css")
202 cc = parse_cache_control_header(rv.headers["Cache-Control"])
203 assert cc.max_age == expected_max_age
tests/test_blueprints.py 149

204 rv.close()
205 finally:
206 app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default
207
208 with app.test_request_context():
209 assert (
210 flask.url_for("admin.static", filename="test.txt")
211 == "/admin/static/test.txt"
212 )
213
214 with app.test_request_context():
215 with pytest.raises(TemplateNotFound) as e:
216 flask.render_template("missing.html")
217 assert e.value.name == "missing.html"
218
219 with flask.Flask(__name__).test_request_context():
220 assert flask.render_template("nested/nested.txt") == "I'm nested"
221
222
223 def test_default_static_max_age(app):
224 class MyBlueprint(flask.Blueprint):
225 def get_send_file_max_age(self, filename):
226 return 100
227
228 blueprint = MyBlueprint("blueprint", __name__, static_folder="static")
229 app.register_blueprint(blueprint)
230
231 # try/finally, in case other tests use this app for Blueprint tests.
232 max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"]
233 try:
234 with app.test_request_context():
235 unexpected_max_age = 3600
236 if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == unexpected_max_age:
237 unexpected_max_age = 7200
238 app.config["SEND_FILE_MAX_AGE_DEFAULT"] = unexpected_max_age
239 rv = blueprint.send_static_file("index.html")
240 cc = parse_cache_control_header(rv.headers["Cache-Control"])
241 assert cc.max_age == 100
242 rv.close()
243 finally:
244 app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default
245
246
247 def test_templates_list(test_apps):
248 from blueprintapp import app
249
250 templates = sorted(app.jinja_env.list_templates())
251 assert templates == ["admin/index.html", "frontend/index.html"]
252
253
254 def test_dotted_name_not_allowed(app, client):
255 with pytest.raises(ValueError):
256 flask.Blueprint("app.ui", __name__)
257
258
259 def test_empty_name_not_allowed(app, client):
260 with pytest.raises(ValueError):
261 flask.Blueprint("", __name__)
262
263
264 def test_dotted_names_from_app(app, client):
265 test = flask.Blueprint("test", __name__)
266
267 @app.route("/")
268 def app_index():
269 return flask.url_for("test.index")
270
271 @test.route("/test/")
272 def index():
273 return flask.url_for("app_index")
274
tests/test_blueprints.py 150

275 app.register_blueprint(test)
276
277 rv = client.get("/")
278 assert rv.data == b"/test/"
279
280
281 def test_empty_url_defaults(app, client):
282 bp = flask.Blueprint("bp", __name__)
283
284 @bp.route("/", defaults={"page": 1})
285 @bp.route("/page/<int:page>")
286 def something(page):
287 return str(page)
288
289 app.register_blueprint(bp)
290
291 assert client.get("/").data == b"1"
292 assert client.get("/page/2").data == b"2"
293
294
295 def test_route_decorator_custom_endpoint(app, client):
296 bp = flask.Blueprint("bp", __name__)
297
298 @bp.route("/foo")
299 def foo():
300 return flask.request.endpoint
301
302 @bp.route("/bar", endpoint="bar")
303 def foo_bar():
304 return flask.request.endpoint
305
306 @bp.route("/bar/123", endpoint="123")
307 def foo_bar_foo():
308 return flask.request.endpoint
309
310 @bp.route("/bar/foo")
311 def bar_foo():
312 return flask.request.endpoint
313
314 app.register_blueprint(bp, url_prefix="/py")
315
316 @app.route("/")
317 def index():
318 return flask.request.endpoint
319
320 assert client.get("/").data == b"index"
321 assert client.get("/py/foo").data == b"bp.foo"
322 assert client.get("/py/bar").data == b"bp.bar"
323 assert client.get("/py/bar/123").data == b"bp.123"
324 assert client.get("/py/bar/foo").data == b"bp.bar_foo"
325
326
327 def test_route_decorator_custom_endpoint_with_dots(app, client):
328 bp = flask.Blueprint("bp", __name__)
329
330 with pytest.raises(ValueError):
331 bp.route("/", endpoint="a.b")(lambda: "")
332
333 with pytest.raises(ValueError):
334 bp.add_url_rule("/", endpoint="a.b")
335
336 def view():
337 return ""
338
339 view.__name__ = "a.b"
340
341 with pytest.raises(ValueError):
342 bp.add_url_rule("/", view_func=view)
343
344
345 def test_endpoint_decorator(app, client):
tests/test_blueprints.py 151

346 from werkzeug.routing import Rule


347
348 app.url_map.add(Rule("/foo", endpoint="bar"))
349
350 bp = flask.Blueprint("bp", __name__)
351
352 @bp.endpoint("bar")
353 def foobar():
354 return flask.request.endpoint
355
356 app.register_blueprint(bp, url_prefix="/bp_prefix")
357
358 assert client.get("/foo").data == b"bar"
359 assert client.get("/bp_prefix/bar").status_code == 404
360
361
362 def test_template_filter(app):
363 bp = flask.Blueprint("bp", __name__)
364
365 @bp.app_template_filter()
366 def my_reverse(s):
367 return s[::-1]
368
369 app.register_blueprint(bp, url_prefix="/py")
370 assert "my_reverse" in app.jinja_env.filters.keys()
371 assert app.jinja_env.filters["my_reverse"] == my_reverse
372 assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
373
374
375 def test_add_template_filter(app):
376 bp = flask.Blueprint("bp", __name__)
377
378 def my_reverse(s):
379 return s[::-1]
380
381 bp.add_app_template_filter(my_reverse)
382 app.register_blueprint(bp, url_prefix="/py")
383 assert "my_reverse" in app.jinja_env.filters.keys()
384 assert app.jinja_env.filters["my_reverse"] == my_reverse
385 assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
386
387
388 def test_template_filter_with_name(app):
389 bp = flask.Blueprint("bp", __name__)
390
391 @bp.app_template_filter("strrev")
392 def my_reverse(s):
393 return s[::-1]
394
395 app.register_blueprint(bp, url_prefix="/py")
396 assert "strrev" in app.jinja_env.filters.keys()
397 assert app.jinja_env.filters["strrev"] == my_reverse
398 assert app.jinja_env.filters["strrev"]("abcd") == "dcba"
399
400
401 def test_add_template_filter_with_name(app):
402 bp = flask.Blueprint("bp", __name__)
403
404 def my_reverse(s):
405 return s[::-1]
406
407 bp.add_app_template_filter(my_reverse, "strrev")
408 app.register_blueprint(bp, url_prefix="/py")
409 assert "strrev" in app.jinja_env.filters.keys()
410 assert app.jinja_env.filters["strrev"] == my_reverse
411 assert app.jinja_env.filters["strrev"]("abcd") == "dcba"
412
413
414 def test_template_filter_with_template(app, client):
415 bp = flask.Blueprint("bp", __name__)
416
tests/test_blueprints.py 152

417 @bp.app_template_filter()
418 def super_reverse(s):
419 return s[::-1]
420
421 app.register_blueprint(bp, url_prefix="/py")
422
423 @app.route("/")
424 def index():
425 return flask.render_template("template_filter.html", value="abcd")
426
427 rv = client.get("/")
428 assert rv.data == b"dcba"
429
430
431 def test_template_filter_after_route_with_template(app, client):
432 @app.route("/")
433 def index():
434 return flask.render_template("template_filter.html", value="abcd")
435
436 bp = flask.Blueprint("bp", __name__)
437
438 @bp.app_template_filter()
439 def super_reverse(s):
440 return s[::-1]
441
442 app.register_blueprint(bp, url_prefix="/py")
443 rv = client.get("/")
444 assert rv.data == b"dcba"
445
446
447 def test_add_template_filter_with_template(app, client):
448 bp = flask.Blueprint("bp", __name__)
449
450 def super_reverse(s):
451 return s[::-1]
452
453 bp.add_app_template_filter(super_reverse)
454 app.register_blueprint(bp, url_prefix="/py")
455
456 @app.route("/")
457 def index():
458 return flask.render_template("template_filter.html", value="abcd")
459
460 rv = client.get("/")
461 assert rv.data == b"dcba"
462
463
464 def test_template_filter_with_name_and_template(app, client):
465 bp = flask.Blueprint("bp", __name__)
466
467 @bp.app_template_filter("super_reverse")
468 def my_reverse(s):
469 return s[::-1]
470
471 app.register_blueprint(bp, url_prefix="/py")
472
473 @app.route("/")
474 def index():
475 return flask.render_template("template_filter.html", value="abcd")
476
477 rv = client.get("/")
478 assert rv.data == b"dcba"
479
480
481 def test_add_template_filter_with_name_and_template(app, client):
482 bp = flask.Blueprint("bp", __name__)
483
484 def my_reverse(s):
485 return s[::-1]
486
487 bp.add_app_template_filter(my_reverse, "super_reverse")
tests/test_blueprints.py 153

488 app.register_blueprint(bp, url_prefix="/py")


489
490 @app.route("/")
491 def index():
492 return flask.render_template("template_filter.html", value="abcd")
493
494 rv = client.get("/")
495 assert rv.data == b"dcba"
496
497
498 def test_template_test(app):
499 bp = flask.Blueprint("bp", __name__)
500
501 @bp.app_template_test()
502 def is_boolean(value):
503 return isinstance(value, bool)
504
505 app.register_blueprint(bp, url_prefix="/py")
506 assert "is_boolean" in app.jinja_env.tests.keys()
507 assert app.jinja_env.tests["is_boolean"] == is_boolean
508 assert app.jinja_env.tests["is_boolean"](False)
509
510
511 def test_add_template_test(app):
512 bp = flask.Blueprint("bp", __name__)
513
514 def is_boolean(value):
515 return isinstance(value, bool)
516
517 bp.add_app_template_test(is_boolean)
518 app.register_blueprint(bp, url_prefix="/py")
519 assert "is_boolean" in app.jinja_env.tests.keys()
520 assert app.jinja_env.tests["is_boolean"] == is_boolean
521 assert app.jinja_env.tests["is_boolean"](False)
522
523
524 def test_template_test_with_name(app):
525 bp = flask.Blueprint("bp", __name__)
526
527 @bp.app_template_test("boolean")
528 def is_boolean(value):
529 return isinstance(value, bool)
530
531 app.register_blueprint(bp, url_prefix="/py")
532 assert "boolean" in app.jinja_env.tests.keys()
533 assert app.jinja_env.tests["boolean"] == is_boolean
534 assert app.jinja_env.tests["boolean"](False)
535
536
537 def test_add_template_test_with_name(app):
538 bp = flask.Blueprint("bp", __name__)
539
540 def is_boolean(value):
541 return isinstance(value, bool)
542
543 bp.add_app_template_test(is_boolean, "boolean")
544 app.register_blueprint(bp, url_prefix="/py")
545 assert "boolean" in app.jinja_env.tests.keys()
546 assert app.jinja_env.tests["boolean"] == is_boolean
547 assert app.jinja_env.tests["boolean"](False)
548
549
550 def test_template_test_with_template(app, client):
551 bp = flask.Blueprint("bp", __name__)
552
553 @bp.app_template_test()
554 def boolean(value):
555 return isinstance(value, bool)
556
557 app.register_blueprint(bp, url_prefix="/py")
558
tests/test_blueprints.py 154

559 @app.route("/")
560 def index():
561 return flask.render_template("template_test.html", value=False)
562
563 rv = client.get("/")
564 assert b"Success!" in rv.data
565
566
567 def test_template_test_after_route_with_template(app, client):
568 @app.route("/")
569 def index():
570 return flask.render_template("template_test.html", value=False)
571
572 bp = flask.Blueprint("bp", __name__)
573
574 @bp.app_template_test()
575 def boolean(value):
576 return isinstance(value, bool)
577
578 app.register_blueprint(bp, url_prefix="/py")
579 rv = client.get("/")
580 assert b"Success!" in rv.data
581
582
583 def test_add_template_test_with_template(app, client):
584 bp = flask.Blueprint("bp", __name__)
585
586 def boolean(value):
587 return isinstance(value, bool)
588
589 bp.add_app_template_test(boolean)
590 app.register_blueprint(bp, url_prefix="/py")
591
592 @app.route("/")
593 def index():
594 return flask.render_template("template_test.html", value=False)
595
596 rv = client.get("/")
597 assert b"Success!" in rv.data
598
599
600 def test_template_test_with_name_and_template(app, client):
601 bp = flask.Blueprint("bp", __name__)
602
603 @bp.app_template_test("boolean")
604 def is_boolean(value):
605 return isinstance(value, bool)
606
607 app.register_blueprint(bp, url_prefix="/py")
608
609 @app.route("/")
610 def index():
611 return flask.render_template("template_test.html", value=False)
612
613 rv = client.get("/")
614 assert b"Success!" in rv.data
615
616
617 def test_add_template_test_with_name_and_template(app, client):
618 bp = flask.Blueprint("bp", __name__)
619
620 def is_boolean(value):
621 return isinstance(value, bool)
622
623 bp.add_app_template_test(is_boolean, "boolean")
624 app.register_blueprint(bp, url_prefix="/py")
625
626 @app.route("/")
627 def index():
628 return flask.render_template("template_test.html", value=False)
629
tests/test_blueprints.py 155

630 rv = client.get("/")
631 assert b"Success!" in rv.data
632
633
634 def test_context_processing(app, client):
635 answer_bp = flask.Blueprint("answer_bp", __name__)
636
637 def template_string():
638 return flask.render_template_string(
639 "{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}"
640 "{% if answer %}{{ answer }} is the answer.{% endif %}"
641 )
642
643 # App global context processor
644 @answer_bp.app_context_processor
645 def not_answer_context_processor():
646 return {"notanswer": 43}
647
648 # Blueprint local context processor
649 @answer_bp.context_processor
650 def answer_context_processor():
651 return {"answer": 42}
652
653 # Setup endpoints for testing
654 @answer_bp.route("/bp")
655 def bp_page():
656 return template_string()
657
658 @app.route("/")
659 def app_page():
660 return template_string()
661
662 # Register the blueprint
663 app.register_blueprint(answer_bp)
664
665 app_page_bytes = client.get("/").data
666 answer_page_bytes = client.get("/bp").data
667
668 assert b"43" in app_page_bytes
669 assert b"42" not in app_page_bytes
670
671 assert b"42" in answer_page_bytes
672 assert b"43" in answer_page_bytes
673
674
675 def test_template_global(app):
676 bp = flask.Blueprint("bp", __name__)
677
678 @bp.app_template_global()
679 def get_answer():
680 return 42
681
682 # Make sure the function is not in the jinja_env already
683 assert "get_answer" not in app.jinja_env.globals.keys()
684 app.register_blueprint(bp)
685
686 # Tests
687 assert "get_answer" in app.jinja_env.globals.keys()
688 assert app.jinja_env.globals["get_answer"] is get_answer
689 assert app.jinja_env.globals["get_answer"]() == 42
690
691 with app.app_context():
692 rv = flask.render_template_string("{{ get_answer() }}")
693 assert rv == "42"
694
695
696 def test_request_processing(app, client):
697 bp = flask.Blueprint("bp", __name__)
698 evts = []
699
700 @bp.before_request
tests/test_blueprints.py 156

701 def before_bp():


702 evts.append("before")
703
704 @bp.after_request
705 def after_bp(response):
706 response.data += b"|after"
707 evts.append("after")
708 return response
709
710 @bp.teardown_request
711 def teardown_bp(exc):
712 evts.append("teardown")
713
714 # Setup routes for testing
715 @bp.route("/bp")
716 def bp_endpoint():
717 return "request"
718
719 app.register_blueprint(bp)
720
721 assert evts == []
722 rv = client.get("/bp")
723 assert rv.data == b"request|after"
724 assert evts == ["before", "after", "teardown"]
725
726
727 def test_app_request_processing(app, client):
728 bp = flask.Blueprint("bp", __name__)
729 evts = []
730
731 @bp.before_app_request
732 def before_app():
733 evts.append("before")
734
735 @bp.after_app_request
736 def after_app(response):
737 response.data += b"|after"
738 evts.append("after")
739 return response
740
741 @bp.teardown_app_request
742 def teardown_app(exc):
743 evts.append("teardown")
744
745 app.register_blueprint(bp)
746
747 # Setup routes for testing
748 @app.route("/")
749 def bp_endpoint():
750 return "request"
751
752 # before first request
753 assert evts == []
754
755 # first request
756 resp = client.get("/").data
757 assert resp == b"request|after"
758 assert evts == ["before", "after", "teardown"]
759
760 # second request
761 resp = client.get("/").data
762 assert resp == b"request|after"
763 assert evts == ["before", "after", "teardown"] * 2
764
765
766 def test_app_url_processors(app, client):
767 bp = flask.Blueprint("bp", __name__)
768
769 # Register app-wide url defaults and preprocessor on blueprint
770 @bp.app_url_defaults
771 def add_language_code(endpoint, values):
tests/test_blueprints.py 157

772 values.setdefault("lang_code", flask.g.lang_code)


773
774 @bp.app_url_value_preprocessor
775 def pull_lang_code(endpoint, values):
776 flask.g.lang_code = values.pop("lang_code")
777
778 # Register route rules at the app level
779 @app.route("/<lang_code>/")
780 def index():
781 return flask.url_for("about")
782
783 @app.route("/<lang_code>/about")
784 def about():
785 return flask.url_for("index")
786
787 app.register_blueprint(bp)
788
789 assert client.get("/de/").data == b"/de/about"
790 assert client.get("/de/about").data == b"/de/"
791
792
793 def test_nested_blueprint(app, client):
794 parent = flask.Blueprint("parent", __name__)
795 child = flask.Blueprint("child", __name__)
796 grandchild = flask.Blueprint("grandchild", __name__)
797
798 @parent.errorhandler(403)
799 def forbidden(e):
800 return "Parent no", 403
801
802 @parent.route("/")
803 def parent_index():
804 return "Parent yes"
805
806 @parent.route("/no")
807 def parent_no():
808 flask.abort(403)
809
810 @child.route("/")
811 def child_index():
812 return "Child yes"
813
814 @child.route("/no")
815 def child_no():
816 flask.abort(403)
817
818 @grandchild.errorhandler(403)
819 def grandchild_forbidden(e):
820 return "Grandchild no", 403
821
822 @grandchild.route("/")
823 def grandchild_index():
824 return "Grandchild yes"
825
826 @grandchild.route("/no")
827 def grandchild_no():
828 flask.abort(403)
829
830 child.register_blueprint(grandchild, url_prefix="/grandchild")
831 parent.register_blueprint(child, url_prefix="/child")
832 app.register_blueprint(parent, url_prefix="/parent")
833
834 assert client.get("/parent/").data == b"Parent yes"
835 assert client.get("/parent/child/").data == b"Child yes"
836 assert client.get("/parent/child/grandchild/").data == b"Grandchild yes"
837 assert client.get("/parent/no").data == b"Parent no"
838 assert client.get("/parent/child/no").data == b"Parent no"
839 assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
840
841
842 def test_nested_callback_order(app, client):
tests/test_blueprints.py 158

843 parent = flask.Blueprint("parent", __name__)


844 child = flask.Blueprint("child", __name__)
845
846 @app.before_request
847 def app_before1():
848 flask.g.setdefault("seen", []).append("app_1")
849
850 @app.teardown_request
851 def app_teardown1(e=None):
852 assert flask.g.seen.pop() == "app_1"
853
854 @app.before_request
855 def app_before2():
856 flask.g.setdefault("seen", []).append("app_2")
857
858 @app.teardown_request
859 def app_teardown2(e=None):
860 assert flask.g.seen.pop() == "app_2"
861
862 @app.context_processor
863 def app_ctx():
864 return dict(key="app")
865
866 @parent.before_request
867 def parent_before1():
868 flask.g.setdefault("seen", []).append("parent_1")
869
870 @parent.teardown_request
871 def parent_teardown1(e=None):
872 assert flask.g.seen.pop() == "parent_1"
873
874 @parent.before_request
875 def parent_before2():
876 flask.g.setdefault("seen", []).append("parent_2")
877
878 @parent.teardown_request
879 def parent_teardown2(e=None):
880 assert flask.g.seen.pop() == "parent_2"
881
882 @parent.context_processor
883 def parent_ctx():
884 return dict(key="parent")
885
886 @child.before_request
887 def child_before1():
888 flask.g.setdefault("seen", []).append("child_1")
889
890 @child.teardown_request
891 def child_teardown1(e=None):
892 assert flask.g.seen.pop() == "child_1"
893
894 @child.before_request
895 def child_before2():
896 flask.g.setdefault("seen", []).append("child_2")
897
898 @child.teardown_request
899 def child_teardown2(e=None):
900 assert flask.g.seen.pop() == "child_2"
901
902 @child.context_processor
903 def child_ctx():
904 return dict(key="child")
905
906 @child.route("/a")
907 def a():
908 return ", ".join(flask.g.seen)
909
910 @child.route("/b")
911 def b():
912 return flask.render_template_string("{{ key }}")
913
tests/test_blueprints.py 159

914 parent.register_blueprint(child)
915 app.register_blueprint(parent)
916 assert (
917 client.get("/a").data == b"app_1, app_2, parent_1, parent_2, child_1, child_2"
918 )
919 assert client.get("/b").data == b"child"
920
921
922 @pytest.mark.parametrize(
923 "parent_init, child_init, parent_registration, child_registration",
924 [
925 ("/parent", "/child", None, None),
926 ("/parent", None, None, "/child"),
927 (None, None, "/parent", "/child"),
928 ("/other", "/something", "/parent", "/child"),
929 ],
930 )
931 def test_nesting_url_prefixes(
932 parent_init,
933 child_init,
934 parent_registration,
935 child_registration,
936 app,
937 client,
938 ) -> None:
939 parent = flask.Blueprint("parent", __name__, url_prefix=parent_init)
940 child = flask.Blueprint("child", __name__, url_prefix=child_init)
941
942 @child.route("/")
943 def index():
944 return "index"
945
946 parent.register_blueprint(child, url_prefix=child_registration)
947 app.register_blueprint(parent, url_prefix=parent_registration)
948
949 response = client.get("/parent/child/")
950 assert response.status_code == 200
951
952
953 def test_nesting_subdomains(app, client) -> None:
954 app.subdomain_matching = True
955 app.config["SERVER_NAME"] = "example.test"
956 client.allow_subdomain_redirects = True
957
958 parent = flask.Blueprint("parent", __name__)
959 child = flask.Blueprint("child", __name__)
960
961 @child.route("/child/")
962 def index():
963 return "child"
964
965 parent.register_blueprint(child)
966 app.register_blueprint(parent, subdomain="api")
967
968 response = client.get("/child/", base_url="http://api.example.test")
969 assert response.status_code == 200
970
971
972 def test_child_and_parent_subdomain(app, client) -> None:
973 app.subdomain_matching = True
974 app.config["SERVER_NAME"] = "example.test"
975 client.allow_subdomain_redirects = True
976
977 parent = flask.Blueprint("parent", __name__)
978 child = flask.Blueprint("child", __name__, subdomain="api")
979
980 @child.route("/")
981 def index():
982 return "child"
983
984 parent.register_blueprint(child)
160

985 app.register_blueprint(parent, subdomain="parent")


986
987 response = client.get("/", base_url="http://api.parent.example.test")
988 assert response.status_code == 200
989
990 response = client.get("/", base_url="http://parent.example.test")
991 assert response.status_code == 404
992
993
994 def test_unique_blueprint_names(app, client) -> None:
995 bp = flask.Blueprint("bp", __name__)
996 bp2 = flask.Blueprint("bp", __name__)
997
998 app.register_blueprint(bp)
999
1000 with pytest.raises(ValueError):
1001 app.register_blueprint(bp) # same bp, same name, error
1002
1003 app.register_blueprint(bp, name="again") # same bp, different name, ok
1004
1005 with pytest.raises(ValueError):
1006 app.register_blueprint(bp2) # different bp, same name, error
1007
1008 app.register_blueprint(bp2, name="alt") # different bp, different name, ok
1009
1010
1011 def test_self_registration(app, client) -> None:
1012 bp = flask.Blueprint("bp", __name__)
1013 with pytest.raises(ValueError):
1014 bp.register_blueprint(bp)
1015
1016
1017 def test_blueprint_renaming(app, client) -> None:
1018 bp = flask.Blueprint("bp", __name__)
1019 bp2 = flask.Blueprint("bp2", __name__)
1020
1021 @bp.get("/")
1022 def index():
1023 return flask.request.endpoint
1024
1025 @bp.get("/error")
1026 def error():
1027 flask.abort(403)
1028
1029 @bp.errorhandler(403)
1030 def forbidden(_: Exception):
1031 return "Error", 403
1032
1033 @bp2.get("/")
1034 def index2():
1035 return flask.request.endpoint
1036
1037 bp.register_blueprint(bp2, url_prefix="/a", name="sub")
1038 app.register_blueprint(bp, url_prefix="/a")
1039 app.register_blueprint(bp, url_prefix="/b", name="alt")
1040
1041 assert client.get("/a/").data == b"bp.index"
1042 assert client.get("/b/").data == b"alt.index"
1043 assert client.get("/a/a/").data == b"bp.sub.index2"
1044 assert client.get("/b/a/").data == b"alt.sub.index2"
1045 assert client.get("/a/error").data == b"Error"
1046 assert client.get("/b/error").data == b"Error"

tests/test_basic.py
1 import gc
2 import re
3 import typing as t
4 import uuid
5 import warnings
tests/test_basic.py 161

6 import weakref
7 from contextlib import nullcontext
8 from datetime import datetime
9 from datetime import timezone
10 from platform import python_implementation
11
12 import pytest
13 import werkzeug.serving
14 from markupsafe import Markup
15 from werkzeug.exceptions import BadRequest
16 from werkzeug.exceptions import Forbidden
17 from werkzeug.exceptions import NotFound
18 from werkzeug.http import parse_date
19 from werkzeug.routing import BuildError
20 from werkzeug.routing import RequestRedirect
21
22 import flask
23
24 require_cpython_gc = pytest.mark.skipif(
25 python_implementation() != "CPython",
26 reason="Requires CPython GC behavior",
27 )
28
29
30 def test_options_work(app, client):
31 @app.route("/", methods=["GET", "POST"])
32 def index():
33 return "Hello World"
34
35 rv = client.open("/", method="OPTIONS")
36 assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"]
37 assert rv.data == b""
38
39
40 def test_options_on_multiple_rules(app, client):
41 @app.route("/", methods=["GET", "POST"])
42 def index():
43 return "Hello World"
44
45 @app.route("/", methods=["PUT"])
46 def index_put():
47 return "Aha!"
48
49 rv = client.open("/", method="OPTIONS")
50 assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST", "PUT"]
51
52
53 @pytest.mark.parametrize("method", ["get", "post", "put", "delete", "patch"])
54 def test_method_route(app, client, method):
55 method_route = getattr(app, method)
56 client_method = getattr(client, method)
57
58 @method_route("/")
59 def hello():
60 return "Hello"
61
62 assert client_method("/").data == b"Hello"
63
64
65 def test_method_route_no_methods(app):
66 with pytest.raises(TypeError):
67 app.get("/", methods=["GET", "POST"])
68
69
70 def test_provide_automatic_options_attr():
71 app = flask.Flask(__name__)
72
73 def index():
74 return "Hello World!"
75
76 index.provide_automatic_options = False
tests/test_basic.py 162

77 app.route("/")(index)
78 rv = app.test_client().open("/", method="OPTIONS")
79 assert rv.status_code == 405
80
81 app = flask.Flask(__name__)
82
83 def index2():
84 return "Hello World!"
85
86 index2.provide_automatic_options = True
87 app.route("/", methods=["OPTIONS"])(index2)
88 rv = app.test_client().open("/", method="OPTIONS")
89 assert sorted(rv.allow) == ["OPTIONS"]
90
91
92 def test_provide_automatic_options_kwarg(app, client):
93 def index():
94 return flask.request.method
95
96 def more():
97 return flask.request.method
98
99 app.add_url_rule("/", view_func=index, provide_automatic_options=False)
100 app.add_url_rule(
101 "/more",
102 view_func=more,
103 methods=["GET", "POST"],
104 provide_automatic_options=False,
105 )
106 assert client.get("/").data == b"GET"
107
108 rv = client.post("/")
109 assert rv.status_code == 405
110 assert sorted(rv.allow) == ["GET", "HEAD"]
111
112 rv = client.open("/", method="OPTIONS")
113 assert rv.status_code == 405
114
115 rv = client.head("/")
116 assert rv.status_code == 200
117 assert not rv.data # head truncates
118 assert client.post("/more").data == b"POST"
119 assert client.get("/more").data == b"GET"
120
121 rv = client.delete("/more")
122 assert rv.status_code == 405
123 assert sorted(rv.allow) == ["GET", "HEAD", "POST"]
124
125 rv = client.open("/more", method="OPTIONS")
126 assert rv.status_code == 405
127
128
129 def test_request_dispatching(app, client):
130 @app.route("/")
131 def index():
132 return flask.request.method
133
134 @app.route("/more", methods=["GET", "POST"])
135 def more():
136 return flask.request.method
137
138 assert client.get("/").data == b"GET"
139 rv = client.post("/")
140 assert rv.status_code == 405
141 assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS"]
142 rv = client.head("/")
143 assert rv.status_code == 200
144 assert not rv.data # head truncates
145 assert client.post("/more").data == b"POST"
146 assert client.get("/more").data == b"GET"
147 rv = client.delete("/more")
tests/test_basic.py 163

148 assert rv.status_code == 405


149 assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"]
150
151
152 def test_disallow_string_for_allowed_methods(app):
153 with pytest.raises(TypeError):
154 app.add_url_rule("/", methods="GET POST", endpoint="test")
155
156
157 def test_url_mapping(app, client):
158 random_uuid4 = "7eb41166-9ebf-4d26-b771-ea3f54f8b383"
159
160 def index():
161 return flask.request.method
162
163 def more():
164 return flask.request.method
165
166 def options():
167 return random_uuid4
168
169 app.add_url_rule("/", "index", index)
170 app.add_url_rule("/more", "more", more, methods=["GET", "POST"])
171
172 # Issue 1288: Test that automatic options are not added
173 # when non-uppercase 'options' in methods
174 app.add_url_rule("/options", "options", options, methods=["options"])
175
176 assert client.get("/").data == b"GET"
177 rv = client.post("/")
178 assert rv.status_code == 405
179 assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS"]
180 rv = client.head("/")
181 assert rv.status_code == 200
182 assert not rv.data # head truncates
183 assert client.post("/more").data == b"POST"
184 assert client.get("/more").data == b"GET"
185 rv = client.delete("/more")
186 assert rv.status_code == 405
187 assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"]
188 rv = client.open("/options", method="OPTIONS")
189 assert rv.status_code == 200
190 assert random_uuid4 in rv.data.decode("utf-8")
191
192
193 def test_werkzeug_routing(app, client):
194 from werkzeug.routing import Rule
195 from werkzeug.routing import Submount
196
197 app.url_map.add(
198 Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")])
199 )
200
201 def bar():
202 return "bar"
203
204 def index():
205 return "index"
206
207 app.view_functions["bar"] = bar
208 app.view_functions["index"] = index
209
210 assert client.get("/foo/").data == b"index"
211 assert client.get("/foo/bar").data == b"bar"
212
213
214 def test_endpoint_decorator(app, client):
215 from werkzeug.routing import Rule
216 from werkzeug.routing import Submount
217
218 app.url_map.add(
tests/test_basic.py 164

219 Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")])


220 )
221
222 @app.endpoint("bar")
223 def bar():
224 return "bar"
225
226 @app.endpoint("index")
227 def index():
228 return "index"
229
230 assert client.get("/foo/").data == b"index"
231 assert client.get("/foo/bar").data == b"bar"
232
233
234 def test_session(app, client):
235 @app.route("/set", methods=["POST"])
236 def set():
237 assert not flask.session.accessed
238 assert not flask.session.modified
239 flask.session["value"] = flask.request.form["value"]
240 assert flask.session.accessed
241 assert flask.session.modified
242 return "value set"
243
244 @app.route("/get")
245 def get():
246 assert not flask.session.accessed
247 assert not flask.session.modified
248 v = flask.session.get("value", "None")
249 assert flask.session.accessed
250 assert not flask.session.modified
251 return v
252
253 assert client.post("/set", data={"value": "42"}).data == b"value set"
254 assert client.get("/get").data == b"42"
255
256
257 def test_session_path(app, client):
258 app.config.update(APPLICATION_ROOT="/foo")
259
260 @app.route("/")
261 def index():
262 flask.session["testing"] = 42
263 return "Hello World"
264
265 rv = client.get("/", "http://example.com:8080/foo")
266 assert "path=/foo" in rv.headers["set-cookie"].lower()
267
268
269 def test_session_using_application_root(app, client):
270 class PrefixPathMiddleware:
271 def __init__(self, app, prefix):
272 self.app = app
273 self.prefix = prefix
274
275 def __call__(self, environ, start_response):
276 environ["SCRIPT_NAME"] = self.prefix
277 return self.app(environ, start_response)
278
279 app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, "/bar")
280 app.config.update(APPLICATION_ROOT="/bar")
281
282 @app.route("/")
283 def index():
284 flask.session["testing"] = 42
285 return "Hello World"
286
287 rv = client.get("/", "http://example.com:8080/")
288 assert "path=/bar" in rv.headers["set-cookie"].lower()
289
tests/test_basic.py 165

290
291 def test_session_using_session_settings(app, client):
292 app.config.update(
293 SERVER_NAME="www.example.com:8080",
294 APPLICATION_ROOT="/test",
295 SESSION_COOKIE_DOMAIN=".example.com",
296 SESSION_COOKIE_HTTPONLY=False,
297 SESSION_COOKIE_SECURE=True,
298 SESSION_COOKIE_PARTITIONED=True,
299 SESSION_COOKIE_SAMESITE="Lax",
300 SESSION_COOKIE_PATH="/",
301 )
302
303 @app.route("/")
304 def index():
305 flask.session["testing"] = 42
306 return "Hello World"
307
308 @app.route("/clear")
309 def clear():
310 flask.session.pop("testing", None)
311 return "Goodbye World"
312
313 rv = client.get("/", "http://www.example.com:8080/test/")
314 cookie = rv.headers["set-cookie"].lower()
315 # or condition for Werkzeug < 2.3
316 assert "domain=example.com" in cookie or "domain=.example.com" in cookie
317 assert "path=/" in cookie
318 assert "secure" in cookie
319 assert "httponly" not in cookie
320 assert "samesite" in cookie
321 assert "partitioned" in cookie
322
323 rv = client.get("/clear", "http://www.example.com:8080/test/")
324 cookie = rv.headers["set-cookie"].lower()
325 assert "session=;" in cookie
326 # or condition for Werkzeug < 2.3
327 assert "domain=example.com" in cookie or "domain=.example.com" in cookie
328 assert "path=/" in cookie
329 assert "secure" in cookie
330 assert "samesite" in cookie
331 assert "partitioned" in cookie
332
333
334 def test_session_using_samesite_attribute(app, client):
335 @app.route("/")
336 def index():
337 flask.session["testing"] = 42
338 return "Hello World"
339
340 app.config.update(SESSION_COOKIE_SAMESITE="invalid")
341
342 with pytest.raises(ValueError):
343 client.get("/")
344
345 app.config.update(SESSION_COOKIE_SAMESITE=None)
346 rv = client.get("/")
347 cookie = rv.headers["set-cookie"].lower()
348 assert "samesite" not in cookie
349
350 app.config.update(SESSION_COOKIE_SAMESITE="Strict")
351 rv = client.get("/")
352 cookie = rv.headers["set-cookie"].lower()
353 assert "samesite=strict" in cookie
354
355 app.config.update(SESSION_COOKIE_SAMESITE="Lax")
356 rv = client.get("/")
357 cookie = rv.headers["set-cookie"].lower()
358 assert "samesite=lax" in cookie
359
360
tests/test_basic.py 166

361 def test_missing_session(app):


362 app.secret_key = None
363
364 def expect_exception(f, *args, **kwargs):
365 e = pytest.raises(RuntimeError, f, *args, **kwargs)
366 assert e.value.args and "session is unavailable" in e.value.args[0]
367
368 with app.test_request_context():
369 assert flask.session.get("missing_key") is None
370 expect_exception(flask.session.__setitem__, "foo", 42)
371 expect_exception(flask.session.pop, "foo")
372
373
374 def test_session_secret_key_fallbacks(app, client) -> None:
375 @app.post("/")
376 def set_session() -> str:
377 flask.session["a"] = 1
378 return ""
379
380 @app.get("/")
381 def get_session() -> dict[str, t.Any]:
382 return dict(flask.session)
383
384 # Set session with initial secret key
385 client.post()
386 assert client.get().json == {"a": 1}
387 # Change secret key, session can't be loaded and appears empty
388 app.secret_key = "new test key"
389 assert client.get().json == {}
390 # Add initial secret key as fallback, session can be loaded
391 app.config["SECRET_KEY_FALLBACKS"] = ["test key"]
392 assert client.get().json == {"a": 1}
393
394
395 def test_session_expiration(app, client):
396 permanent = True
397
398 @app.route("/")
399 def index():
400 flask.session["test"] = 42
401 flask.session.permanent = permanent
402 return ""
403
404 @app.route("/test")
405 def test():
406 return str(flask.session.permanent)
407
408 rv = client.get("/")
409 assert "set-cookie" in rv.headers
410 match = re.search(r"(?i)\bexpires=([^;]+)", rv.headers["set-cookie"])
411 expires = parse_date(match.group())
412 expected = datetime.now(timezone.utc) + app.permanent_session_lifetime
413 assert expires.year == expected.year
414 assert expires.month == expected.month
415 assert expires.day == expected.day
416
417 rv = client.get("/test")
418 assert rv.data == b"True"
419
420 permanent = False
421 rv = client.get("/")
422 assert "set-cookie" in rv.headers
423 match = re.search(r"\bexpires=([^;]+)", rv.headers["set-cookie"])
424 assert match is None
425
426
427 def test_session_stored_last(app, client):
428 @app.after_request
429 def modify_session(response):
430 flask.session["foo"] = 42
431 return response
tests/test_basic.py 167

432
433 @app.route("/")
434 def dump_session_contents():
435 return repr(flask.session.get("foo"))
436
437 assert client.get("/").data == b"None"
438 assert client.get("/").data == b"42"
439
440
441 def test_session_special_types(app, client):
442 now = datetime.now(timezone.utc).replace(microsecond=0)
443 the_uuid = uuid.uuid4()
444
445 @app.route("/")
446 def dump_session_contents():
447 flask.session["t"] = (1, 2, 3)
448 flask.session["b"] = b"\xff"
449 flask.session["m"] = Markup("<html>")
450 flask.session["u"] = the_uuid
451 flask.session["d"] = now
452 flask.session["t_tag"] = {" t": "not-a-tuple"}
453 flask.session["di_t_tag"] = {" t__": "not-a-tuple"}
454 flask.session["di_tag"] = {" di": "not-a-dict"}
455 return "", 204
456
457 with client:
458 client.get("/")
459 s = flask.session
460 assert s["t"] == (1, 2, 3)
461 assert type(s["b"]) is bytes # noqa: E721
462 assert s["b"] == b"\xff"
463 assert type(s["m"]) is Markup # noqa: E721
464 assert s["m"] == Markup("<html>")
465 assert s["u"] == the_uuid
466 assert s["d"] == now
467 assert s["t_tag"] == {" t": "not-a-tuple"}
468 assert s["di_t_tag"] == {" t__": "not-a-tuple"}
469 assert s["di_tag"] == {" di": "not-a-dict"}
470
471
472 def test_session_cookie_setting(app):
473 is_permanent = True
474
475 @app.route("/bump")
476 def bump():
477 rv = flask.session["foo"] = flask.session.get("foo", 0) + 1
478 flask.session.permanent = is_permanent
479 return str(rv)
480
481 @app.route("/read")
482 def read():
483 return str(flask.session.get("foo", 0))
484
485 def run_test(expect_header):
486 with app.test_client() as c:
487 assert c.get("/bump").data == b"1"
488 assert c.get("/bump").data == b"2"
489 assert c.get("/bump").data == b"3"
490
491 rv = c.get("/read")
492 set_cookie = rv.headers.get("set-cookie")
493 assert (set_cookie is not None) == expect_header
494 assert rv.data == b"3"
495
496 is_permanent = True
497 app.config["SESSION_REFRESH_EACH_REQUEST"] = True
498 run_test(expect_header=True)
499
500 is_permanent = True
501 app.config["SESSION_REFRESH_EACH_REQUEST"] = False
502 run_test(expect_header=False)
tests/test_basic.py 168

503
504 is_permanent = False
505 app.config["SESSION_REFRESH_EACH_REQUEST"] = True
506 run_test(expect_header=False)
507
508 is_permanent = False
509 app.config["SESSION_REFRESH_EACH_REQUEST"] = False
510 run_test(expect_header=False)
511
512
513 def test_session_vary_cookie(app, client):
514 @app.route("/set")
515 def set_session():
516 flask.session["test"] = "test"
517 return ""
518
519 @app.route("/get")
520 def get():
521 return flask.session.get("test")
522
523 @app.route("/getitem")
524 def getitem():
525 return flask.session["test"]
526
527 @app.route("/setdefault")
528 def setdefault():
529 return flask.session.setdefault("test", "default")
530
531 @app.route("/clear")
532 def clear():
533 flask.session.clear()
534 return ""
535
536 @app.route("/vary-cookie-header-set")
537 def vary_cookie_header_set():
538 response = flask.Response()
539 response.vary.add("Cookie")
540 flask.session["test"] = "test"
541 return response
542
543 @app.route("/vary-header-set")
544 def vary_header_set():
545 response = flask.Response()
546 response.vary.update(("Accept-Encoding", "Accept-Language"))
547 flask.session["test"] = "test"
548 return response
549
550 @app.route("/no-vary-header")
551 def no_vary_header():
552 return ""
553
554 def expect(path, header_value="Cookie"):
555 rv = client.get(path)
556
557 if header_value:
558 # The 'Vary' key should exist in the headers only once.
559 assert len(rv.headers.get_all("Vary")) == 1
560 assert rv.headers["Vary"] == header_value
561 else:
562 assert "Vary" not in rv.headers
563
564 expect("/set")
565 expect("/get")
566 expect("/getitem")
567 expect("/setdefault")
568 expect("/clear")
569 expect("/vary-cookie-header-set")
570 expect("/vary-header-set", "Accept-Encoding, Accept-Language, Cookie")
571 expect("/no-vary-header", None)
572
573
tests/test_basic.py 169

574 def test_session_refresh_vary(app, client):


575 @app.get("/login")
576 def login():
577 flask.session["user_id"] = 1
578 flask.session.permanent = True
579 return ""
580
581 @app.get("/ignored")
582 def ignored():
583 return ""
584
585 rv = client.get("/login")
586 assert rv.headers["Vary"] == "Cookie"
587 rv = client.get("/ignored")
588 assert rv.headers["Vary"] == "Cookie"
589
590
591 def test_flashes(app, req_ctx):
592 assert not flask.session.modified
593 flask.flash("Zap")
594 flask.session.modified = False
595 flask.flash("Zip")
596 assert flask.session.modified
597 assert list(flask.get_flashed_messages()) == ["Zap", "Zip"]
598
599
600 def test_extended_flashing(app):
601 # Be sure app.testing=True below, else tests can fail silently.
602 #
603 # Specifically, if app.testing is not set to True, the AssertionErrors
604 # in the view functions will cause a 500 response to the test client
605 # instead of propagating exceptions.
606
607 @app.route("/")
608 def index():
609 flask.flash("Hello World")
610 flask.flash("Hello World", "error")
611 flask.flash(Markup("<em>Testing</em>"), "warning")
612 return ""
613
614 @app.route("/test/")
615 def test():
616 messages = flask.get_flashed_messages()
617 assert list(messages) == [
618 "Hello World",
619 "Hello World",
620 Markup("<em>Testing</em>"),
621 ]
622 return ""
623
624 @app.route("/test_with_categories/")
625 def test_with_categories():
626 messages = flask.get_flashed_messages(with_categories=True)
627 assert len(messages) == 3
628 assert list(messages) == [
629 ("message", "Hello World"),
630 ("error", "Hello World"),
631 ("warning", Markup("<em>Testing</em>")),
632 ]
633 return ""
634
635 @app.route("/test_filter/")
636 def test_filter():
637 messages = flask.get_flashed_messages(
638 category_filter=["message"], with_categories=True
639 )
640 assert list(messages) == [("message", "Hello World")]
641 return ""
642
643 @app.route("/test_filters/")
644 def test_filters():
tests/test_basic.py 170

645 messages = flask.get_flashed_messages(


646 category_filter=["message", "warning"], with_categories=True
647 )
648 assert list(messages) == [
649 ("message", "Hello World"),
650 ("warning", Markup("<em>Testing</em>")),
651 ]
652 return ""
653
654 @app.route("/test_filters_without_returning_categories/")
655 def test_filters2():
656 messages = flask.get_flashed_messages(category_filter=["message", "warning"])
657 assert len(messages) == 2
658 assert messages[0] == "Hello World"
659 assert messages[1] == Markup("<em>Testing</em>")
660 return ""
661
662 # Create new test client on each test to clean flashed messages.
663
664 client = app.test_client()
665 client.get("/")
666 client.get("/test_with_categories/")
667
668 client = app.test_client()
669 client.get("/")
670 client.get("/test_filter/")
671
672 client = app.test_client()
673 client.get("/")
674 client.get("/test_filters/")
675
676 client = app.test_client()
677 client.get("/")
678 client.get("/test_filters_without_returning_categories/")
679
680
681 def test_request_processing(app, client):
682 evts = []
683
684 @app.before_request
685 def before_request():
686 evts.append("before")
687
688 @app.after_request
689 def after_request(response):
690 response.data += b"|after"
691 evts.append("after")
692 return response
693
694 @app.route("/")
695 def index():
696 assert "before" in evts
697 assert "after" not in evts
698 return "request"
699
700 assert "after" not in evts
701 rv = client.get("/").data
702 assert "after" in evts
703 assert rv == b"request|after"
704
705
706 def test_request_preprocessing_early_return(app, client):
707 evts = []
708
709 @app.before_request
710 def before_request1():
711 evts.append(1)
712
713 @app.before_request
714 def before_request2():
715 evts.append(2)
tests/test_basic.py 171

716 return "hello"


717
718 @app.before_request
719 def before_request3():
720 evts.append(3)
721 return "bye"
722
723 @app.route("/")
724 def index():
725 evts.append("index")
726 return "damnit"
727
728 rv = client.get("/").data.strip()
729 assert rv == b"hello"
730 assert evts == [1, 2]
731
732
733 def test_after_request_processing(app, client):
734 @app.route("/")
735 def index():
736 @flask.after_this_request
737 def foo(response):
738 response.headers["X-Foo"] = "a header"
739 return response
740
741 return "Test"
742
743 resp = client.get("/")
744 assert resp.status_code == 200
745 assert resp.headers["X-Foo"] == "a header"
746
747
748 def test_teardown_request_handler(app, client):
749 called = []
750
751 @app.teardown_request
752 def teardown_request(exc):
753 called.append(True)
754 return "Ignored"
755
756 @app.route("/")
757 def root():
758 return "Response"
759
760 rv = client.get("/")
761 assert rv.status_code == 200
762 assert b"Response" in rv.data
763 assert len(called) == 1
764
765
766 def test_teardown_request_handler_debug_mode(app, client):
767 called = []
768
769 @app.teardown_request
770 def teardown_request(exc):
771 called.append(True)
772 return "Ignored"
773
774 @app.route("/")
775 def root():
776 return "Response"
777
778 rv = client.get("/")
779 assert rv.status_code == 200
780 assert b"Response" in rv.data
781 assert len(called) == 1
782
783
784 def test_teardown_request_handler_error(app, client):
785 called = []
786 app.testing = False
tests/test_basic.py 172

787
788 @app.teardown_request
789 def teardown_request1(exc):
790 assert type(exc) is ZeroDivisionError
791 called.append(True)
792 # This raises a new error and blows away sys.exc_info(), so we can
793 # test that all teardown_requests get passed the same original
794 # exception.
795 try:
796 raise TypeError()
797 except Exception:
798 pass
799
800 @app.teardown_request
801 def teardown_request2(exc):
802 assert type(exc) is ZeroDivisionError
803 called.append(True)
804 # This raises a new error and blows away sys.exc_info(), so we can
805 # test that all teardown_requests get passed the same original
806 # exception.
807 try:
808 raise TypeError()
809 except Exception:
810 pass
811
812 @app.route("/")
813 def fails():
814 raise ZeroDivisionError
815
816 rv = client.get("/")
817 assert rv.status_code == 500
818 assert b"Internal Server Error" in rv.data
819 assert len(called) == 2
820
821
822 def test_before_after_request_order(app, client):
823 called = []
824
825 @app.before_request
826 def before1():
827 called.append(1)
828
829 @app.before_request
830 def before2():
831 called.append(2)
832
833 @app.after_request
834 def after1(response):
835 called.append(4)
836 return response
837
838 @app.after_request
839 def after2(response):
840 called.append(3)
841 return response
842
843 @app.teardown_request
844 def finish1(exc):
845 called.append(6)
846
847 @app.teardown_request
848 def finish2(exc):
849 called.append(5)
850
851 @app.route("/")
852 def index():
853 return "42"
854
855 rv = client.get("/")
856 assert rv.data == b"42"
857 assert called == [1, 2, 3, 4, 5, 6]
tests/test_basic.py 173

858
859
860 def test_error_handling(app, client):
861 app.testing = False
862
863 @app.errorhandler(404)
864 def not_found(e):
865 return "not found", 404
866
867 @app.errorhandler(500)
868 def internal_server_error(e):
869 return "internal server error", 500
870
871 @app.errorhandler(Forbidden)
872 def forbidden(e):
873 return "forbidden", 403
874
875 @app.route("/")
876 def index():
877 flask.abort(404)
878
879 @app.route("/error")
880 def error():
881 raise ZeroDivisionError
882
883 @app.route("/forbidden")
884 def error2():
885 flask.abort(403)
886
887 rv = client.get("/")
888 assert rv.status_code == 404
889 assert rv.data == b"not found"
890 rv = client.get("/error")
891 assert rv.status_code == 500
892 assert b"internal server error" == rv.data
893 rv = client.get("/forbidden")
894 assert rv.status_code == 403
895 assert b"forbidden" == rv.data
896
897
898 def test_error_handling_processing(app, client):
899 app.testing = False
900
901 @app.errorhandler(500)
902 def internal_server_error(e):
903 return "internal server error", 500
904
905 @app.route("/")
906 def broken_func():
907 raise ZeroDivisionError
908
909 @app.after_request
910 def after_request(resp):
911 resp.mimetype = "text/x-special"
912 return resp
913
914 resp = client.get("/")
915 assert resp.mimetype == "text/x-special"
916 assert resp.data == b"internal server error"
917
918
919 def test_baseexception_error_handling(app, client):
920 app.testing = False
921
922 @app.route("/")
923 def broken_func():
924 raise KeyboardInterrupt()
925
926 with pytest.raises(KeyboardInterrupt):
927 client.get("/")
928
tests/test_basic.py 174

929
930 def test_before_request_and_routing_errors(app, client):
931 @app.before_request
932 def attach_something():
933 flask.g.something = "value"
934
935 @app.errorhandler(404)
936 def return_something(error):
937 return flask.g.something, 404
938
939 rv = client.get("/")
940 assert rv.status_code == 404
941 assert rv.data == b"value"
942
943
944 def test_user_error_handling(app, client):
945 class MyException(Exception):
946 pass
947
948 @app.errorhandler(MyException)
949 def handle_my_exception(e):
950 assert isinstance(e, MyException)
951 return "42"
952
953 @app.route("/")
954 def index():
955 raise MyException()
956
957 assert client.get("/").data == b"42"
958
959
960 def test_http_error_subclass_handling(app, client):
961 class ForbiddenSubclass(Forbidden):
962 pass
963
964 @app.errorhandler(ForbiddenSubclass)
965 def handle_forbidden_subclass(e):
966 assert isinstance(e, ForbiddenSubclass)
967 return "banana"
968
969 @app.errorhandler(403)
970 def handle_403(e):
971 assert not isinstance(e, ForbiddenSubclass)
972 assert isinstance(e, Forbidden)
973 return "apple"
974
975 @app.route("/1")
976 def index1():
977 raise ForbiddenSubclass()
978
979 @app.route("/2")
980 def index2():
981 flask.abort(403)
982
983 @app.route("/3")
984 def index3():
985 raise Forbidden()
986
987 assert client.get("/1").data == b"banana"
988 assert client.get("/2").data == b"apple"
989 assert client.get("/3").data == b"apple"
990
991
992 def test_errorhandler_precedence(app, client):
993 class E1(Exception):
994 pass
995
996 class E2(Exception):
997 pass
998
999 class E3(E1, E2):
tests/test_basic.py 175

1000 pass
1001
1002 @app.errorhandler(E2)
1003 def handle_e2(e):
1004 return "E2"
1005
1006 @app.errorhandler(Exception)
1007 def handle_exception(e):
1008 return "Exception"
1009
1010 @app.route("/E1")
1011 def raise_e1():
1012 raise E1
1013
1014 @app.route("/E3")
1015 def raise_e3():
1016 raise E3
1017
1018 rv = client.get("/E1")
1019 assert rv.data == b"Exception"
1020
1021 rv = client.get("/E3")
1022 assert rv.data == b"E2"
1023
1024
1025 @pytest.mark.parametrize(
1026 ("debug", "trap", "expect_key", "expect_abort"),
1027 [(False, None, True, True), (True, None, False, True), (False, True, False, False)],
1028 )
1029 def test_trap_bad_request_key_error(app, client, debug, trap, expect_key, expect_abort):
1030 app.config["DEBUG"] = debug
1031 app.config["TRAP_BAD_REQUEST_ERRORS"] = trap
1032
1033 @app.route("/key")
1034 def fail():
1035 flask.request.form["missing_key"]
1036
1037 @app.route("/abort")
1038 def allow_abort():
1039 flask.abort(400)
1040
1041 if expect_key:
1042 rv = client.get("/key")
1043 assert rv.status_code == 400
1044 assert b"missing_key" not in rv.data
1045 else:
1046 with pytest.raises(KeyError) as exc_info:
1047 client.get("/key")
1048
1049 assert exc_info.errisinstance(BadRequest)
1050 assert "missing_key" in exc_info.value.get_description()
1051
1052 if expect_abort:
1053 rv = client.get("/abort")
1054 assert rv.status_code == 400
1055 else:
1056 with pytest.raises(BadRequest):
1057 client.get("/abort")
1058
1059
1060 def test_trapping_of_all_http_exceptions(app, client):
1061 app.config["TRAP_HTTP_EXCEPTIONS"] = True
1062
1063 @app.route("/fail")
1064 def fail():
1065 flask.abort(404)
1066
1067 with pytest.raises(NotFound):
1068 client.get("/fail")
1069
1070
tests/test_basic.py 176

1071 def test_error_handler_after_processor_error(app, client):


1072 app.testing = False
1073
1074 @app.before_request
1075 def before_request():
1076 if _trigger == "before":
1077 raise ZeroDivisionError
1078
1079 @app.after_request
1080 def after_request(response):
1081 if _trigger == "after":
1082 raise ZeroDivisionError
1083
1084 return response
1085
1086 @app.route("/")
1087 def index():
1088 return "Foo"
1089
1090 @app.errorhandler(500)
1091 def internal_server_error(e):
1092 return "Hello Server Error", 500
1093
1094 for _trigger in "before", "after":
1095 rv = client.get("/")
1096 assert rv.status_code == 500
1097 assert rv.data == b"Hello Server Error"
1098
1099
1100 def test_enctype_debug_helper(app, client):
1101 from flask.debughelpers import DebugFilesKeyError
1102
1103 app.debug = True
1104
1105 @app.route("/fail", methods=["POST"])
1106 def index():
1107 return flask.request.files["foo"].filename
1108
1109 with pytest.raises(DebugFilesKeyError) as e:
1110 client.post("/fail", data={"foo": "index.txt"})
1111 assert "no file contents were transmitted" in str(e.value)
1112 assert "This was submitted: 'index.txt'" in str(e.value)
1113
1114
1115 def test_response_types(app, client):
1116 @app.route("/text")
1117 def from_text():
1118 return "Hällo Wörld"
1119
1120 @app.route("/bytes")
1121 def from_bytes():
1122 return "Hällo Wörld".encode()
1123
1124 @app.route("/full_tuple")
1125 def from_full_tuple():
1126 return (
1127 "Meh",
1128 400,
1129 {"X-Foo": "Testing", "Content-Type": "text/plain; charset=utf-8"},
1130 )
1131
1132 @app.route("/text_headers")
1133 def from_text_headers():
1134 return "Hello", {"X-Foo": "Test", "Content-Type": "text/plain; charset=utf-8"}
1135
1136 @app.route("/text_status")
1137 def from_text_status():
1138 return "Hi, status!", 400
1139
1140 @app.route("/response_headers")
1141 def from_response_headers():
tests/test_basic.py 177

1142 return (
1143 flask.Response(
1144 "Hello world", 404, {"Content-Type": "text/html", "X-Foo": "Baz"}
1145 ),
1146 {"Content-Type": "text/plain", "X-Foo": "Bar", "X-Bar": "Foo"},
1147 )
1148
1149 @app.route("/response_status")
1150 def from_response_status():
1151 return app.response_class("Hello world", 400), 500
1152
1153 @app.route("/wsgi")
1154 def from_wsgi():
1155 return NotFound()
1156
1157 @app.route("/dict")
1158 def from_dict():
1159 return {"foo": "bar"}, 201
1160
1161 @app.route("/list")
1162 def from_list():
1163 return ["foo", "bar"], 201
1164
1165 assert client.get("/text").data == "Hällo Wörld".encode()
1166 assert client.get("/bytes").data == "Hällo Wörld".encode()
1167
1168 rv = client.get("/full_tuple")
1169 assert rv.data == b"Meh"
1170 assert rv.headers["X-Foo"] == "Testing"
1171 assert rv.status_code == 400
1172 assert rv.mimetype == "text/plain"
1173
1174 rv = client.get("/text_headers")
1175 assert rv.data == b"Hello"
1176 assert rv.headers["X-Foo"] == "Test"
1177 assert rv.status_code == 200
1178 assert rv.mimetype == "text/plain"
1179
1180 rv = client.get("/text_status")
1181 assert rv.data == b"Hi, status!"
1182 assert rv.status_code == 400
1183 assert rv.mimetype == "text/html"
1184
1185 rv = client.get("/response_headers")
1186 assert rv.data == b"Hello world"
1187 assert rv.content_type == "text/plain"
1188 assert rv.headers.getlist("X-Foo") == ["Bar"]
1189 assert rv.headers["X-Bar"] == "Foo"
1190 assert rv.status_code == 404
1191
1192 rv = client.get("/response_status")
1193 assert rv.data == b"Hello world"
1194 assert rv.status_code == 500
1195
1196 rv = client.get("/wsgi")
1197 assert b"Not Found" in rv.data
1198 assert rv.status_code == 404
1199
1200 rv = client.get("/dict")
1201 assert rv.json == {"foo": "bar"}
1202 assert rv.status_code == 201
1203
1204 rv = client.get("/list")
1205 assert rv.json == ["foo", "bar"]
1206 assert rv.status_code == 201
1207
1208
1209 def test_response_type_errors():
1210 app = flask.Flask(__name__)
1211 app.testing = True
1212
tests/test_basic.py 178

1213 @app.route("/none")
1214 def from_none():
1215 pass
1216
1217 @app.route("/small_tuple")
1218 def from_small_tuple():
1219 return ("Hello",)
1220
1221 @app.route("/large_tuple")
1222 def from_large_tuple():
1223 return "Hello", 234, {"X-Foo": "Bar"}, "???"
1224
1225 @app.route("/bad_type")
1226 def from_bad_type():
1227 return True
1228
1229 @app.route("/bad_wsgi")
1230 def from_bad_wsgi():
1231 return lambda: None
1232
1233 c = app.test_client()
1234
1235 with pytest.raises(TypeError) as e:
1236 c.get("/none")
1237
1238 assert "returned None" in str(e.value)
1239 assert "from_none" in str(e.value)
1240
1241 with pytest.raises(TypeError) as e:
1242 c.get("/small_tuple")
1243
1244 assert "tuple must have the form" in str(e.value)
1245
1246 with pytest.raises(TypeError):
1247 c.get("/large_tuple")
1248
1249 with pytest.raises(TypeError) as e:
1250 c.get("/bad_type")
1251
1252 assert "it was a bool" in str(e.value)
1253
1254 with pytest.raises(TypeError):
1255 c.get("/bad_wsgi")
1256
1257
1258 def test_make_response(app, req_ctx):
1259 rv = flask.make_response()
1260 assert rv.status_code == 200
1261 assert rv.data == b""
1262 assert rv.mimetype == "text/html"
1263
1264 rv = flask.make_response("Awesome")
1265 assert rv.status_code == 200
1266 assert rv.data == b"Awesome"
1267 assert rv.mimetype == "text/html"
1268
1269 rv = flask.make_response("W00t", 404)
1270 assert rv.status_code == 404
1271 assert rv.data == b"W00t"
1272 assert rv.mimetype == "text/html"
1273
1274 rv = flask.make_response(c for c in "Hello")
1275 assert rv.status_code == 200
1276 assert rv.data == b"Hello"
1277 assert rv.mimetype == "text/html"
1278
1279
1280 def test_make_response_with_response_instance(app, req_ctx):
1281 rv = flask.make_response(flask.jsonify({"msg": "W00t"}), 400)
1282 assert rv.status_code == 400
1283 assert rv.data == b'{"msg":"W00t"}\n'
tests/test_basic.py 179

1284 assert rv.mimetype == "application/json"


1285
1286 rv = flask.make_response(flask.Response(""), 400)
1287 assert rv.status_code == 400
1288 assert rv.data == b""
1289 assert rv.mimetype == "text/html"
1290
1291 rv = flask.make_response(
1292 flask.Response("", headers={"Content-Type": "text/html"}),
1293 400,
1294 [("X-Foo", "bar")],
1295 )
1296 assert rv.status_code == 400
1297 assert rv.headers["Content-Type"] == "text/html"
1298 assert rv.headers["X-Foo"] == "bar"
1299
1300
1301 @pytest.mark.parametrize("compact", [True, False])
1302 def test_jsonify_no_prettyprint(app, compact):
1303 app.json.compact = compact
1304 rv = app.json.response({"msg": {"submsg": "W00t"}, "msg2": "foobar"})
1305 data = rv.data.strip()
1306 assert (b" " not in data) is compact
1307 assert (b"\n" not in data) is compact
1308
1309
1310 def test_jsonify_mimetype(app, req_ctx):
1311 app.json.mimetype = "application/vnd.api+json"
1312 msg = {"msg": {"submsg": "W00t"}}
1313 rv = flask.make_response(flask.jsonify(msg), 200)
1314 assert rv.mimetype == "application/vnd.api+json"
1315
1316
1317 def test_json_dump_dataclass(app, req_ctx):
1318 from dataclasses import make_dataclass
1319
1320 Data = make_dataclass("Data", [("name", str)])
1321 value = app.json.dumps(Data("Flask"))
1322 value = app.json.loads(value)
1323 assert value == {"name": "Flask"}
1324
1325
1326 def test_jsonify_args_and_kwargs_check(app, req_ctx):
1327 with pytest.raises(TypeError) as e:
1328 flask.jsonify("fake args", kwargs="fake")
1329 assert "args or kwargs" in str(e.value)
1330
1331
1332 def test_url_generation(app, req_ctx):
1333 @app.route("/hello/<name>", methods=["POST"])
1334 def hello():
1335 pass
1336
1337 assert flask.url_for("hello", name="test x") == "/hello/test%20x"
1338 assert (
1339 flask.url_for("hello", name="test x", _external=True)
1340 == "http://localhost/hello/test%20x"
1341 )
1342
1343
1344 def test_build_error_handler(app):
1345 # Test base case, a URL which results in a BuildError.
1346 with app.test_request_context():
1347 pytest.raises(BuildError, flask.url_for, "spam")
1348
1349 # Verify the error is re-raised if not the current exception.
1350 try:
1351 with app.test_request_context():
1352 flask.url_for("spam")
1353 except BuildError as err:
1354 error = err
tests/test_basic.py 180

1355 try:
1356 raise RuntimeError("Test case where BuildError is not current.")
1357 except RuntimeError:
1358 pytest.raises(BuildError, app.handle_url_build_error, error, "spam", {})
1359
1360 # Test a custom handler.
1361 def handler(error, endpoint, values):
1362 # Just a test.
1363 return "/test_handler/"
1364
1365 app.url_build_error_handlers.append(handler)
1366 with app.test_request_context():
1367 assert flask.url_for("spam") == "/test_handler/"
1368
1369
1370 def test_build_error_handler_reraise(app):
1371 # Test a custom handler which reraises the BuildError
1372 def handler_raises_build_error(error, endpoint, values):
1373 raise error
1374
1375 app.url_build_error_handlers.append(handler_raises_build_error)
1376
1377 with app.test_request_context():
1378 pytest.raises(BuildError, flask.url_for, "not.existing")
1379
1380
1381 def test_url_for_passes_special_values_to_build_error_handler(app):
1382 @app.url_build_error_handlers.append
1383 def handler(error, endpoint, values):
1384 assert values == {
1385 "_external": False,
1386 "_anchor": None,
1387 "_method": None,
1388 "_scheme": None,
1389 }
1390 return "handled"
1391
1392 with app.test_request_context():
1393 flask.url_for("/")
1394
1395
1396 def test_static_files(app, client):
1397 rv = client.get("/static/index.html")
1398 assert rv.status_code == 200
1399 assert rv.data.strip() == b"<h1>Hello World!</h1>"
1400 with app.test_request_context():
1401 assert flask.url_for("static", filename="index.html") == "/static/index.html"
1402 rv.close()
1403
1404
1405 def test_static_url_path():
1406 app = flask.Flask(__name__, static_url_path="/foo")
1407 app.testing = True
1408 rv = app.test_client().get("/foo/index.html")
1409 assert rv.status_code == 200
1410 rv.close()
1411
1412 with app.test_request_context():
1413 assert flask.url_for("static", filename="index.html") == "/foo/index.html"
1414
1415
1416 def test_static_url_path_with_ending_slash():
1417 app = flask.Flask(__name__, static_url_path="/foo/")
1418 app.testing = True
1419 rv = app.test_client().get("/foo/index.html")
1420 assert rv.status_code == 200
1421 rv.close()
1422
1423 with app.test_request_context():
1424 assert flask.url_for("static", filename="index.html") == "/foo/index.html"
1425
tests/test_basic.py 181

1426
1427 def test_static_url_empty_path(app):
1428 app = flask.Flask(__name__, static_folder="", static_url_path="")
1429 rv = app.test_client().open("/static/index.html", method="GET")
1430 assert rv.status_code == 200
1431 rv.close()
1432
1433
1434 def test_static_url_empty_path_default(app):
1435 app = flask.Flask(__name__, static_folder="")
1436 rv = app.test_client().open("/static/index.html", method="GET")
1437 assert rv.status_code == 200
1438 rv.close()
1439
1440
1441 def test_static_folder_with_pathlib_path(app):
1442 from pathlib import Path
1443
1444 app = flask.Flask(__name__, static_folder=Path("static"))
1445 rv = app.test_client().open("/static/index.html", method="GET")
1446 assert rv.status_code == 200
1447 rv.close()
1448
1449
1450 def test_static_folder_with_ending_slash():
1451 app = flask.Flask(__name__, static_folder="static/")
1452
1453 @app.route("/<path:path>")
1454 def catch_all(path):
1455 return path
1456
1457 rv = app.test_client().get("/catch/all")
1458 assert rv.data == b"catch/all"
1459
1460
1461 def test_static_route_with_host_matching():
1462 app = flask.Flask(__name__, host_matching=True, static_host="example.com")
1463 c = app.test_client()
1464 rv = c.get("http://example.com/static/index.html")
1465 assert rv.status_code == 200
1466 rv.close()
1467 with app.test_request_context():
1468 rv = flask.url_for("static", filename="index.html", _external=True)
1469 assert rv == "http://example.com/static/index.html"
1470 # Providing static_host without host_matching=True should error.
1471 with pytest.raises(AssertionError):
1472 flask.Flask(__name__, static_host="example.com")
1473 # Providing host_matching=True with static_folder
1474 # but without static_host should error.
1475 with pytest.raises(AssertionError):
1476 flask.Flask(__name__, host_matching=True)
1477 # Providing host_matching=True without static_host
1478 # but with static_folder=None should not error.
1479 flask.Flask(__name__, host_matching=True, static_folder=None)
1480
1481
1482 def test_request_locals():
1483 assert repr(flask.g) == "<LocalProxy unbound>"
1484 assert not flask.g
1485
1486
1487 @pytest.mark.parametrize(
1488 ("subdomain_matching", "host_matching", "expect_base", "expect_abc", "expect_xyz"),
1489 [
1490 (False, False, "default", "default", "default"),
1491 (True, False, "default", "abc", "<invalid>"),
1492 (False, True, "default", "abc", "default"),
1493 ],
1494 )
1495 def test_server_name_matching(
1496 subdomain_matching: bool,
tests/test_basic.py 182

1497 host_matching: bool,


1498 expect_base: str,
1499 expect_abc: str,
1500 expect_xyz: str,
1501 ) -> None:
1502 app = flask.Flask(
1503 __name__,
1504 subdomain_matching=subdomain_matching,
1505 host_matching=host_matching,
1506 static_host="example.test" if host_matching else None,
1507 )
1508 app.config["SERVER_NAME"] = "example.test"
1509
1510 @app.route("/", defaults={"name": "default"}, host="<name>")
1511 @app.route("/", subdomain="<name>", host="<name>.example.test")
1512 def index(name: str) -> str:
1513 return name
1514
1515 client = app.test_client()
1516
1517 r = client.get(base_url="http://example.test")
1518 assert r.text == expect_base
1519
1520 r = client.get(base_url="http://abc.example.test")
1521 assert r.text == expect_abc
1522
1523 with pytest.warns() if subdomain_matching else nullcontext():
1524 r = client.get(base_url="http://xyz.other.test")
1525
1526 assert r.text == expect_xyz
1527
1528
1529 def test_server_name_subdomain():
1530 app = flask.Flask(__name__, subdomain_matching=True)
1531 client = app.test_client()
1532
1533 @app.route("/")
1534 def index():
1535 return "default"
1536
1537 @app.route("/", subdomain="foo")
1538 def subdomain():
1539 return "subdomain"
1540
1541 app.config["SERVER_NAME"] = "dev.local:5000"
1542 rv = client.get("/")
1543 assert rv.data == b"default"
1544
1545 rv = client.get("/", "http://dev.local:5000")
1546 assert rv.data == b"default"
1547
1548 rv = client.get("/", "https://dev.local:5000")
1549 assert rv.data == b"default"
1550
1551 app.config["SERVER_NAME"] = "dev.local:443"
1552 rv = client.get("/", "https://dev.local")
1553
1554 # Werkzeug 1.0 fixes matching https scheme with 443 port
1555 if rv.status_code != 404:
1556 assert rv.data == b"default"
1557
1558 app.config["SERVER_NAME"] = "dev.local"
1559 rv = client.get("/", "https://dev.local")
1560 assert rv.data == b"default"
1561
1562 # suppress Werkzeug 0.15 warning about name mismatch
1563 with warnings.catch_warnings():
1564 warnings.filterwarnings(
1565 "ignore", "Current server name", UserWarning, "flask.app"
1566 )
1567 rv = client.get("/", "http://foo.localhost")
tests/test_basic.py 183

1568 assert rv.status_code == 404


1569
1570 rv = client.get("/", "http://foo.dev.local")
1571 assert rv.data == b"subdomain"
1572
1573
1574 @pytest.mark.parametrize("key", ["TESTING", "PROPAGATE_EXCEPTIONS", "DEBUG", None])
1575 def test_exception_propagation(app, client, key):
1576 app.testing = False
1577
1578 @app.route("/")
1579 def index():
1580 raise ZeroDivisionError
1581
1582 if key is not None:
1583 app.config[key] = True
1584
1585 with pytest.raises(ZeroDivisionError):
1586 client.get("/")
1587 else:
1588 assert client.get("/").status_code == 500
1589
1590
1591 @pytest.mark.parametrize("debug", [True, False])
1592 @pytest.mark.parametrize("use_debugger", [True, False])
1593 @pytest.mark.parametrize("use_reloader", [True, False])
1594 @pytest.mark.parametrize("propagate_exceptions", [None, True, False])
1595 def test_werkzeug_passthrough_errors(
1596 monkeypatch, debug, use_debugger, use_reloader, propagate_exceptions, app
1597 ):
1598 rv = {}
1599
1600 # Mocks werkzeug.serving.run_simple method
1601 def run_simple_mock(*args, **kwargs):
1602 rv["passthrough_errors"] = kwargs.get("passthrough_errors")
1603
1604 monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
1605 app.config["PROPAGATE_EXCEPTIONS"] = propagate_exceptions
1606 app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader)
1607
1608
1609 def test_url_processors(app, client):
1610 @app.url_defaults
1611 def add_language_code(endpoint, values):
1612 if flask.g.lang_code is not None and app.url_map.is_endpoint_expecting(
1613 endpoint, "lang_code"
1614 ):
1615 values.setdefault("lang_code", flask.g.lang_code)
1616
1617 @app.url_value_preprocessor
1618 def pull_lang_code(endpoint, values):
1619 flask.g.lang_code = values.pop("lang_code", None)
1620
1621 @app.route("/<lang_code>/")
1622 def index():
1623 return flask.url_for("about")
1624
1625 @app.route("/<lang_code>/about")
1626 def about():
1627 return flask.url_for("something_else")
1628
1629 @app.route("/foo")
1630 def something_else():
1631 return flask.url_for("about", lang_code="en")
1632
1633 assert client.get("/de/").data == b"/de/about"
1634 assert client.get("/de/about").data == b"/foo"
1635 assert client.get("/foo").data == b"/en/about"
1636
1637
1638 def test_inject_blueprint_url_defaults(app):
tests/test_basic.py 184

1639 bp = flask.Blueprint("foo", __name__, template_folder="template")


1640
1641 @bp.url_defaults
1642 def bp_defaults(endpoint, values):
1643 values["page"] = "login"
1644
1645 @bp.route("/<page>")
1646 def view(page):
1647 pass
1648
1649 app.register_blueprint(bp)
1650
1651 values = dict()
1652 app.inject_url_defaults("foo.view", values)
1653 expected = dict(page="login")
1654 assert values == expected
1655
1656 with app.test_request_context("/somepage"):
1657 url = flask.url_for("foo.view")
1658 expected = "/login"
1659 assert url == expected
1660
1661
1662 def test_nonascii_pathinfo(app, client):
1663 @app.route("/�������")
1664 def index():
1665 return "Hello World!"
1666
1667 rv = client.get("/�������")
1668 assert rv.data == b"Hello World!"
1669
1670
1671 def test_no_setup_after_first_request(app, client):
1672 app.debug = True
1673
1674 @app.route("/")
1675 def index():
1676 return "Awesome"
1677
1678 assert client.get("/").data == b"Awesome"
1679
1680 with pytest.raises(AssertionError) as exc_info:
1681 app.add_url_rule("/foo", endpoint="late")
1682
1683 assert "setup method 'add_url_rule'" in str(exc_info.value)
1684
1685
1686 def test_routing_redirect_debugging(monkeypatch, app, client):
1687 app.config["DEBUG"] = True
1688
1689 @app.route("/user/", methods=["GET", "POST"])
1690 def user():
1691 return flask.request.form["status"]
1692
1693 # default redirect code preserves form data
1694 rv = client.post("/user", data={"status": "success"}, follow_redirects=True)
1695 assert rv.data == b"success"
1696
1697 # 301 and 302 raise error
1698 monkeypatch.setattr(RequestRedirect, "code", 301)
1699
1700 with client, pytest.raises(AssertionError) as exc_info:
1701 client.post("/user", data={"status": "error"}, follow_redirects=True)
1702
1703 assert "canonical URL 'http://localhost/user/'" in str(exc_info.value)
1704
1705
1706 def test_route_decorator_custom_endpoint(app, client):
1707 app.debug = True
1708
1709 @app.route("/foo/")
tests/test_basic.py 185

1710 def foo():


1711 return flask.request.endpoint
1712
1713 @app.route("/bar/", endpoint="bar")
1714 def for_bar():
1715 return flask.request.endpoint
1716
1717 @app.route("/bar/123", endpoint="123")
1718 def for_bar_foo():
1719 return flask.request.endpoint
1720
1721 with app.test_request_context():
1722 assert flask.url_for("foo") == "/foo/"
1723 assert flask.url_for("bar") == "/bar/"
1724 assert flask.url_for("123") == "/bar/123"
1725
1726 assert client.get("/foo/").data == b"foo"
1727 assert client.get("/bar/").data == b"bar"
1728 assert client.get("/bar/123").data == b"123"
1729
1730
1731 def test_get_method_on_g(app_ctx):
1732 assert flask.g.get("x") is None
1733 assert flask.g.get("x", 11) == 11
1734 flask.g.x = 42
1735 assert flask.g.get("x") == 42
1736 assert flask.g.x == 42
1737
1738
1739 def test_g_iteration_protocol(app_ctx):
1740 flask.g.foo = 23
1741 flask.g.bar = 42
1742 assert "foo" in flask.g
1743 assert "foos" not in flask.g
1744 assert sorted(flask.g) == ["bar", "foo"]
1745
1746
1747 def test_subdomain_basic_support():
1748 app = flask.Flask(__name__, subdomain_matching=True)
1749 app.config["SERVER_NAME"] = "localhost.localdomain"
1750 client = app.test_client()
1751
1752 @app.route("/")
1753 def normal_index():
1754 return "normal index"
1755
1756 @app.route("/", subdomain="test")
1757 def test_index():
1758 return "test index"
1759
1760 rv = client.get("/", "http://localhost.localdomain/")
1761 assert rv.data == b"normal index"
1762
1763 rv = client.get("/", "http://test.localhost.localdomain/")
1764 assert rv.data == b"test index"
1765
1766
1767 def test_subdomain_matching():
1768 app = flask.Flask(__name__, subdomain_matching=True)
1769 client = app.test_client()
1770 app.config["SERVER_NAME"] = "localhost.localdomain"
1771
1772 @app.route("/", subdomain="<user>")
1773 def index(user):
1774 return f"index for {user}"
1775
1776 rv = client.get("/", "http://mitsuhiko.localhost.localdomain/")
1777 assert rv.data == b"index for mitsuhiko"
1778
1779
1780 def test_subdomain_matching_with_ports():
tests/test_basic.py 186

1781 app = flask.Flask(__name__, subdomain_matching=True)


1782 app.config["SERVER_NAME"] = "localhost.localdomain:3000"
1783 client = app.test_client()
1784
1785 @app.route("/", subdomain="<user>")
1786 def index(user):
1787 return f"index for {user}"
1788
1789 rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/")
1790 assert rv.data == b"index for mitsuhiko"
1791
1792
1793 @pytest.mark.parametrize("matching", (False, True))
1794 def test_subdomain_matching_other_name(matching):
1795 app = flask.Flask(__name__, subdomain_matching=matching)
1796 app.config["SERVER_NAME"] = "localhost.localdomain:3000"
1797 client = app.test_client()
1798
1799 @app.route("/")
1800 def index():
1801 return "", 204
1802
1803 # suppress Werkzeug 0.15 warning about name mismatch
1804 with warnings.catch_warnings():
1805 warnings.filterwarnings(
1806 "ignore", "Current server name", UserWarning, "flask.app"
1807 )
1808 # ip address can't match name
1809 rv = client.get("/", "http://127.0.0.1:3000/")
1810 assert rv.status_code == 404 if matching else 204
1811
1812 # allow all subdomains if matching is disabled
1813 rv = client.get("/", "http://www.localhost.localdomain:3000/")
1814 assert rv.status_code == 404 if matching else 204
1815
1816
1817 def test_multi_route_rules(app, client):
1818 @app.route("/")
1819 @app.route("/<test>/")
1820 def index(test="a"):
1821 return test
1822
1823 rv = client.open("/")
1824 assert rv.data == b"a"
1825 rv = client.open("/b/")
1826 assert rv.data == b"b"
1827
1828
1829 def test_multi_route_class_views(app, client):
1830 class View:
1831 def __init__(self, app):
1832 app.add_url_rule("/", "index", self.index)
1833 app.add_url_rule("/<test>/", "index", self.index)
1834
1835 def index(self, test="a"):
1836 return test
1837
1838 _ = View(app)
1839 rv = client.open("/")
1840 assert rv.data == b"a"
1841 rv = client.open("/b/")
1842 assert rv.data == b"b"
1843
1844
1845 def test_run_defaults(monkeypatch, app):
1846 rv = {}
1847
1848 # Mocks werkzeug.serving.run_simple method
1849 def run_simple_mock(*args, **kwargs):
1850 rv["result"] = "running..."
1851
tests/test_basic.py 187

1852 monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)


1853 app.run()
1854 assert rv["result"] == "running..."
1855
1856
1857 def test_run_server_port(monkeypatch, app):
1858 rv = {}
1859
1860 # Mocks werkzeug.serving.run_simple method
1861 def run_simple_mock(hostname, port, application, *args, **kwargs):
1862 rv["result"] = f"running on {hostname}:{port} ..."
1863
1864 monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
1865 hostname, port = "localhost", 8000
1866 app.run(hostname, port, debug=True)
1867 assert rv["result"] == f"running on {hostname}:{port} ..."
1868
1869
1870 @pytest.mark.parametrize(
1871 "host,port,server_name,expect_host,expect_port",
1872 (
1873 (None, None, "pocoo.org:8080", "pocoo.org", 8080),
1874 ("localhost", None, "pocoo.org:8080", "localhost", 8080),
1875 (None, 80, "pocoo.org:8080", "pocoo.org", 80),
1876 ("localhost", 80, "pocoo.org:8080", "localhost", 80),
1877 ("localhost", 0, "localhost:8080", "localhost", 0),
1878 (None, None, "localhost:8080", "localhost", 8080),
1879 (None, None, "localhost:0", "localhost", 0),
1880 ),
1881 )
1882 def test_run_from_config(
1883 monkeypatch, host, port, server_name, expect_host, expect_port, app
1884 ):
1885 def run_simple_mock(hostname, port, *args, **kwargs):
1886 assert hostname == expect_host
1887 assert port == expect_port
1888
1889 monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
1890 app.config["SERVER_NAME"] = server_name
1891 app.run(host, port)
1892
1893
1894 def test_max_cookie_size(app, client, recwarn):
1895 app.config["MAX_COOKIE_SIZE"] = 100
1896
1897 # outside app context, default to Werkzeug static value,
1898 # which is also the default config
1899 response = flask.Response()
1900 default = flask.Flask.default_config["MAX_COOKIE_SIZE"]
1901 assert response.max_cookie_size == default
1902
1903 # inside app context, use app config
1904 with app.app_context():
1905 assert flask.Response().max_cookie_size == 100
1906
1907 @app.route("/")
1908 def index():
1909 r = flask.Response("", status=204)
1910 r.set_cookie("foo", "bar" * 100)
1911 return r
1912
1913 client.get("/")
1914 assert len(recwarn) == 1
1915 w = recwarn.pop()
1916 assert "cookie is too large" in str(w.message)
1917
1918 app.config["MAX_COOKIE_SIZE"] = 0
1919
1920 client.get("/")
1921 assert len(recwarn) == 0
1922
188

1923
1924 @require_cpython_gc
1925 def test_app_freed_on_zero_refcount():
1926 # A Flask instance should not create a reference cycle that prevents CPython
1927 # from freeing it when all external references to it are released (see #3761).
1928 gc.disable()
1929 try:
1930 app = flask.Flask(__name__)
1931 assert app.view_functions["static"]
1932 weak = weakref.ref(app)
1933 assert weak() is not None
1934 del app
1935 assert weak() is None
1936 finally:
1937 gc.enable()

src/flask/py.typed
1

src/flask/__main__.py
1 from .cli import main
2
3 main()

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")

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
189

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 )

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
190

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 <flask.Flask.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

src/flask/__init__.py
1 from __future__ import annotations
2
3 import typing as t
4
5 from . import json as json
6 from .app import Flask as Flask
7 from .blueprints import Blueprint as Blueprint
8 from .config import Config as Config
9 from .ctx import after_this_request as after_this_request
10 from .ctx import copy_current_request_context as copy_current_request_context
11 from .ctx import has_app_context as has_app_context
12 from .ctx import has_request_context as has_request_context
13 from .globals import current_app as current_app
14 from .globals import g as g
15 from .globals import request as request
16 from .globals import session as session
17 from .helpers import abort as abort
18 from .helpers import flash as flash
19 from .helpers import get_flashed_messages as get_flashed_messages
20 from .helpers import get_template_attribute as get_template_attribute
191

21 from .helpers import make_response as make_response


22 from .helpers import redirect as redirect
23 from .helpers import send_file as send_file
24 from .helpers import send_from_directory as send_from_directory
25 from .helpers import stream_with_context as stream_with_context
26 from .helpers import url_for as url_for
27 from .json import jsonify as jsonify
28 from .signals import appcontext_popped as appcontext_popped
29 from .signals import appcontext_pushed as appcontext_pushed
30 from .signals import appcontext_tearing_down as appcontext_tearing_down
31 from .signals import before_render_template as before_render_template
32 from .signals import got_request_exception as got_request_exception
33 from .signals import message_flashed as message_flashed
34 from .signals import request_finished as request_finished
35 from .signals import request_started as request_started
36 from .signals import request_tearing_down as request_tearing_down
37 from .signals import template_rendered as template_rendered
38 from .templating import render_template as render_template
39 from .templating import render_template_string as render_template_string
40 from .templating import stream_template as stream_template
41 from .templating import stream_template_string as stream_template_string
42 from .wrappers import Request as Request
43 from .wrappers import Response as Response
44
45
46 def __getattr__(name: str) -> t.Any:
47 if name == "__version__":
48 import importlib.metadata
49 import warnings
50
51 warnings.warn(
52 "The '__version__' attribute is deprecated and will be removed in"
53 " Flask 3.1. Use feature detection or"
54 " 'importlib.metadata.version(\"flask\")' instead.",
55 DeprecationWarning,
56 stacklevel=2,
57 )
58 return importlib.metadata.version("flask")
59
60 raise AttributeError(name)

src/flask/typing.py
1 from __future__ import annotations
2
3 import typing as t
4
5 if t.TYPE_CHECKING: # pragma: no cover
6 from _typeshed.wsgi import WSGIApplication # noqa: F401
7 from werkzeug.datastructures import Headers # noqa: F401
8 from werkzeug.sansio.response import Response # noqa: F401
9
10 # The possible types that are directly convertible or are a Response object.
11 ResponseValue = t.Union[
12 "Response",
13 str,
14 bytes,
15 list[t.Any],
16 # Only dict is actually accepted, but Mapping allows for TypedDict.
17 t.Mapping[str, t.Any],
18 t.Iterator[str],
19 t.Iterator[bytes],
20 ]
21
22 # the possible types for an individual HTTP header
23 # This should be a Union, but mypy doesn't pass unless it's a TypeVar.
24 HeaderValue = t.Union[str, list[str], tuple[str, ...]]
25
26 # the possible types for HTTP headers
27 HeadersValue = t.Union[
192

28 "Headers",
29 t.Mapping[str, HeaderValue],
30 t.Sequence[tuple[str, HeaderValue]],
31 ]
32
33 # The possible types returned by a route function.
34 ResponseReturnValue = t.Union[
35 ResponseValue,
36 tuple[ResponseValue, HeadersValue],
37 tuple[ResponseValue, int],
38 tuple[ResponseValue, int, HeadersValue],
39 "WSGIApplication",
40 ]
41
42 # Allow any subclass of werkzeug.Response, such as the one from Flask,
43 # as a callback argument. Using werkzeug.Response directly makes a
44 # callback annotated with flask.Response fail type checking.
45 ResponseClass = t.TypeVar("ResponseClass", bound="Response")
46
47 AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
48 AfterRequestCallable = t.Union[
49 t.Callable[[ResponseClass], ResponseClass],
50 t.Callable[[ResponseClass], t.Awaitable[ResponseClass]],
51 ]
52 BeforeFirstRequestCallable = t.Union[
53 t.Callable[[], None], t.Callable[[], t.Awaitable[None]]
54 ]
55 BeforeRequestCallable = t.Union[
56 t.Callable[[], t.Optional[ResponseReturnValue]],
57 t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]],
58 ]
59 ShellContextProcessorCallable = t.Callable[[], dict[str, t.Any]]
60 TeardownCallable = t.Union[
61 t.Callable[[t.Optional[BaseException]], None],
62 t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
63 ]
64 TemplateContextProcessorCallable = t.Union[
65 t.Callable[[], dict[str, t.Any]],
66 t.Callable[[], t.Awaitable[dict[str, t.Any]]],
67 ]
68 TemplateFilterCallable = t.Callable[..., t.Any]
69 TemplateGlobalCallable = t.Callable[..., t.Any]
70 TemplateTestCallable = t.Callable[..., bool]
71 URLDefaultCallable = t.Callable[[str, dict[str, t.Any]], None]
72 URLValuePreprocessorCallable = t.Callable[
73 [t.Optional[str], t.Optional[dict[str, t.Any]]], None
74 ]
75
76 # This should take Exception, but that either breaks typing the argument
77 # with a specific exception, or decorating multiple times with different
78 # exceptions (and using a union type on the argument).
79 # https://github.com/pallets/flask/issues/4095
80 # https://github.com/pallets/flask/issues/4295
81 # https://github.com/pallets/flask/issues/4297
82 ErrorHandlerCallable = t.Union[
83 t.Callable[[t.Any], ResponseReturnValue],
84 t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]],
85 ]
86
87 RouteCallable = t.Union[
88 t.Callable[..., ResponseReturnValue],
89 t.Callable[..., t.Awaitable[ResponseReturnValue]],
90 ]

src/flask/blueprints.py
1 from __future__ import annotations
2
3 import os
4 import typing as t
src/flask/blueprints.py 193

5 from datetime import timedelta


6
7 from .cli import AppGroup
8 from .globals import current_app
9 from .helpers import send_from_directory
10 from .sansio.blueprints import Blueprint as SansioBlueprint
11 from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa
12 from .sansio.scaffold import _sentinel
13
14 if t.TYPE_CHECKING: # pragma: no cover
15 from .wrappers import Response
16
17
18 class Blueprint(SansioBlueprint):
19 def __init__(
20 self,
21 name: str,
22 import_name: str,
23 static_folder: str | os.PathLike[str] | None = None,
24 static_url_path: str | None = None,
25 template_folder: str | os.PathLike[str] | None = None,
26 url_prefix: str | None = None,
27 subdomain: str | None = None,
28 url_defaults: dict[str, t.Any] | None = None,
29 root_path: str | None = None,
30 cli_group: str | None = _sentinel, # type: ignore
31 ) -> None:
32 super().__init__(
33 name,
34 import_name,
35 static_folder,
36 static_url_path,
37 template_folder,
38 url_prefix,
39 subdomain,
40 url_defaults,
41 root_path,
42 cli_group,
43 )
44
45 #: The Click command group for registering CLI commands for this
46 #: object. The commands are available from the ``flask`` command
47 #: once the application has been discovered and blueprints have
48 #: been registered.
49 self.cli = AppGroup()
50
51 # Set the name of the Click group in case someone wants to add
52 # the app's commands to another CLI tool.
53 self.cli.name = self.name
54
55 def get_send_file_max_age(self, filename: str | None) -> int | None:
56 """Used by :func:`send_file` to determine the ``max_age`` cache
57 value for a given file path if it wasn't passed.
58
59 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
60 the configuration of :data:`~flask.current_app`. This defaults
61 to ``None``, which tells the browser to use conditional requests
62 instead of a timed cache, which is usually preferable.
63
64 Note this is a duplicate of the same method in the Flask
65 class.
66
67 .. versionchanged:: 2.0
68 The default configuration is ``None`` instead of 12 hours.
69
70 .. versionadded:: 0.9
71 """
72 value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
73
74 if value is None:
75 return None
194

76
77 if isinstance(value, timedelta):
78 return int(value.total_seconds())
79
80 return value # type: ignore[no-any-return]
81
82 def send_static_file(self, filename: str) -> Response:
83 """The view function used to serve files from
84 :attr:`static_folder`. A route is automatically registered for
85 this view at :attr:`static_url_path` if :attr:`static_folder` is
86 set.
87
88 Note this is a duplicate of the same method in the Flask
89 class.
90
91 .. versionadded:: 0.5
92
93 """
94 if not self.has_static_folder:
95 raise RuntimeError("'static_folder' must be set to serve static_files.")
96
97 # send_file only knows to call get_send_file_max_age on the app,
98 # call it here so it works for blueprints too.
99 max_age = self.get_send_file_max_age(filename)
100 return send_from_directory(
101 t.cast(str, self.static_folder), filename, max_age=max_age
102 )
103
104 def open_resource(
105 self, resource: str, mode: str = "rb", encoding: str | None = "utf-8"
106 ) -> t.IO[t.AnyStr]:
107 """Open a resource file relative to :attr:`root_path` for reading. The
108 blueprint-relative equivalent of the app's :meth:`~.Flask.open_resource`
109 method.
110
111 :param resource: Path to the resource relative to :attr:`root_path`.
112 :param mode: Open the file in this mode. Only reading is supported,
113 valid values are ``"r"`` (or ``"rt"``) and ``"rb"``.
114 :param encoding: Open the file with this encoding when opening in text
115 mode. This is ignored when opening in binary mode.
116
117 .. versionchanged:: 3.1
118 Added the ``encoding`` parameter.
119 """
120 if mode not in {"r", "rt", "rb"}:
121 raise ValueError("Resources can only be opened for reading.")
122
123 path = os.path.join(self.root_path, resource)
124
125 if mode == "rb":
126 return open(path, mode) # pyright: ignore
127
128 return open(path, mode, encoding=encoding)

src/flask/debughelpers.py
1 from __future__ import annotations
2
3 import typing as t
4
5 from jinja2.loaders import BaseLoader
6 from werkzeug.routing import RequestRedirect
7
8 from .blueprints import Blueprint
9 from .globals import request_ctx
10 from .sansio.app import App
11
12 if t.TYPE_CHECKING:
13 from .sansio.scaffold import Scaffold
14 from .wrappers import Request
src/flask/debughelpers.py 195

15
16
17 class UnexpectedUnicodeError(AssertionError, UnicodeError):
18 """Raised in places where we want some better error reporting for
19 unexpected unicode or binary data.
20 """
21
22
23 class DebugFilesKeyError(KeyError, AssertionError):
24 """Raised from request.files during debugging. The idea is that it can
25 provide a better error message than just a generic KeyError/BadRequest.
26 """
27
28 def __init__(self, request: Request, key: str) -> None:
29 form_matches = request.form.getlist(key)
30 buf = [
31 f"You tried to access the file {key!r} in the request.files"
32 " dictionary but it does not exist. The mimetype for the"
33 f" request is {request.mimetype!r} instead of"
34 " 'multipart/form-data' which means that no file contents"
35 " were transmitted. To fix this error you should provide"
36 ' enctype="multipart/form-data" in your form.'
37 ]
38 if form_matches:
39 names = ", ".join(repr(x) for x in form_matches)
40 buf.append(
41 "\n\nThe browser instead transmitted some file names. "
42 f"This was submitted: {names}"
43 )
44 self.msg = "".join(buf)
45
46 def __str__(self) -> str:
47 return self.msg
48
49
50 class FormDataRoutingRedirect(AssertionError):
51 """This exception is raised in debug mode if a routing redirect
52 would cause the browser to drop the method or body. This happens
53 when method is not GET, HEAD or OPTIONS and the status code is not
54 307 or 308.
55 """
56
57 def __init__(self, request: Request) -> None:
58 exc = request.routing_exception
59 assert isinstance(exc, RequestRedirect)
60 buf = [
61 f"A request was sent to '{request.url}', but routing issued"
62 f" a redirect to the canonical URL '{exc.new_url}'."
63 ]
64
65 if f"{request.base_url}/" == exc.new_url.partition("?")[0]:
66 buf.append(
67 " The URL was defined with a trailing slash. Flask"
68 " will redirect to the URL with a trailing slash if it"
69 " was accessed without one."
70 )
71
72 buf.append(
73 " Send requests to the canonical URL, or use 307 or 308 for"
74 " routing redirects. Otherwise, browsers will drop form"
75 " data.\n\n"
76 "This exception is only raised in debug mode."
77 )
78 super().__init__("".join(buf))
79
80
81 def attach_enctype_error_multidict(request: Request) -> None:
82 """Patch ``request.files.__getitem__`` to raise a descriptive error
83 about ``enctype=multipart/form-data``.
84
85 :param request: The request to patch.
src/flask/debughelpers.py 196

86 :meta private:
87 """
88 oldcls = request.files.__class__
89
90 class newcls(oldcls): # type: ignore[valid-type, misc]
91 def __getitem__(self, key: str) -> t.Any:
92 try:
93 return super().__getitem__(key)
94 except KeyError as e:
95 if key not in request.form:
96 raise
97
98 raise DebugFilesKeyError(request, key).with_traceback(
99 e.__traceback__
100 ) from None
101
102 newcls.__name__ = oldcls.__name__
103 newcls.__module__ = oldcls.__module__
104 request.files.__class__ = newcls
105
106
107 def _dump_loader_info(loader: BaseLoader) -> t.Iterator[str]:
108 yield f"class: {type(loader).__module__}.{type(loader).__name__}"
109 for key, value in sorted(loader.__dict__.items()):
110 if key.startswith("_"):
111 continue
112 if isinstance(value, (tuple, list)):
113 if not all(isinstance(x, str) for x in value):
114 continue
115 yield f"{key}:"
116 for item in value:
117 yield f" - {item}"
118 continue
119 elif not isinstance(value, (str, int, float, bool)):
120 continue
121 yield f"{key}: {value!r}"
122
123
124 def explain_template_loading_attempts(
125 app: App,
126 template: str,
127 attempts: list[
128 tuple[
129 BaseLoader,
130 Scaffold,
131 tuple[str, str | None, t.Callable[[], bool] | None] | None,
132 ]
133 ],
134 ) -> None:
135 """This should help developers understand what failed"""
136 info = [f"Locating template {template!r}:"]
137 total_found = 0
138 blueprint = None
139 if request_ctx and request_ctx.request.blueprint is not None:
140 blueprint = request_ctx.request.blueprint
141
142 for idx, (loader, srcobj, triple) in enumerate(attempts):
143 if isinstance(srcobj, App):
144 src_info = f"application {srcobj.import_name!r}"
145 elif isinstance(srcobj, Blueprint):
146 src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
147 else:
148 src_info = repr(srcobj)
149
150 info.append(f"{idx + 1:5}: trying loader of {src_info}")
151
152 for line in _dump_loader_info(loader):
153 info.append(f" {line}")
154
155 if triple is None:
156 detail = "no match"
197

157 else:
158 detail = f"found ({triple[1] or '<string>'!r})"
159 total_found += 1
160 info.append(f" -> {detail}")
161
162 seems_fishy = False
163 if total_found == 0:
164 info.append("Error: the template could not be found.")
165 seems_fishy = True
166 elif total_found > 1:
167 info.append("Warning: multiple loaders returned a match for the template.")
168 seems_fishy = True
169
170 if blueprint is not None and seems_fishy:
171 info.append(
172 " The template was looked up from an endpoint that belongs"
173 f" to the blueprint {blueprint!r}."
174 )
175 info.append(" Maybe you did not place a template in the right folder?")
176 info.append(" See https://flask.palletsprojects.com/blueprints/#templates")
177
178 app.logger.info("\n".join(info))

src/flask/views.py
1 from __future__ import annotations
2
3 import typing as t
4
5 from . import typing as ft
6 from .globals import current_app
7 from .globals import request
8
9 F = t.TypeVar("F", bound=t.Callable[..., t.Any])
10
11 http_method_funcs = frozenset(
12 ["get", "post", "head", "options", "delete", "put", "trace", "patch"]
13 )
14
15
16 class View:
17 """Subclass this class and override :meth:`dispatch_request` to
18 create a generic class-based view. Call :meth:`as_view` to create a
19 view function that creates an instance of the class with the given
20 arguments and calls its ``dispatch_request`` method with any URL
21 variables.
22
23 See :doc:`views` for a detailed guide.
24
25 .. code-block:: python
26
27 class Hello(View):
28 init_every_request = False
29
30 def dispatch_request(self, name):
31 return f"Hello, {name}!"
32
33 app.add_url_rule(
34 "/hello/<name>", view_func=Hello.as_view("hello")
35 )
36
37 Set :attr:`methods` on the class to change what methods the view
38 accepts.
39
40 Set :attr:`decorators` on the class to apply a list of decorators to
41 the generated view function. Decorators applied to the class itself
42 will not be applied to the generated view function!
43
44 Set :attr:`init_every_request` to ``False`` for efficiency, unless
45 you need to store request-global data on ``self``.
src/flask/views.py 198

46 """
47
48 #: The methods this view is registered for. Uses the same default
49 #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and
50 #: ``add_url_rule`` by default.
51 methods: t.ClassVar[t.Collection[str] | None] = None
52
53 #: Control whether the ``OPTIONS`` method is handled automatically.
54 #: Uses the same default (``True``) as ``route`` and
55 #: ``add_url_rule`` by default.
56 provide_automatic_options: t.ClassVar[bool | None] = None
57
58 #: A list of decorators to apply, in order, to the generated view
59 #: function. Remember that ``@decorator`` syntax is applied bottom
60 #: to top, so the first decorator in the list would be the bottom
61 #: decorator.
62 #:
63 #: .. versionadded:: 0.8
64 decorators: t.ClassVar[list[t.Callable[..., t.Any]]] = []
65
66 #: Create a new instance of this view class for every request by
67 #: default. If a view subclass sets this to ``False``, the same
68 #: instance is used for every request.
69 #:
70 #: A single instance is more efficient, especially if complex setup
71 #: is done during init. However, storing data on ``self`` is no
72 #: longer safe across requests, and :data:`~flask.g` should be used
73 #: instead.
74 #:
75 #: .. versionadded:: 2.2
76 init_every_request: t.ClassVar[bool] = True
77
78 def dispatch_request(self) -> ft.ResponseReturnValue:
79 """The actual view function behavior. Subclasses must override
80 this and return a valid response. Any variables from the URL
81 rule are passed as keyword arguments.
82 """
83 raise NotImplementedError()
84
85 @classmethod
86 def as_view(
87 cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
88 ) -> ft.RouteCallable:
89 """Convert the class into a view function that can be registered
90 for a route.
91
92 By default, the generated view will create a new instance of the
93 view class for every request and call its
94 :meth:`dispatch_request` method. If the view class sets
95 :attr:`init_every_request` to ``False``, the same instance will
96 be used for every request.
97
98 Except for ``name``, all other arguments passed to this method
99 are forwarded to the view class ``__init__`` method.
100
101 .. versionchanged:: 2.2
102 Added the ``init_every_request`` class attribute.
103 """
104 if cls.init_every_request:
105
106 def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
107 self = view.view_class( # type: ignore[attr-defined]
108 *class_args, **class_kwargs
109 )
110 return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return]
111
112 else:
113 self = cls(*class_args, **class_kwargs) # pyright: ignore
114
115 def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
116 return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return]
src/flask/views.py 199

117
118 if cls.decorators:
119 view.__name__ = name
120 view.__module__ = cls.__module__
121 for decorator in cls.decorators:
122 view = decorator(view)
123
124 # We attach the view class to the view function for two reasons:
125 # first of all it allows us to easily figure out what class-based
126 # view this thing came from, secondly it's also used for instantiating
127 # the view class so you can actually replace it with something else
128 # for testing purposes and debugging.
129 view.view_class = cls # type: ignore
130 view.__name__ = name
131 view.__doc__ = cls.__doc__
132 view.__module__ = cls.__module__
133 view.methods = cls.methods # type: ignore
134 view.provide_automatic_options = cls.provide_automatic_options # type: ignore
135 return view
136
137
138 class MethodView(View):
139 """Dispatches request methods to the corresponding instance methods.
140 For example, if you implement a ``get`` method, it will be used to
141 handle ``GET`` requests.
142
143 This can be useful for defining a REST API.
144
145 :attr:`methods` is automatically set based on the methods defined on
146 the class.
147
148 See :doc:`views` for a detailed guide.
149
150 .. code-block:: python
151
152 class CounterAPI(MethodView):
153 def get(self):
154 return str(session.get("counter", 0))
155
156 def post(self):
157 session["counter"] = session.get("counter", 0) + 1
158 return redirect(url_for("counter"))
159
160 app.add_url_rule(
161 "/counter", view_func=CounterAPI.as_view("counter")
162 )
163 """
164
165 def __init_subclass__(cls, **kwargs: t.Any) -> None:
166 super().__init_subclass__(**kwargs)
167
168 if "methods" not in cls.__dict__:
169 methods = set()
170
171 for base in cls.__bases__:
172 if getattr(base, "methods", None):
173 methods.update(base.methods) # type: ignore[attr-defined]
174
175 for key in http_method_funcs:
176 if hasattr(cls, key):
177 methods.add(key.upper())
178
179 if methods:
180 cls.methods = methods
181
182 def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
183 meth = getattr(self, request.method.lower(), None)
184
185 # If the request method is HEAD and we don't have a handler for it
186 # retry with GET.
187 if meth is None and request.method == "HEAD":
200

188 meth = getattr(self, "get", None)


189
190 assert meth is not None, f"Unimplemented method {request.method!r}"
191 return current_app.ensure_sync(meth)(**kwargs) # type: ignore[no-any-return]

src/flask/templating.py
1 from __future__ import annotations
2
3 import typing as t
4
5 from jinja2 import BaseLoader
6 from jinja2 import Environment as BaseEnvironment
7 from jinja2 import Template
8 from jinja2 import TemplateNotFound
9
10 from .globals import _cv_app
11 from .globals import _cv_request
12 from .globals import current_app
13 from .globals import request
14 from .helpers import stream_with_context
15 from .signals import before_render_template
16 from .signals import template_rendered
17
18 if t.TYPE_CHECKING: # pragma: no cover
19 from .app import Flask
20 from .sansio.app import App
21 from .sansio.scaffold import Scaffold
22
23
24 def _default_template_ctx_processor() -> dict[str, t.Any]:
25 """Default template context processor. Injects `request`,
26 `session` and `g`.
27 """
28 appctx = _cv_app.get(None)
29 reqctx = _cv_request.get(None)
30 rv: dict[str, t.Any] = {}
31 if appctx is not None:
32 rv["g"] = appctx.g
33 if reqctx is not None:
34 rv["request"] = reqctx.request
35 rv["session"] = reqctx.session
36 return rv
37
38
39 class Environment(BaseEnvironment):
40 """Works like a regular Jinja2 environment but has some additional
41 knowledge of how Flask's blueprint works so that it can prepend the
42 name of the blueprint to referenced templates if necessary.
43 """
44
45 def __init__(self, app: App, **options: t.Any) -> None:
46 if "loader" not in options:
47 options["loader"] = app.create_global_jinja_loader()
48 BaseEnvironment.__init__(self, **options)
49 self.app = app
50
51
52 class DispatchingJinjaLoader(BaseLoader):
53 """A loader that looks for templates in the application and all
54 the blueprint folders.
55 """
56
57 def __init__(self, app: App) -> None:
58 self.app = app
59
60 def get_source(
61 self, environment: BaseEnvironment, template: str
62 ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
63 if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
src/flask/templating.py 201

64 return self._get_source_explained(environment, template)


65 return self._get_source_fast(environment, template)
66
67 def _get_source_explained(
68 self, environment: BaseEnvironment, template: str
69 ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
70 attempts = []
71 rv: tuple[str, str | None, t.Callable[[], bool] | None] | None
72 trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None
73
74 for srcobj, loader in self._iter_loaders(template):
75 try:
76 rv = loader.get_source(environment, template)
77 if trv is None:
78 trv = rv
79 except TemplateNotFound:
80 rv = None
81 attempts.append((loader, srcobj, rv))
82
83 from .debughelpers import explain_template_loading_attempts
84
85 explain_template_loading_attempts(self.app, template, attempts)
86
87 if trv is not None:
88 return trv
89 raise TemplateNotFound(template)
90
91 def _get_source_fast(
92 self, environment: BaseEnvironment, template: str
93 ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
94 for _srcobj, loader in self._iter_loaders(template):
95 try:
96 return loader.get_source(environment, template)
97 except TemplateNotFound:
98 continue
99 raise TemplateNotFound(template)
100
101 def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]:
102 loader = self.app.jinja_loader
103 if loader is not None:
104 yield self.app, loader
105
106 for blueprint in self.app.iter_blueprints():
107 loader = blueprint.jinja_loader
108 if loader is not None:
109 yield blueprint, loader
110
111 def list_templates(self) -> list[str]:
112 result = set()
113 loader = self.app.jinja_loader
114 if loader is not None:
115 result.update(loader.list_templates())
116
117 for blueprint in self.app.iter_blueprints():
118 loader = blueprint.jinja_loader
119 if loader is not None:
120 for template in loader.list_templates():
121 result.add(template)
122
123 return list(result)
124
125
126 def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str:
127 app.update_template_context(context)
128 before_render_template.send(
129 app, _async_wrapper=app.ensure_sync, template=template, context=context
130 )
131 rv = template.render(context)
132 template_rendered.send(
133 app, _async_wrapper=app.ensure_sync, template=template, context=context
134 )
src/flask/templating.py 202

135 return rv
136
137
138 def render_template(
139 template_name_or_list: str | Template | list[str | Template],
140 **context: t.Any,
141 ) -> str:
142 """Render a template by name with the given context.
143
144 :param template_name_or_list: The name of the template to render. If
145 a list is given, the first name to exist will be rendered.
146 :param context: The variables to make available in the template.
147 """
148 app = current_app._get_current_object() # type: ignore[attr-defined]
149 template = app.jinja_env.get_or_select_template(template_name_or_list)
150 return _render(app, template, context)
151
152
153 def render_template_string(source: str, **context: t.Any) -> str:
154 """Render a template from the given source string with the given
155 context.
156
157 :param source: The source code of the template to render.
158 :param context: The variables to make available in the template.
159 """
160 app = current_app._get_current_object() # type: ignore[attr-defined]
161 template = app.jinja_env.from_string(source)
162 return _render(app, template, context)
163
164
165 def _stream(
166 app: Flask, template: Template, context: dict[str, t.Any]
167 ) -> t.Iterator[str]:
168 app.update_template_context(context)
169 before_render_template.send(
170 app, _async_wrapper=app.ensure_sync, template=template, context=context
171 )
172
173 def generate() -> t.Iterator[str]:
174 yield from template.generate(context)
175 template_rendered.send(
176 app, _async_wrapper=app.ensure_sync, template=template, context=context
177 )
178
179 rv = generate()
180
181 # If a request context is active, keep it while generating.
182 if request:
183 rv = stream_with_context(rv)
184
185 return rv
186
187
188 def stream_template(
189 template_name_or_list: str | Template | list[str | Template],
190 **context: t.Any,
191 ) -> t.Iterator[str]:
192 """Render a template by name with the given context as a stream.
193 This returns an iterator of strings, which can be used as a
194 streaming response from a view.
195
196 :param template_name_or_list: The name of the template to render. If
197 a list is given, the first name to exist will be rendered.
198 :param context: The variables to make available in the template.
199
200 .. versionadded:: 2.2
201 """
202 app = current_app._get_current_object() # type: ignore[attr-defined]
203 template = app.jinja_env.get_or_select_template(template_name_or_list)
204 return _stream(app, template, context)
205
203

206
207 def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:
208 """Render a template from the given source string with the given
209 context as a stream. This returns an iterator of strings, which can
210 be used as a streaming response from a view.
211
212 :param source: The source code of the template to render.
213 :param context: The variables to make available in the template.
214
215 .. versionadded:: 2.2
216 """
217 app = current_app._get_current_object() # type: ignore[attr-defined]
218 template = app.jinja_env.from_string(source)
219 return _stream(app, template, context)

src/flask/wrappers.py
1 from __future__ import annotations
2
3 import typing as t
4
5 from werkzeug.exceptions import BadRequest
6 from werkzeug.exceptions import HTTPException
7 from werkzeug.wrappers import Request as RequestBase
8 from werkzeug.wrappers import Response as ResponseBase
9
10 from . import json
11 from .globals import current_app
12 from .helpers import _split_blueprint_path
13
14 if t.TYPE_CHECKING: # pragma: no cover
15 from werkzeug.routing import Rule
16
17
18 class Request(RequestBase):
19 """The request object used by default in Flask. Remembers the
20 matched endpoint and view arguments.
21
22 It is what ends up as :class:`~flask.request`. If you want to replace
23 the request object used you can subclass this and set
24 :attr:`~flask.Flask.request_class` to your subclass.
25
26 The request object is a :class:`~werkzeug.wrappers.Request` subclass and
27 provides all of the attributes Werkzeug defines plus a few Flask
28 specific ones.
29 """
30
31 json_module: t.Any = json
32
33 #: The internal URL rule that matched the request. This can be
34 #: useful to inspect which methods are allowed for the URL from
35 #: a before/after handler (``request.url_rule.methods``) etc.
36 #: Though if the request's method was invalid for the URL rule,
37 #: the valid list is available in ``routing_exception.valid_methods``
38 #: instead (an attribute of the Werkzeug exception
39 #: :exc:`~werkzeug.exceptions.MethodNotAllowed`)
40 #: because the request was never internally bound.
41 #:
42 #: .. versionadded:: 0.6
43 url_rule: Rule | None = None
44
45 #: A dict of view arguments that matched the request. If an exception
46 #: happened when matching, this will be ``None``.
47 view_args: dict[str, t.Any] | None = None
48
49 #: If matching the URL failed, this is the exception that will be
50 #: raised / was raised as part of the request handling. This is
51 #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
52 #: something similar.
53 routing_exception: HTTPException | None = None
src/flask/wrappers.py 204

54
55 _max_content_length: int | None = None
56 _max_form_memory_size: int | None = None
57 _max_form_parts: int | None = None
58
59 @property
60 def max_content_length(self) -> int | None:
61 """The maximum number of bytes that will be read during this request. If
62 this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge`
63 error is raised. If it is set to ``None``, no limit is enforced at the
64 Flask application level. However, if it is ``None`` and the request has
65 no ``Content-Length`` header and the WSGI server does not indicate that
66 it terminates the stream, then no data is read to avoid an infinite
67 stream.
68
69 Each request defaults to the :data:`MAX_CONTENT_LENGTH` config, which
70 defaults to ``None``. It can be set on a specific ``request`` to apply
71 the limit to that specific view. This should be set appropriately based
72 on an application's or view's specific needs.
73
74 .. versionchanged:: 3.1
75 This can be set per-request.
76
77 .. versionchanged:: 0.6
78 This is configurable through Flask config.
79 """
80 if self._max_content_length is not None:
81 return self._max_content_length
82
83 if not current_app:
84 return super().max_content_length
85
86 return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return]
87
88 @max_content_length.setter
89 def max_content_length(self, value: int | None) -> None:
90 self._max_content_length = value
91
92 @property
93 def max_form_memory_size(self) -> int | None:
94 """The maximum size in bytes any non-file form field may be in a
95 ``multipart/form-data`` body. If this limit is exceeded, a 413
96 :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it
97 is set to ``None``, no limit is enforced at the Flask application level.
98
99 Each request defaults to the :data:`MAX_FORM_MEMORY_SIZE` config, which
100 defaults to ``500_000``. It can be set on a specific ``request`` to
101 apply the limit to that specific view. This should be set appropriately
102 based on an application's or view's specific needs.
103
104 .. versionchanged:: 3.1
105 This is configurable through Flask config.
106 """
107 if self._max_form_memory_size is not None:
108 return self._max_form_memory_size
109
110 if not current_app:
111 return super().max_form_memory_size
112
113 return current_app.config["MAX_FORM_MEMORY_SIZE"] # type: ignore[no-any-return]
114
115 @max_form_memory_size.setter
116 def max_form_memory_size(self, value: int | None) -> None:
117 self._max_form_memory_size = value
118
119 @property # type: ignore[override]
120 def max_form_parts(self) -> int | None:
121 """The maximum number of fields that may be present in a
122 ``multipart/form-data`` body. If this limit is exceeded, a 413
123 :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it
124 is set to ``None``, no limit is enforced at the Flask application level.
src/flask/wrappers.py 205

125
126 Each request defaults to the :data:`MAX_FORM_PARTS` config, which
127 defaults to ``1_000``. It can be set on a specific ``request`` to apply
128 the limit to that specific view. This should be set appropriately based
129 on an application's or view's specific needs.
130
131 .. versionchanged:: 3.1
132 This is configurable through Flask config.
133 """
134 if self._max_form_parts is not None:
135 return self._max_form_parts
136
137 if not current_app:
138 return super().max_form_parts
139
140 return current_app.config["MAX_FORM_PARTS"] # type: ignore[no-any-return]
141
142 @max_form_parts.setter
143 def max_form_parts(self, value: int | None) -> None:
144 self._max_form_parts = value
145
146 @property
147 def endpoint(self) -> str | None:
148 """The endpoint that matched the request URL.
149
150 This will be ``None`` if matching failed or has not been
151 performed yet.
152
153 This in combination with :attr:`view_args` can be used to
154 reconstruct the same URL or a modified URL.
155 """
156 if self.url_rule is not None:
157 return self.url_rule.endpoint # type: ignore[no-any-return]
158
159 return None
160
161 @property
162 def blueprint(self) -> str | None:
163 """The registered name of the current blueprint.
164
165 This will be ``None`` if the endpoint is not part of a
166 blueprint, or if URL matching failed or has not been performed
167 yet.
168
169 This does not necessarily match the name the blueprint was
170 created with. It may have been nested, or registered with a
171 different name.
172 """
173 endpoint = self.endpoint
174
175 if endpoint is not None and "." in endpoint:
176 return endpoint.rpartition(".")[0]
177
178 return None
179
180 @property
181 def blueprints(self) -> list[str]:
182 """The registered names of the current blueprint upwards through
183 parent blueprints.
184
185 This will be an empty list if there is no current blueprint, or
186 if URL matching failed.
187
188 .. versionadded:: 2.0.1
189 """
190 name = self.blueprint
191
192 if name is None:
193 return []
194
195 return _split_blueprint_path(name)
206

196
197 def _load_form_data(self) -> None:
198 super()._load_form_data()
199
200 # In debug mode we're replacing the files multidict with an ad-hoc
201 # subclass that raises a different error for key errors.
202 if (
203 current_app
204 and current_app.debug
205 and self.mimetype != "multipart/form-data"
206 and not self.files
207 ):
208 from .debughelpers import attach_enctype_error_multidict
209
210 attach_enctype_error_multidict(self)
211
212 def on_json_loading_failed(self, e: ValueError | None) -> t.Any:
213 try:
214 return super().on_json_loading_failed(e)
215 except BadRequest as ebr:
216 if current_app and current_app.debug:
217 raise
218
219 raise BadRequest() from ebr
220
221
222 class Response(ResponseBase):
223 """The response object that is used by default in Flask. Works like the
224 response object from Werkzeug but is set to have an HTML mimetype by
225 default. Quite often you don't have to create this object yourself because
226 :meth:`~flask.Flask.make_response` will take care of that for you.
227
228 If you want to replace the response object used you can subclass this and
229 set :attr:`~flask.Flask.response_class` to your subclass.
230
231 .. versionchanged:: 1.0
232 JSON support is added to the response, like the request. This is useful
233 when testing to get the test client response data as JSON.
234
235 .. versionchanged:: 1.0
236
237 Added :attr:`max_cookie_size`.
238 """
239
240 default_mimetype: str | None = "text/html"
241
242 json_module = json
243
244 autocorrect_location_header = False
245
246 @property
247 def max_cookie_size(self) -> int: # type: ignore
248 """Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
249
250 See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in
251 Werkzeug's docs.
252 """
253 if current_app:
254 return current_app.config["MAX_COOKIE_SIZE"] # type: ignore[no-any-return]
255
256 # return Werkzeug's default when not in an app context
257 return super().max_cookie_size

src/flask/testing.py
1 from __future__ import annotations
2
3 import importlib.metadata
4 import typing as t
5 from contextlib import contextmanager
src/flask/testing.py 207

6 from contextlib import ExitStack


7 from copy import copy
8 from types import TracebackType
9 from urllib.parse import urlsplit
10
11 import werkzeug.test
12 from click.testing import CliRunner
13 from werkzeug.test import Client
14 from werkzeug.wrappers import Request as BaseRequest
15
16 from .cli import ScriptInfo
17 from .sessions import SessionMixin
18
19 if t.TYPE_CHECKING: # pragma: no cover
20 from _typeshed.wsgi import WSGIEnvironment
21 from werkzeug.test import TestResponse
22
23 from .app import Flask
24
25
26 class EnvironBuilder(werkzeug.test.EnvironBuilder):
27 """An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the
28 application.
29
30 :param app: The Flask application to configure the environment from.
31 :param path: URL path being requested.
32 :param base_url: Base URL where the app is being served, which
33 ``path`` is relative to. If not given, built from
34 :data:`PREFERRED_URL_SCHEME`, ``subdomain``,
35 :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
36 :param subdomain: Subdomain name to append to :data:`SERVER_NAME`.
37 :param url_scheme: Scheme to use instead of
38 :data:`PREFERRED_URL_SCHEME`.
39 :param json: If given, this is serialized as JSON and passed as
40 ``data``. Also defaults ``content_type`` to
41 ``application/json``.
42 :param args: other positional arguments passed to
43 :class:`~werkzeug.test.EnvironBuilder`.
44 :param kwargs: other keyword arguments passed to
45 :class:`~werkzeug.test.EnvironBuilder`.
46 """
47
48 def __init__(
49 self,
50 app: Flask,
51 path: str = "/",
52 base_url: str | None = None,
53 subdomain: str | None = None,
54 url_scheme: str | None = None,
55 *args: t.Any,
56 **kwargs: t.Any,
57 ) -> None:
58 assert not (base_url or subdomain or url_scheme) or (
59 base_url is not None
60 ) != bool(
61 subdomain or url_scheme
62 ), 'Cannot pass "subdomain" or "url_scheme" with "base_url".'
63
64 if base_url is None:
65 http_host = app.config.get("SERVER_NAME") or "localhost"
66 app_root = app.config["APPLICATION_ROOT"]
67
68 if subdomain:
69 http_host = f"{subdomain}.{http_host}"
70
71 if url_scheme is None:
72 url_scheme = app.config["PREFERRED_URL_SCHEME"]
73
74 url = urlsplit(path)
75 base_url = (
76 f"{url.scheme or url_scheme}://{url.netloc or http_host}"
src/flask/testing.py 208

77 f"/{app_root.lstrip('/')}"
78 )
79 path = url.path
80
81 if url.query:
82 path = f"{path}?{url.query}"
83
84 self.app = app
85 super().__init__(path, base_url, *args, **kwargs)
86
87 def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: # type: ignore
88 """Serialize ``obj`` to a JSON-formatted string.
89
90 The serialization will be configured according to the config associated
91 with this EnvironBuilder's ``app``.
92 """
93 return self.app.json.dumps(obj, **kwargs)
94
95
96 _werkzeug_version = ""
97
98
99 def _get_werkzeug_version() -> str:
100 global _werkzeug_version
101
102 if not _werkzeug_version:
103 _werkzeug_version = importlib.metadata.version("werkzeug")
104
105 return _werkzeug_version
106
107
108 class FlaskClient(Client):
109 """Works like a regular Werkzeug test client but has knowledge about
110 Flask's contexts to defer the cleanup of the request context until
111 the end of a ``with`` block. For general information about how to
112 use this class refer to :class:`werkzeug.test.Client`.
113
114 .. versionchanged:: 0.12
115 `app.test_client()` includes preset default environment, which can be
116 set after instantiation of the `app.test_client()` object in
117 `client.environ_base`.
118
119 Basic usage is outlined in the :doc:`/testing` chapter.
120 """
121
122 application: Flask
123
124 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
125 super().__init__(*args, **kwargs)
126 self.preserve_context = False
127 self._new_contexts: list[t.ContextManager[t.Any]] = []
128 self._context_stack = ExitStack()
129 self.environ_base = {
130 "REMOTE_ADDR": "127.0.0.1",
131 "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}",
132 }
133
134 @contextmanager
135 def session_transaction(
136 self, *args: t.Any, **kwargs: t.Any
137 ) -> t.Iterator[SessionMixin]:
138 """When used in combination with a ``with`` statement this opens a
139 session transaction. This can be used to modify the session that
140 the test client uses. Once the ``with`` block is left the session is
141 stored back.
142
143 ::
144
145 with client.session_transaction() as session:
146 session['value'] = 42
147
src/flask/testing.py 209

148 Internally this is implemented by going through a temporary test


149 request context and since session handling could depend on
150 request variables this function accepts the same arguments as
151 :meth:`~flask.Flask.test_request_context` which are directly
152 passed through.
153 """
154 if self._cookies is None:
155 raise TypeError(
156 "Cookies are disabled. Create a client with 'use_cookies=True'."
157 )
158
159 app = self.application
160 ctx = app.test_request_context(*args, **kwargs)
161 self._add_cookies_to_wsgi(ctx.request.environ)
162
163 with ctx:
164 sess = app.session_interface.open_session(app, ctx.request)
165
166 if sess is None:
167 raise RuntimeError("Session backend did not open a session.")
168
169 yield sess
170 resp = app.response_class()
171
172 if app.session_interface.is_null_session(sess):
173 return
174
175 with ctx:
176 app.session_interface.save_session(app, sess, resp)
177
178 self._update_cookies_from_response(
179 ctx.request.host.partition(":")[0],
180 ctx.request.path,
181 resp.headers.getlist("Set-Cookie"),
182 )
183
184 def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment:
185 out = {**self.environ_base, **other}
186
187 if self.preserve_context:
188 out["werkzeug.debug.preserve_context"] = self._new_contexts.append
189
190 return out
191
192 def _request_from_builder_args(
193 self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
194 ) -> BaseRequest:
195 kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {}))
196 builder = EnvironBuilder(self.application, *args, **kwargs)
197
198 try:
199 return builder.get_request()
200 finally:
201 builder.close()
202
203 def open(
204 self,
205 *args: t.Any,
206 buffered: bool = False,
207 follow_redirects: bool = False,
208 **kwargs: t.Any,
209 ) -> TestResponse:
210 if args and isinstance(
211 args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest)
212 ):
213 if isinstance(args[0], werkzeug.test.EnvironBuilder):
214 builder = copy(args[0])
215 builder.environ_base = self._copy_environ(builder.environ_base or {}) # type: ignore[arg-type]
216 request = builder.get_request()
217 elif isinstance(args[0], dict):
218 request = EnvironBuilder.from_environ(
src/flask/testing.py 210

219 args[0], app=self.application, environ_base=self._copy_environ({})


220 ).get_request()
221 else:
222 # isinstance(args[0], BaseRequest)
223 request = copy(args[0])
224 request.environ = self._copy_environ(request.environ)
225 else:
226 # request is None
227 request = self._request_from_builder_args(args, kwargs)
228
229 # Pop any previously preserved contexts. This prevents contexts
230 # from being preserved across redirects or multiple requests
231 # within a single block.
232 self._context_stack.close()
233
234 response = super().open(
235 request,
236 buffered=buffered,
237 follow_redirects=follow_redirects,
238 )
239 response.json_module = self.application.json # type: ignore[assignment]
240
241 # Re-push contexts that were preserved during the request.
242 while self._new_contexts:
243 cm = self._new_contexts.pop()
244 self._context_stack.enter_context(cm)
245
246 return response
247
248 def __enter__(self) -> FlaskClient:
249 if self.preserve_context:
250 raise RuntimeError("Cannot nest client invocations")
251 self.preserve_context = True
252 return self
253
254 def __exit__(
255 self,
256 exc_type: type | None,
257 exc_value: BaseException | None,
258 tb: TracebackType | None,
259 ) -> None:
260 self.preserve_context = False
261 self._context_stack.close()
262
263
264 class FlaskCliRunner(CliRunner):
265 """A :class:`~click.testing.CliRunner` for testing a Flask app's
266 CLI commands. Typically created using
267 :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.
268 """
269
270 def __init__(self, app: Flask, **kwargs: t.Any) -> None:
271 self.app = app
272 super().__init__(**kwargs)
273
274 def invoke( # type: ignore
275 self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any
276 ) -> t.Any:
277 """Invokes a CLI command in an isolated environment. See
278 :meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for
279 full method documentation. See :ref:`testing-cli` for examples.
280
281 If the ``obj`` argument is not given, passes an instance of
282 :class:`~flask.cli.ScriptInfo` that knows how to load the Flask
283 app being tested.
284
285 :param cli: Command object to invoke. Default is the app's
286 :attr:`~flask.app.Flask.cli` group.
287 :param args: List of strings to invoke the command with.
288
289 :return: a :class:`~click.testing.Result` object.
211

290 """
291 if cli is None:
292 cli = self.app.cli
293
294 if "obj" not in kwargs:
295 kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
296
297 return super().invoke(cli, args, **kwargs)

src/flask/config.py
1 from __future__ import annotations
2
3 import errno
4 import json
5 import os
6 import types
7 import typing as t
8
9 from werkzeug.utils import import_string
10
11 if t.TYPE_CHECKING:
12 import typing_extensions as te
13
14 from .sansio.app import App
15
16
17 T = t.TypeVar("T")
18
19
20 class ConfigAttribute(t.Generic[T]):
21 """Makes an attribute forward to the config"""
22
23 def __init__(
24 self, name: str, get_converter: t.Callable[[t.Any], T] | None = None
25 ) -> None:
26 self.__name__ = name
27 self.get_converter = get_converter
28
29 @t.overload
30 def __get__(self, obj: None, owner: None) -> te.Self: ...
31
32 @t.overload
33 def __get__(self, obj: App, owner: type[App]) -> T: ...
34
35 def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self:
36 if obj is None:
37 return self
38
39 rv = obj.config[self.__name__]
40
41 if self.get_converter is not None:
42 rv = self.get_converter(rv)
43
44 return rv # type: ignore[no-any-return]
45
46 def __set__(self, obj: App, value: t.Any) -> None:
47 obj.config[self.__name__] = value
48
49
50 class Config(dict): # type: ignore[type-arg]
51 """Works exactly like a dict but provides ways to fill it from files
52 or special dictionaries. There are two common patterns to populate the
53 config.
54
55 Either you can fill the config from a config file::
56
57 app.config.from_pyfile('yourconfig.cfg')
58
59 Or alternatively you can define the configuration options in the
src/flask/config.py 212

60 module that calls :meth:`from_object` or provide an import path to


61 a module that should be loaded. It is also possible to tell it to
62 use the same module and with that provide the configuration values
63 just before the call::
64
65 DEBUG = True
66 SECRET_KEY = 'development key'
67 app.config.from_object(__name__)
68
69 In both cases (loading from any Python file or loading from modules),
70 only uppercase keys are added to the config. This makes it possible to use
71 lowercase values in the config file for temporary values that are not added
72 to the config or to define the config keys in the same file that implements
73 the application.
74
75 Probably the most interesting way to load configurations is from an
76 environment variable pointing to a file::
77
78 app.config.from_envvar('YOURAPPLICATION_SETTINGS')
79
80 In this case before launching the application you have to set this
81 environment variable to the file you want to use. On Linux and OS X
82 use the export statement::
83
84 export YOURAPPLICATION_SETTINGS='/path/to/config/file'
85
86 On windows use `set` instead.
87
88 :param root_path: path to which files are read relative from. When the
89 config object is created by the application, this is
90 the application's :attr:`~flask.Flask.root_path`.
91 :param defaults: an optional dictionary of default values
92 """
93
94 def __init__(
95 self,
96 root_path: str | os.PathLike[str],
97 defaults: dict[str, t.Any] | None = None,
98 ) -> None:
99 super().__init__(defaults or {})
100 self.root_path = root_path
101
102 def from_envvar(self, variable_name: str, silent: bool = False) -> bool:
103 """Loads a configuration from an environment variable pointing to
104 a configuration file. This is basically just a shortcut with nicer
105 error messages for this line of code::
106
107 app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
108
109 :param variable_name: name of the environment variable
110 :param silent: set to ``True`` if you want silent failure for missing
111 files.
112 :return: ``True`` if the file was loaded successfully.
113 """
114 rv = os.environ.get(variable_name)
115 if not rv:
116 if silent:
117 return False
118 raise RuntimeError(
119 f"The environment variable {variable_name!r} is not set"
120 " and as such configuration could not be loaded. Set"
121 " this variable and make it point to a configuration"
122 " file"
123 )
124 return self.from_pyfile(rv, silent=silent)
125
126 def from_prefixed_env(
127 self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads
128 ) -> bool:
129 """Load any environment variables that start with ``FLASK_``,
130 dropping the prefix from the env key for the config key. Values
src/flask/config.py 213

131 are passed through a loading function to attempt to convert them


132 to more specific types than strings.
133
134 Keys are loaded in :func:`sorted` order.
135
136 The default loading function attempts to parse values as any
137 valid JSON type, including dicts and lists.
138
139 Specific items in nested dicts can be set by separating the
140 keys with double underscores (``__``). If an intermediate key
141 doesn't exist, it will be initialized to an empty dict.
142
143 :param prefix: Load env vars that start with this prefix,
144 separated with an underscore (``_``).
145 :param loads: Pass each string value to this function and use
146 the returned value as the config value. If any error is
147 raised it is ignored and the value remains a string. The
148 default is :func:`json.loads`.
149
150 .. versionadded:: 2.1
151 """
152 prefix = f"{prefix}_"
153
154 for key in sorted(os.environ):
155 if not key.startswith(prefix):
156 continue
157
158 value = os.environ[key]
159 key = key.removeprefix(prefix)
160
161 try:
162 value = loads(value)
163 except Exception:
164 # Keep the value as a string if loading failed.
165 pass
166
167 if "__" not in key:
168 # A non-nested key, set directly.
169 self[key] = value
170 continue
171
172 # Traverse nested dictionaries with keys separated by "__".
173 current = self
174 *parts, tail = key.split("__")
175
176 for part in parts:
177 # If an intermediate dict does not exist, create it.
178 if part not in current:
179 current[part] = {}
180
181 current = current[part]
182
183 current[tail] = value
184
185 return True
186
187 def from_pyfile(
188 self, filename: str | os.PathLike[str], silent: bool = False
189 ) -> bool:
190 """Updates the values in the config from a Python file. This function
191 behaves as if the file was imported as module with the
192 :meth:`from_object` function.
193
194 :param filename: the filename of the config. This can either be an
195 absolute filename or a filename relative to the
196 root path.
197 :param silent: set to ``True`` if you want silent failure for missing
198 files.
199 :return: ``True`` if the file was loaded successfully.
200
201 .. versionadded:: 0.7
src/flask/config.py 214

202 `silent` parameter.


203 """
204 filename = os.path.join(self.root_path, filename)
205 d = types.ModuleType("config")
206 d.__file__ = filename
207 try:
208 with open(filename, mode="rb") as config_file:
209 exec(compile(config_file.read(), filename, "exec"), d.__dict__)
210 except OSError as e:
211 if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
212 return False
213 e.strerror = f"Unable to load configuration file ({e.strerror})"
214 raise
215 self.from_object(d)
216 return True
217
218 def from_object(self, obj: object | str) -> None:
219 """Updates the values from the given object. An object can be of one
220 of the following two types:
221
222 - a string: in this case the object with that name will be imported
223 - an actual object reference: that object is used directly
224
225 Objects are usually either modules or classes. :meth:`from_object`
226 loads only the uppercase attributes of the module/class. A ``dict``
227 object will not work with :meth:`from_object` because the keys of a
228 ``dict`` are not attributes of the ``dict`` class.
229
230 Example of module-based configuration::
231
232 app.config.from_object('yourapplication.default_config')
233 from yourapplication import default_config
234 app.config.from_object(default_config)
235
236 Nothing is done to the object before loading. If the object is a
237 class and has ``@property`` attributes, it needs to be
238 instantiated before being passed to this method.
239
240 You should not use this function to load the actual configuration but
241 rather configuration defaults. The actual config should be loaded
242 with :meth:`from_pyfile` and ideally from a location not within the
243 package because the package might be installed system wide.
244
245 See :ref:`config-dev-prod` for an example of class-based configuration
246 using :meth:`from_object`.
247
248 :param obj: an import name or object
249 """
250 if isinstance(obj, str):
251 obj = import_string(obj)
252 for key in dir(obj):
253 if key.isupper():
254 self[key] = getattr(obj, key)
255
256 def from_file(
257 self,
258 filename: str | os.PathLike[str],
259 load: t.Callable[[t.IO[t.Any]], t.Mapping[str, t.Any]],
260 silent: bool = False,
261 text: bool = True,
262 ) -> bool:
263 """Update the values in the config from a file that is loaded
264 using the ``load`` parameter. The loaded data is passed to the
265 :meth:`from_mapping` method.
266
267 .. code-block:: python
268
269 import json
270 app.config.from_file("config.json", load=json.load)
271
272 import tomllib
src/flask/config.py 215

273 app.config.from_file("config.toml", load=tomllib.load, text=False)


274
275 :param filename: The path to the data file. This can be an
276 absolute path or relative to the config root path.
277 :param load: A callable that takes a file handle and returns a
278 mapping of loaded data from the file.
279 :type load: ``Callable[[Reader], Mapping]`` where ``Reader``
280 implements a ``read`` method.
281 :param silent: Ignore the file if it doesn't exist.
282 :param text: Open the file in text or binary mode.
283 :return: ``True`` if the file was loaded successfully.
284
285 .. versionchanged:: 2.3
286 The ``text`` parameter was added.
287
288 .. versionadded:: 2.0
289 """
290 filename = os.path.join(self.root_path, filename)
291
292 try:
293 with open(filename, "r" if text else "rb") as f:
294 obj = load(f)
295 except OSError as e:
296 if silent and e.errno in (errno.ENOENT, errno.EISDIR):
297 return False
298
299 e.strerror = f"Unable to load configuration file ({e.strerror})"
300 raise
301
302 return self.from_mapping(obj)
303
304 def from_mapping(
305 self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any
306 ) -> bool:
307 """Updates the config like :meth:`update` ignoring items with
308 non-upper keys.
309
310 :return: Always returns ``True``.
311
312 .. versionadded:: 0.11
313 """
314 mappings: dict[str, t.Any] = {}
315 if mapping is not None:
316 mappings.update(mapping)
317 mappings.update(kwargs)
318 for key, value in mappings.items():
319 if key.isupper():
320 self[key] = value
321 return True
322
323 def get_namespace(
324 self, namespace: str, lowercase: bool = True, trim_namespace: bool = True
325 ) -> dict[str, t.Any]:
326 """Returns a dictionary containing a subset of configuration options
327 that match the specified namespace/prefix. Example usage::
328
329 app.config['IMAGE_STORE_TYPE'] = 'fs'
330 app.config['IMAGE_STORE_PATH'] = '/var/app/images'
331 app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
332 image_store_config = app.config.get_namespace('IMAGE_STORE_')
333
334 The resulting dictionary `image_store_config` would look like::
335
336 {
337 'type': 'fs',
338 'path': '/var/app/images',
339 'base_url': 'http://img.website.com'
340 }
341
342 This is often useful when configuration options map directly to
343 keyword arguments in functions or class constructors.
216

344
345 :param namespace: a configuration namespace
346 :param lowercase: a flag indicating if the keys of the resulting
347 dictionary should be lowercase
348 :param trim_namespace: a flag indicating if the keys of the resulting
349 dictionary should not include the namespace
350
351 .. versionadded:: 0.11
352 """
353 rv = {}
354 for k, v in self.items():
355 if not k.startswith(namespace):
356 continue
357 if trim_namespace:
358 key = k[len(namespace) :]
359 else:
360 key = k
361 if lowercase:
362 key = key.lower()
363 rv[key] = v
364 return rv
365
366 def __repr__(self) -> str:
367 return f"<{type(self).__name__} {dict.__repr__(self)}>"

src/flask/ctx.py
1 from __future__ import annotations
2
3 import contextvars
4 import sys
5 import typing as t
6 from functools import update_wrapper
7 from types import TracebackType
8
9 from werkzeug.exceptions import HTTPException
10
11 from . import typing as ft
12 from .globals import _cv_app
13 from .globals import _cv_request
14 from .signals import appcontext_popped
15 from .signals import appcontext_pushed
16
17 if t.TYPE_CHECKING: # pragma: no cover
18 from _typeshed.wsgi import WSGIEnvironment
19
20 from .app import Flask
21 from .sessions import SessionMixin
22 from .wrappers import Request
23
24
25 # a singleton sentinel value for parameter defaults
26 _sentinel = object()
27
28
29 class _AppCtxGlobals:
30 """A plain object. Used as a namespace for storing data during an
31 application context.
32
33 Creating an app context automatically creates this object, which is
34 made available as the :data:`g` proxy.
35
36 .. describe:: 'key' in g
37
38 Check whether an attribute is present.
39
40 .. versionadded:: 0.10
41
42 .. describe:: iter(g)
43
src/flask/ctx.py 217

44 Return an iterator over the attribute names.


45
46 .. versionadded:: 0.10
47 """
48
49 # Define attr methods to let mypy know this is a namespace object
50 # that has arbitrary attributes.
51
52 def __getattr__(self, name: str) -> t.Any:
53 try:
54 return self.__dict__[name]
55 except KeyError:
56 raise AttributeError(name) from None
57
58 def __setattr__(self, name: str, value: t.Any) -> None:
59 self.__dict__[name] = value
60
61 def __delattr__(self, name: str) -> None:
62 try:
63 del self.__dict__[name]
64 except KeyError:
65 raise AttributeError(name) from None
66
67 def get(self, name: str, default: t.Any | None = None) -> t.Any:
68 """Get an attribute by name, or a default value. Like
69 :meth:`dict.get`.
70
71 :param name: Name of attribute to get.
72 :param default: Value to return if the attribute is not present.
73
74 .. versionadded:: 0.10
75 """
76 return self.__dict__.get(name, default)
77
78 def pop(self, name: str, default: t.Any = _sentinel) -> t.Any:
79 """Get and remove an attribute by name. Like :meth:`dict.pop`.
80
81 :param name: Name of attribute to pop.
82 :param default: Value to return if the attribute is not present,
83 instead of raising a ``KeyError``.
84
85 .. versionadded:: 0.11
86 """
87 if default is _sentinel:
88 return self.__dict__.pop(name)
89 else:
90 return self.__dict__.pop(name, default)
91
92 def setdefault(self, name: str, default: t.Any = None) -> t.Any:
93 """Get the value of an attribute if it is present, otherwise
94 set and return a default value. Like :meth:`dict.setdefault`.
95
96 :param name: Name of attribute to get.
97 :param default: Value to set and return if the attribute is not
98 present.
99
100 .. versionadded:: 0.11
101 """
102 return self.__dict__.setdefault(name, default)
103
104 def __contains__(self, item: str) -> bool:
105 return item in self.__dict__
106
107 def __iter__(self) -> t.Iterator[str]:
108 return iter(self.__dict__)
109
110 def __repr__(self) -> str:
111 ctx = _cv_app.get(None)
112 if ctx is not None:
113 return f"<flask.g of '{ctx.app.name}'>"
114 return object.__repr__(self)
src/flask/ctx.py 218

115
116
117 def after_this_request(
118 f: ft.AfterRequestCallable[t.Any],
119 ) -> ft.AfterRequestCallable[t.Any]:
120 """Executes a function after this request. This is useful to modify
121 response objects. The function is passed the response object and has
122 to return the same or a new one.
123
124 Example::
125
126 @app.route('/')
127 def index():
128 @after_this_request
129 def add_header(response):
130 response.headers['X-Foo'] = 'Parachute'
131 return response
132 return 'Hello World!'
133
134 This is more useful if a function other than the view function wants to
135 modify a response. For instance think of a decorator that wants to add
136 some headers without converting the return value into a response object.
137
138 .. versionadded:: 0.9
139 """
140 ctx = _cv_request.get(None)
141
142 if ctx is None:
143 raise RuntimeError(
144 "'after_this_request' can only be used when a request"
145 " context is active, such as in a view function."
146 )
147
148 ctx._after_request_functions.append(f)
149 return f
150
151
152 F = t.TypeVar("F", bound=t.Callable[..., t.Any])
153
154
155 def copy_current_request_context(f: F) -> F:
156 """A helper function that decorates a function to retain the current
157 request context. This is useful when working with greenlets. The moment
158 the function is decorated a copy of the request context is created and
159 then pushed when the function is called. The current session is also
160 included in the copied request context.
161
162 Example::
163
164 import gevent
165 from flask import copy_current_request_context
166
167 @app.route('/')
168 def index():
169 @copy_current_request_context
170 def do_some_work():
171 # do some work here, it can access flask.request or
172 # flask.session like you would otherwise in the view function.
173 ...
174 gevent.spawn(do_some_work)
175 return 'Regular response'
176
177 .. versionadded:: 0.10
178 """
179 ctx = _cv_request.get(None)
180
181 if ctx is None:
182 raise RuntimeError(
183 "'copy_current_request_context' can only be used when a"
184 " request context is active, such as in a view function."
185 )
src/flask/ctx.py 219

186
187 ctx = ctx.copy()
188
189 def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
190 with ctx: # type: ignore[union-attr]
191 return ctx.app.ensure_sync(f)(*args, **kwargs) # type: ignore[union-attr]
192
193 return update_wrapper(wrapper, f) # type: ignore[return-value]
194
195
196 def has_request_context() -> bool:
197 """If you have code that wants to test if a request context is there or
198 not this function can be used. For instance, you may want to take advantage
199 of request information if the request object is available, but fail
200 silently if it is unavailable.
201
202 ::
203
204 class User(db.Model):
205
206 def __init__(self, username, remote_addr=None):
207 self.username = username
208 if remote_addr is None and has_request_context():
209 remote_addr = request.remote_addr
210 self.remote_addr = remote_addr
211
212 Alternatively you can also just test any of the context bound objects
213 (such as :class:`request` or :class:`g`) for truthness::
214
215 class User(db.Model):
216
217 def __init__(self, username, remote_addr=None):
218 self.username = username
219 if remote_addr is None and request:
220 remote_addr = request.remote_addr
221 self.remote_addr = remote_addr
222
223 .. versionadded:: 0.7
224 """
225 return _cv_request.get(None) is not None
226
227
228 def has_app_context() -> bool:
229 """Works like :func:`has_request_context` but for the application
230 context. You can also just do a boolean check on the
231 :data:`current_app` object instead.
232
233 .. versionadded:: 0.9
234 """
235 return _cv_app.get(None) is not None
236
237
238 class AppContext:
239 """The app context contains application-specific information. An app
240 context is created and pushed at the beginning of each request if
241 one is not already active. An app context is also pushed when
242 running CLI commands.
243 """
244
245 def __init__(self, app: Flask) -> None:
246 self.app = app
247 self.url_adapter = app.create_url_adapter(None)
248 self.g: _AppCtxGlobals = app.app_ctx_globals_class()
249 self._cv_tokens: list[contextvars.Token[AppContext]] = []
250
251 def push(self) -> None:
252 """Binds the app context to the current context."""
253 self._cv_tokens.append(_cv_app.set(self))
254 appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
255
256 def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
src/flask/ctx.py 220

257 """Pops the app context."""


258 try:
259 if len(self._cv_tokens) == 1:
260 if exc is _sentinel:
261 exc = sys.exc_info()[1]
262 self.app.do_teardown_appcontext(exc)
263 finally:
264 ctx = _cv_app.get()
265 _cv_app.reset(self._cv_tokens.pop())
266
267 if ctx is not self:
268 raise AssertionError(
269 f"Popped wrong app context. ({ctx!r} instead of {self!r})"
270 )
271
272 appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
273
274 def __enter__(self) -> AppContext:
275 self.push()
276 return self
277
278 def __exit__(
279 self,
280 exc_type: type | None,
281 exc_value: BaseException | None,
282 tb: TracebackType | None,
283 ) -> None:
284 self.pop(exc_value)
285
286
287 class RequestContext:
288 """The request context contains per-request information. The Flask
289 app creates and pushes it at the beginning of the request, then pops
290 it at the end of the request. It will create the URL adapter and
291 request object for the WSGI environment provided.
292
293 Do not attempt to use this class directly, instead use
294 :meth:`~flask.Flask.test_request_context` and
295 :meth:`~flask.Flask.request_context` to create this object.
296
297 When the request context is popped, it will evaluate all the
298 functions registered on the application for teardown execution
299 (:meth:`~flask.Flask.teardown_request`).
300
301 The request context is automatically popped at the end of the
302 request. When using the interactive debugger, the context will be
303 restored so ``request`` is still accessible. Similarly, the test
304 client can preserve the context after the request ends. However,
305 teardown functions may already have closed some resources such as
306 database connections.
307 """
308
309 def __init__(
310 self,
311 app: Flask,
312 environ: WSGIEnvironment,
313 request: Request | None = None,
314 session: SessionMixin | None = None,
315 ) -> None:
316 self.app = app
317 if request is None:
318 request = app.request_class(environ)
319 request.json_module = app.json
320 self.request: Request = request
321 self.url_adapter = None
322 try:
323 self.url_adapter = app.create_url_adapter(self.request)
324 except HTTPException as e:
325 self.request.routing_exception = e
326 self.flashes: list[tuple[str, str]] | None = None
327 self.session: SessionMixin | None = session
src/flask/ctx.py 221

328 # Functions that should be executed after the request on the response
329 # object. These will be called before the regular "after_request"
330 # functions.
331 self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = []
332
333 self._cv_tokens: list[
334 tuple[contextvars.Token[RequestContext], AppContext | None]
335 ] = []
336
337 def copy(self) -> RequestContext:
338 """Creates a copy of this request context with the same request object.
339 This can be used to move a request context to a different greenlet.
340 Because the actual request object is the same this cannot be used to
341 move a request context to a different thread unless access to the
342 request object is locked.
343
344 .. versionadded:: 0.10
345
346 .. versionchanged:: 1.1
347 The current session object is used instead of reloading the original
348 data. This prevents `flask.session` pointing to an out-of-date object.
349 """
350 return self.__class__(
351 self.app,
352 environ=self.request.environ,
353 request=self.request,
354 session=self.session,
355 )
356
357 def match_request(self) -> None:
358 """Can be overridden by a subclass to hook into the matching
359 of the request.
360 """
361 try:
362 result = self.url_adapter.match(return_rule=True) # type: ignore
363 self.request.url_rule, self.request.view_args = result # type: ignore
364 except HTTPException as e:
365 self.request.routing_exception = e
366
367 def push(self) -> None:
368 # Before we push the request context we have to ensure that there
369 # is an application context.
370 app_ctx = _cv_app.get(None)
371
372 if app_ctx is None or app_ctx.app is not self.app:
373 app_ctx = self.app.app_context()
374 app_ctx.push()
375 else:
376 app_ctx = None
377
378 self._cv_tokens.append((_cv_request.set(self), app_ctx))
379
380 # Open the session at the moment that the request context is available.
381 # This allows a custom open_session method to use the request context.
382 # Only open a new session if this is the first time the request was
383 # pushed, otherwise stream_with_context loses the session.
384 if self.session is None:
385 session_interface = self.app.session_interface
386 self.session = session_interface.open_session(self.app, self.request)
387
388 if self.session is None:
389 self.session = session_interface.make_null_session(self.app)
390
391 # Match the request URL after loading the session, so that the
392 # session is available in custom URL converters.
393 if self.url_adapter is not None:
394 self.match_request()
395
396 def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
397 """Pops the request context and unbinds it by doing that. This will
398 also trigger the execution of functions registered by the
222

399 :meth:`~flask.Flask.teardown_request` decorator.


400
401 .. versionchanged:: 0.9
402 Added the `exc` argument.
403 """
404 clear_request = len(self._cv_tokens) == 1
405
406 try:
407 if clear_request:
408 if exc is _sentinel:
409 exc = sys.exc_info()[1]
410 self.app.do_teardown_request(exc)
411
412 request_close = getattr(self.request, "close", None)
413 if request_close is not None:
414 request_close()
415 finally:
416 ctx = _cv_request.get()
417 token, app_ctx = self._cv_tokens.pop()
418 _cv_request.reset(token)
419
420 # get rid of circular dependencies at the end of the request
421 # so that we don't require the GC to be active.
422 if clear_request:
423 ctx.request.environ["werkzeug.request"] = None
424
425 if app_ctx is not None:
426 app_ctx.pop(exc)
427
428 if ctx is not self:
429 raise AssertionError(
430 f"Popped wrong request context. ({ctx!r} instead of {self!r})"
431 )
432
433 def __enter__(self) -> RequestContext:
434 self.push()
435 return self
436
437 def __exit__(
438 self,
439 exc_type: type | None,
440 exc_value: BaseException | None,
441 tb: TracebackType | None,
442 ) -> None:
443 self.pop(exc_value)
444
445 def __repr__(self) -> str:
446 return (
447 f"<{type(self).__name__} {self.request.url!r}"
448 f" [{self.request.method}] of {self.app.name}>"
449 )

src/flask/sessions.py
1 from __future__ import annotations
2
3 import collections.abc as c
4 import hashlib
5 import typing as t
6 from collections.abc import MutableMapping
7 from datetime import datetime
8 from datetime import timezone
9
10 from itsdangerous import BadSignature
11 from itsdangerous import URLSafeTimedSerializer
12 from werkzeug.datastructures import CallbackDict
13
14 from .json.tag import TaggedJSONSerializer
15
16 if t.TYPE_CHECKING: # pragma: no cover
src/flask/sessions.py 223

17 import typing_extensions as te
18
19 from .app import Flask
20 from .wrappers import Request
21 from .wrappers import Response
22
23
24 class SessionMixin(MutableMapping[str, t.Any]):
25 """Expands a basic dictionary with session attributes."""
26
27 @property
28 def permanent(self) -> bool:
29 """This reflects the ``'_permanent'`` key in the dict."""
30 return self.get("_permanent", False)
31
32 @permanent.setter
33 def permanent(self, value: bool) -> None:
34 self["_permanent"] = bool(value)
35
36 #: Some implementations can detect whether a session is newly
37 #: created, but that is not guaranteed. Use with caution. The mixin
38 # default is hard-coded ``False``.
39 new = False
40
41 #: Some implementations can detect changes to the session and set
42 #: this when that happens. The mixin default is hard coded to
43 #: ``True``.
44 modified = True
45
46 #: Some implementations can detect when session data is read or
47 #: written and set this when that happens. The mixin default is hard
48 #: coded to ``True``.
49 accessed = True
50
51
52 class SecureCookieSession(CallbackDict[str, t.Any], SessionMixin):
53 """Base class for sessions based on signed cookies.
54
55 This session backend will set the :attr:`modified` and
56 :attr:`accessed` attributes. It cannot reliably track whether a
57 session is new (vs. empty), so :attr:`new` remains hard coded to
58 ``False``.
59 """
60
61 #: When data is changed, this is set to ``True``. Only the session
62 #: dictionary itself is tracked; if the session contains mutable
63 #: data (for example a nested dict) then this must be set to
64 #: ``True`` manually when modifying that data. The session cookie
65 #: will only be written to the response if this is ``True``.
66 modified = False
67
68 #: When data is read or written, this is set to ``True``. Used by
69 # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
70 #: header, which allows caching proxies to cache different pages for
71 #: different users.
72 accessed = False
73
74 def __init__(
75 self,
76 initial: c.Mapping[str, t.Any] | c.Iterable[tuple[str, t.Any]] | None = None,
77 ) -> None:
78 def on_update(self: te.Self) -> None:
79 self.modified = True
80 self.accessed = True
81
82 super().__init__(initial, on_update)
83
84 def __getitem__(self, key: str) -> t.Any:
85 self.accessed = True
86 return super().__getitem__(key)
87
src/flask/sessions.py 224

88 def get(self, key: str, default: t.Any = None) -> t.Any:


89 self.accessed = True
90 return super().get(key, default)
91
92 def setdefault(self, key: str, default: t.Any = None) -> t.Any:
93 self.accessed = True
94 return super().setdefault(key, default)
95
96
97 class NullSession(SecureCookieSession):
98 """Class used to generate nicer error messages if sessions are not
99 available. Will still allow read-only access to the empty session
100 but fail on setting.
101 """
102
103 def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
104 raise RuntimeError(
105 "The session is unavailable because no secret "
106 "key was set. Set the secret_key on the "
107 "application to something unique and secret."
108 )
109
110 __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950
111 del _fail
112
113
114 class SessionInterface:
115 """The basic interface you have to implement in order to replace the
116 default session interface which uses werkzeug's securecookie
117 implementation. The only methods you have to implement are
118 :meth:`open_session` and :meth:`save_session`, the others have
119 useful defaults which you don't need to change.
120
121 The session object returned by the :meth:`open_session` method has to
122 provide a dictionary like interface plus the properties and methods
123 from the :class:`SessionMixin`. We recommend just subclassing a dict
124 and adding that mixin::
125
126 class Session(dict, SessionMixin):
127 pass
128
129 If :meth:`open_session` returns ``None`` Flask will call into
130 :meth:`make_null_session` to create a session that acts as replacement
131 if the session support cannot work because some requirement is not
132 fulfilled. The default :class:`NullSession` class that is created
133 will complain that the secret key was not set.
134
135 To replace the session interface on an application all you have to do
136 is to assign :attr:`flask.Flask.session_interface`::
137
138 app = Flask(__name__)
139 app.session_interface = MySessionInterface()
140
141 Multiple requests with the same session may be sent and handled
142 concurrently. When implementing a new session interface, consider
143 whether reads or writes to the backing store must be synchronized.
144 There is no guarantee on the order in which the session for each
145 request is opened or saved, it will occur in the order that requests
146 begin and end processing.
147
148 .. versionadded:: 0.8
149 """
150
151 #: :meth:`make_null_session` will look here for the class that should
152 #: be created when a null session is requested. Likewise the
153 #: :meth:`is_null_session` method will perform a typecheck against
154 #: this type.
155 null_session_class = NullSession
156
157 #: A flag that indicates if the session interface is pickle based.
158 #: This can be used by Flask extensions to make a decision in regards
src/flask/sessions.py 225

159 #: to how to deal with the session object.


160 #:
161 #: .. versionadded:: 0.10
162 pickle_based = False
163
164 def make_null_session(self, app: Flask) -> NullSession:
165 """Creates a null session which acts as a replacement object if the
166 real session support could not be loaded due to a configuration
167 error. This mainly aids the user experience because the job of the
168 null session is to still support lookup without complaining but
169 modifications are answered with a helpful error message of what
170 failed.
171
172 This creates an instance of :attr:`null_session_class` by default.
173 """
174 return self.null_session_class()
175
176 def is_null_session(self, obj: object) -> bool:
177 """Checks if a given object is a null session. Null sessions are
178 not asked to be saved.
179
180 This checks if the object is an instance of :attr:`null_session_class`
181 by default.
182 """
183 return isinstance(obj, self.null_session_class)
184
185 def get_cookie_name(self, app: Flask) -> str:
186 """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``."""
187 return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return]
188
189 def get_cookie_domain(self, app: Flask) -> str | None:
190 """The value of the ``Domain`` parameter on the session cookie. If not set,
191 browsers will only send the cookie to the exact domain it was set from.
192 Otherwise, they will send it to any subdomain of the given value as well.
193
194 Uses the :data:`SESSION_COOKIE_DOMAIN` config.
195
196 .. versionchanged:: 2.3
197 Not set by default, does not fall back to ``SERVER_NAME``.
198 """
199 return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return]
200
201 def get_cookie_path(self, app: Flask) -> str:
202 """Returns the path for which the cookie should be valid. The
203 default implementation uses the value from the ``SESSION_COOKIE_PATH``
204 config var if it's set, and falls back to ``APPLICATION_ROOT`` or
205 uses ``/`` if it's ``None``.
206 """
207 return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] # type: ignore[no-any-return]
208
209 def get_cookie_httponly(self, app: Flask) -> bool:
210 """Returns True if the session cookie should be httponly. This
211 currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
212 config var.
213 """
214 return app.config["SESSION_COOKIE_HTTPONLY"] # type: ignore[no-any-return]
215
216 def get_cookie_secure(self, app: Flask) -> bool:
217 """Returns True if the cookie should be secure. This currently
218 just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
219 """
220 return app.config["SESSION_COOKIE_SECURE"] # type: ignore[no-any-return]
221
222 def get_cookie_samesite(self, app: Flask) -> str | None:
223 """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
224 ``SameSite`` attribute. This currently just returns the value of
225 the :data:`SESSION_COOKIE_SAMESITE` setting.
226 """
227 return app.config["SESSION_COOKIE_SAMESITE"] # type: ignore[no-any-return]
228
229 def get_cookie_partitioned(self, app: Flask) -> bool:
src/flask/sessions.py 226

230 """Returns True if the cookie should be partitioned. By default, uses


231 the value of :data:`SESSION_COOKIE_PARTITIONED`.
232
233 .. versionadded:: 3.1
234 """
235 return app.config["SESSION_COOKIE_PARTITIONED"] # type: ignore[no-any-return]
236
237 def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None:
238 """A helper method that returns an expiration date for the session
239 or ``None`` if the session is linked to the browser session. The
240 default implementation returns now + the permanent session
241 lifetime configured on the application.
242 """
243 if session.permanent:
244 return datetime.now(timezone.utc) + app.permanent_session_lifetime
245 return None
246
247 def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool:
248 """Used by session backends to determine if a ``Set-Cookie`` header
249 should be set for this session cookie for this response. If the session
250 has been modified, the cookie is set. If the session is permanent and
251 the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
252 always set.
253
254 This check is usually skipped if the session was deleted.
255
256 .. versionadded:: 0.11
257 """
258
259 return session.modified or (
260 session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"]
261 )
262
263 def open_session(self, app: Flask, request: Request) -> SessionMixin | None:
264 """This is called at the beginning of each request, after
265 pushing the request context, before matching the URL.
266
267 This must return an object which implements a dictionary-like
268 interface as well as the :class:`SessionMixin` interface.
269
270 This will return ``None`` to indicate that loading failed in
271 some way that is not immediately an error. The request
272 context will fall back to using :meth:`make_null_session`
273 in this case.
274 """
275 raise NotImplementedError()
276
277 def save_session(
278 self, app: Flask, session: SessionMixin, response: Response
279 ) -> None:
280 """This is called at the end of each request, after generating
281 a response, before removing the request context. It is skipped
282 if :meth:`is_null_session` returns ``True``.
283 """
284 raise NotImplementedError()
285
286
287 session_json_serializer = TaggedJSONSerializer()
288
289
290 def _lazy_sha1(string: bytes = b"") -> t.Any:
291 """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include
292 SHA-1, in which case the import and use as a default would fail before the
293 developer can configure something else.
294 """
295 return hashlib.sha1(string)
296
297
298 class SecureCookieSessionInterface(SessionInterface):
299 """The default session interface that stores sessions in signed cookies
300 through the :mod:`itsdangerous` module.
src/flask/sessions.py 227

301 """
302
303 #: the salt that should be applied on top of the secret key for the
304 #: signing of cookie based sessions.
305 salt = "cookie-session"
306 #: the hash function to use for the signature. The default is sha1
307 digest_method = staticmethod(_lazy_sha1)
308 #: the name of the itsdangerous supported key derivation. The default
309 #: is hmac.
310 key_derivation = "hmac"
311 #: A python serializer for the payload. The default is a compact
312 #: JSON derived serializer with support for some extra Python types
313 #: such as datetime objects or tuples.
314 serializer = session_json_serializer
315 session_class = SecureCookieSession
316
317 def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None:
318 if not app.secret_key:
319 return None
320
321 keys: list[str | bytes] = [app.secret_key]
322
323 if fallbacks := app.config["SECRET_KEY_FALLBACKS"]:
324 keys.extend(fallbacks)
325
326 return URLSafeTimedSerializer(
327 keys, # type: ignore[arg-type]
328 salt=self.salt,
329 serializer=self.serializer,
330 signer_kwargs={
331 "key_derivation": self.key_derivation,
332 "digest_method": self.digest_method,
333 },
334 )
335
336 def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None:
337 s = self.get_signing_serializer(app)
338 if s is None:
339 return None
340 val = request.cookies.get(self.get_cookie_name(app))
341 if not val:
342 return self.session_class()
343 max_age = int(app.permanent_session_lifetime.total_seconds())
344 try:
345 data = s.loads(val, max_age=max_age)
346 return self.session_class(data)
347 except BadSignature:
348 return self.session_class()
349
350 def save_session(
351 self, app: Flask, session: SessionMixin, response: Response
352 ) -> None:
353 name = self.get_cookie_name(app)
354 domain = self.get_cookie_domain(app)
355 path = self.get_cookie_path(app)
356 secure = self.get_cookie_secure(app)
357 partitioned = self.get_cookie_partitioned(app)
358 samesite = self.get_cookie_samesite(app)
359 httponly = self.get_cookie_httponly(app)
360
361 # Add a "Vary: Cookie" header if the session was accessed at all.
362 if session.accessed:
363 response.vary.add("Cookie")
364
365 # If the session is modified to be empty, remove the cookie.
366 # If the session is empty, return without setting the cookie.
367 if not session:
368 if session.modified:
369 response.delete_cookie(
370 name,
371 domain=domain,
228

372 path=path,
373 secure=secure,
374 partitioned=partitioned,
375 samesite=samesite,
376 httponly=httponly,
377 )
378 response.vary.add("Cookie")
379
380 return
381
382 if not self.should_set_cookie(app, session):
383 return
384
385 expires = self.get_expiration_time(app, session)
386 val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore[union-attr]
387 response.set_cookie(
388 name,
389 val,
390 expires=expires,
391 httponly=httponly,
392 domain=domain,
393 path=path,
394 secure=secure,
395 partitioned=partitioned,
396 samesite=samesite,
397 )
398 response.vary.add("Cookie")

src/flask/json/__init__.py
1 from __future__ import annotations
2
3 import json as _json
4 import typing as t
5
6 from ..globals import current_app
7 from .provider import _default
8
9 if t.TYPE_CHECKING: # pragma: no cover
10 from ..wrappers import Response
11
12
13 def dumps(obj: t.Any, **kwargs: t.Any) -> str:
14 """Serialize data as JSON.
15
16 If :data:`~flask.current_app` is available, it will use its
17 :meth:`app.json.dumps() <flask.json.provider.JSONProvider.dumps>`
18 method, otherwise it will use :func:`json.dumps`.
19
20 :param obj: The data to serialize.
21 :param kwargs: Arguments passed to the ``dumps`` implementation.
22
23 .. versionchanged:: 2.3
24 The ``app`` parameter was removed.
25
26 .. versionchanged:: 2.2
27 Calls ``current_app.json.dumps``, allowing an app to override
28 the behavior.
29
30 .. versionchanged:: 2.0.2
31 :class:`decimal.Decimal` is supported by converting to a string.
32
33 .. versionchanged:: 2.0
34 ``encoding`` will be removed in Flask 2.1.
35
36 .. versionchanged:: 1.0.3
37 ``app`` can be passed directly, rather than requiring an app
38 context for configuration.
39 """
40 if current_app:
src/flask/json/__init__.py 229

41 return current_app.json.dumps(obj, **kwargs)


42
43 kwargs.setdefault("default", _default)
44 return _json.dumps(obj, **kwargs)
45
46
47 def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
48 """Serialize data as JSON and write to a file.
49
50 If :data:`~flask.current_app` is available, it will use its
51 :meth:`app.json.dump() <flask.json.provider.JSONProvider.dump>`
52 method, otherwise it will use :func:`json.dump`.
53
54 :param obj: The data to serialize.
55 :param fp: A file opened for writing text. Should use the UTF-8
56 encoding to be valid JSON.
57 :param kwargs: Arguments passed to the ``dump`` implementation.
58
59 .. versionchanged:: 2.3
60 The ``app`` parameter was removed.
61
62 .. versionchanged:: 2.2
63 Calls ``current_app.json.dump``, allowing an app to override
64 the behavior.
65
66 .. versionchanged:: 2.0
67 Writing to a binary file, and the ``encoding`` argument, will be
68 removed in Flask 2.1.
69 """
70 if current_app:
71 current_app.json.dump(obj, fp, **kwargs)
72 else:
73 kwargs.setdefault("default", _default)
74 _json.dump(obj, fp, **kwargs)
75
76
77 def loads(s: str | bytes, **kwargs: t.Any) -> t.Any:
78 """Deserialize data as JSON.
79
80 If :data:`~flask.current_app` is available, it will use its
81 :meth:`app.json.loads() <flask.json.provider.JSONProvider.loads>`
82 method, otherwise it will use :func:`json.loads`.
83
84 :param s: Text or UTF-8 bytes.
85 :param kwargs: Arguments passed to the ``loads`` implementation.
86
87 .. versionchanged:: 2.3
88 The ``app`` parameter was removed.
89
90 .. versionchanged:: 2.2
91 Calls ``current_app.json.loads``, allowing an app to override
92 the behavior.
93
94 .. versionchanged:: 2.0
95 ``encoding`` will be removed in Flask 2.1. The data must be a
96 string or UTF-8 bytes.
97
98 .. versionchanged:: 1.0.3
99 ``app`` can be passed directly, rather than requiring an app
100 context for configuration.
101 """
102 if current_app:
103 return current_app.json.loads(s, **kwargs)
104
105 return _json.loads(s, **kwargs)
106
107
108 def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
109 """Deserialize data as JSON read from a file.
110
111 If :data:`~flask.current_app` is available, it will use its
230

112 :meth:`app.json.load() <flask.json.provider.JSONProvider.load>`


113 method, otherwise it will use :func:`json.load`.
114
115 :param fp: A file opened for reading text or UTF-8 bytes.
116 :param kwargs: Arguments passed to the ``load`` implementation.
117
118 .. versionchanged:: 2.3
119 The ``app`` parameter was removed.
120
121 .. versionchanged:: 2.2
122 Calls ``current_app.json.load``, allowing an app to override
123 the behavior.
124
125 .. versionchanged:: 2.2
126 The ``app`` parameter will be removed in Flask 2.3.
127
128 .. versionchanged:: 2.0
129 ``encoding`` will be removed in Flask 2.1. The file must be text
130 mode, or binary mode with UTF-8 bytes.
131 """
132 if current_app:
133 return current_app.json.load(fp, **kwargs)
134
135 return _json.load(fp, **kwargs)
136
137
138 def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
139 """Serialize the given arguments as JSON, and return a
140 :class:`~flask.Response` object with the ``application/json``
141 mimetype. A dict or list returned from a view will be converted to a
142 JSON response automatically without needing to call this.
143
144 This requires an active request or application context, and calls
145 :meth:`app.json.response() <flask.json.provider.JSONProvider.response>`.
146
147 In debug mode, the output is formatted with indentation to make it
148 easier to read. This may also be controlled by the provider.
149
150 Either positional or keyword arguments can be given, not both.
151 If no arguments are given, ``None`` is serialized.
152
153 :param args: A single value to serialize, or multiple values to
154 treat as a list to serialize.
155 :param kwargs: Treat as a dict to serialize.
156
157 .. versionchanged:: 2.2
158 Calls ``current_app.json.response``, allowing an app to override
159 the behavior.
160
161 .. versionchanged:: 2.0.2
162 :class:`decimal.Decimal` is supported by converting to a string.
163
164 .. versionchanged:: 0.11
165 Added support for serializing top-level arrays. This was a
166 security risk in ancient browsers. See :ref:`security-json`.
167
168 .. versionadded:: 0.2
169 """
170 return current_app.json.response(*args, **kwargs) # type: ignore[return-value]

src/flask/json/provider.py
1 from __future__ import annotations
2
3 import dataclasses
4 import decimal
5 import json
6 import typing as t
7 import uuid
8 import weakref
src/flask/json/provider.py 231

9 from datetime import date


10
11 from werkzeug.http import http_date
12
13 if t.TYPE_CHECKING: # pragma: no cover
14 from werkzeug.sansio.response import Response
15
16 from ..sansio.app import App
17
18
19 class JSONProvider:
20 """A standard set of JSON operations for an application. Subclasses
21 of this can be used to customize JSON behavior or use different
22 JSON libraries.
23
24 To implement a provider for a specific library, subclass this base
25 class and implement at least :meth:`dumps` and :meth:`loads`. All
26 other methods have default implementations.
27
28 To use a different provider, either subclass ``Flask`` and set
29 :attr:`~flask.Flask.json_provider_class` to a provider class, or set
30 :attr:`app.json <flask.Flask.json>` to an instance of the class.
31
32 :param app: An application instance. This will be stored as a
33 :class:`weakref.proxy` on the :attr:`_app` attribute.
34
35 .. versionadded:: 2.2
36 """
37
38 def __init__(self, app: App) -> None:
39 self._app: App = weakref.proxy(app)
40
41 def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
42 """Serialize data as JSON.
43
44 :param obj: The data to serialize.
45 :param kwargs: May be passed to the underlying JSON library.
46 """
47 raise NotImplementedError
48
49 def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
50 """Serialize data as JSON and write to a file.
51
52 :param obj: The data to serialize.
53 :param fp: A file opened for writing text. Should use the UTF-8
54 encoding to be valid JSON.
55 :param kwargs: May be passed to the underlying JSON library.
56 """
57 fp.write(self.dumps(obj, **kwargs))
58
59 def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
60 """Deserialize data as JSON.
61
62 :param s: Text or UTF-8 bytes.
63 :param kwargs: May be passed to the underlying JSON library.
64 """
65 raise NotImplementedError
66
67 def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
68 """Deserialize data as JSON read from a file.
69
70 :param fp: A file opened for reading text or UTF-8 bytes.
71 :param kwargs: May be passed to the underlying JSON library.
72 """
73 return self.loads(fp.read(), **kwargs)
74
75 def _prepare_response_obj(
76 self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
77 ) -> t.Any:
78 if args and kwargs:
79 raise TypeError("app.json.response() takes either args or kwargs, not both")
src/flask/json/provider.py 232

80
81 if not args and not kwargs:
82 return None
83
84 if len(args) == 1:
85 return args[0]
86
87 return args or kwargs
88
89 def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
90 """Serialize the given arguments as JSON, and return a
91 :class:`~flask.Response` object with the ``application/json``
92 mimetype.
93
94 The :func:`~flask.json.jsonify` function calls this method for
95 the current application.
96
97 Either positional or keyword arguments can be given, not both.
98 If no arguments are given, ``None`` is serialized.
99
100 :param args: A single value to serialize, or multiple values to
101 treat as a list to serialize.
102 :param kwargs: Treat as a dict to serialize.
103 """
104 obj = self._prepare_response_obj(args, kwargs)
105 return self._app.response_class(self.dumps(obj), mimetype="application/json")
106
107
108 def _default(o: t.Any) -> t.Any:
109 if isinstance(o, date):
110 return http_date(o)
111
112 if isinstance(o, (decimal.Decimal, uuid.UUID)):
113 return str(o)
114
115 if dataclasses and dataclasses.is_dataclass(o):
116 return dataclasses.asdict(o) # type: ignore[arg-type]
117
118 if hasattr(o, "__html__"):
119 return str(o.__html__())
120
121 raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
122
123
124 class DefaultJSONProvider(JSONProvider):
125 """Provide JSON operations using Python's built-in :mod:`json`
126 library. Serializes the following additional data types:
127
128 - :class:`datetime.datetime` and :class:`datetime.date` are
129 serialized to :rfc:`822` strings. This is the same as the HTTP
130 date format.
131 - :class:`uuid.UUID` is serialized to a string.
132 - :class:`dataclasses.dataclass` is passed to
133 :func:`dataclasses.asdict`.
134 - :class:`~markupsafe.Markup` (or any object with a ``__html__``
135 method) will call the ``__html__`` method to get a string.
136 """
137
138 default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment]
139 """Apply this function to any object that :meth:`json.dumps` does
140 not know how to serialize. It should return a valid JSON type or
141 raise a ``TypeError``.
142 """
143
144 ensure_ascii = True
145 """Replace non-ASCII characters with escape sequences. This may be
146 more compatible with some clients, but can be disabled for better
147 performance and size.
148 """
149
150 sort_keys = True
233

151 """Sort the keys in any serialized dicts. This may be useful for
152 some caching situations, but can be disabled for better performance.
153 When enabled, keys must all be strings, they are not converted
154 before sorting.
155 """
156
157 compact: bool | None = None
158 """If ``True``, or ``None`` out of debug mode, the :meth:`response`
159 output will not add indentation, newlines, or spaces. If ``False``,
160 or ``None`` in debug mode, it will use a non-compact representation.
161 """
162
163 mimetype = "application/json"
164 """The mimetype set in :meth:`response`."""
165
166 def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
167 """Serialize data as JSON to a string.
168
169 Keyword arguments are passed to :func:`json.dumps`. Sets some
170 parameter defaults from the :attr:`default`,
171 :attr:`ensure_ascii`, and :attr:`sort_keys` attributes.
172
173 :param obj: The data to serialize.
174 :param kwargs: Passed to :func:`json.dumps`.
175 """
176 kwargs.setdefault("default", self.default)
177 kwargs.setdefault("ensure_ascii", self.ensure_ascii)
178 kwargs.setdefault("sort_keys", self.sort_keys)
179 return json.dumps(obj, **kwargs)
180
181 def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
182 """Deserialize data as JSON from a string or bytes.
183
184 :param s: Text or UTF-8 bytes.
185 :param kwargs: Passed to :func:`json.loads`.
186 """
187 return json.loads(s, **kwargs)
188
189 def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
190 """Serialize the given arguments as JSON, and return a
191 :class:`~flask.Response` object with it. The response mimetype
192 will be "application/json" and can be changed with
193 :attr:`mimetype`.
194
195 If :attr:`compact` is ``False`` or debug mode is enabled, the
196 output will be formatted to be easier to read.
197
198 Either positional or keyword arguments can be given, not both.
199 If no arguments are given, ``None`` is serialized.
200
201 :param args: A single value to serialize, or multiple values to
202 treat as a list to serialize.
203 :param kwargs: Treat as a dict to serialize.
204 """
205 obj = self._prepare_response_obj(args, kwargs)
206 dump_args: dict[str, t.Any] = {}
207
208 if (self.compact is None and self._app.debug) or self.compact is False:
209 dump_args.setdefault("indent", 2)
210 else:
211 dump_args.setdefault("separators", (",", ":"))
212
213 return self._app.response_class(
214 f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
215 )

src/flask/json/tag.py
1 """
2 Tagged JSON
src/flask/json/tag.py 234

3 ~~~~~~~~~~~
4
5 A compact representation for lossless serialization of non-standard JSON
6 types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this
7 to serialize the session data, but it may be useful in other places. It
8 can be extended to support other types.
9
10 .. autoclass:: TaggedJSONSerializer
11 :members:
12
13 .. autoclass:: JSONTag
14 :members:
15
16 Let's see an example that adds support for
17 :class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so
18 to handle this we will dump the items as a list of ``[key, value]``
19 pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to
20 identify the type. The session serializer processes dicts first, so
21 insert the new tag at the front of the order since ``OrderedDict`` must
22 be processed before ``dict``.
23
24 .. code-block:: python
25
26 from flask.json.tag import JSONTag
27
28 class TagOrderedDict(JSONTag):
29 __slots__ = ('serializer',)
30 key = ' od'
31
32 def check(self, value):
33 return isinstance(value, OrderedDict)
34
35 def to_json(self, value):
36 return [[k, self.serializer.tag(v)] for k, v in iteritems(value)]
37
38 def to_python(self, value):
39 return OrderedDict(value)
40
41 app.session_interface.serializer.register(TagOrderedDict, index=0)
42 """
43
44 from __future__ import annotations
45
46 import typing as t
47 from base64 import b64decode
48 from base64 import b64encode
49 from datetime import datetime
50 from uuid import UUID
51
52 from markupsafe import Markup
53 from werkzeug.http import http_date
54 from werkzeug.http import parse_date
55
56 from ..json import dumps
57 from ..json import loads
58
59
60 class JSONTag:
61 """Base class for defining type tags for :class:`TaggedJSONSerializer`."""
62
63 __slots__ = ("serializer",)
64
65 #: The tag to mark the serialized object with. If empty, this tag is
66 #: only used as an intermediate step during tagging.
67 key: str = ""
68
69 def __init__(self, serializer: TaggedJSONSerializer) -> None:
70 """Create a tagger for the given serializer."""
71 self.serializer = serializer
72
73 def check(self, value: t.Any) -> bool:
src/flask/json/tag.py 235

74 """Check if the given value should be tagged by this tag."""


75 raise NotImplementedError
76
77 def to_json(self, value: t.Any) -> t.Any:
78 """Convert the Python object to an object that is a valid JSON type.
79 The tag will be added later."""
80 raise NotImplementedError
81
82 def to_python(self, value: t.Any) -> t.Any:
83 """Convert the JSON representation back to the correct type. The tag
84 will already be removed."""
85 raise NotImplementedError
86
87 def tag(self, value: t.Any) -> dict[str, t.Any]:
88 """Convert the value to a valid JSON type and add the tag structure
89 around it."""
90 return {self.key: self.to_json(value)}
91
92
93 class TagDict(JSONTag):
94 """Tag for 1-item dicts whose only key matches a registered tag.
95
96 Internally, the dict key is suffixed with `__`, and the suffix is removed
97 when deserializing.
98 """
99
100 __slots__ = ()
101 key = " di"
102
103 def check(self, value: t.Any) -> bool:
104 return (
105 isinstance(value, dict)
106 and len(value) == 1
107 and next(iter(value)) in self.serializer.tags
108 )
109
110 def to_json(self, value: t.Any) -> t.Any:
111 key = next(iter(value))
112 return {f"{key}__": self.serializer.tag(value[key])}
113
114 def to_python(self, value: t.Any) -> t.Any:
115 key = next(iter(value))
116 return {key[:-2]: value[key]}
117
118
119 class PassDict(JSONTag):
120 __slots__ = ()
121
122 def check(self, value: t.Any) -> bool:
123 return isinstance(value, dict)
124
125 def to_json(self, value: t.Any) -> t.Any:
126 # JSON objects may only have string keys, so don't bother tagging the
127 # key here.
128 return {k: self.serializer.tag(v) for k, v in value.items()}
129
130 tag = to_json
131
132
133 class TagTuple(JSONTag):
134 __slots__ = ()
135 key = " t"
136
137 def check(self, value: t.Any) -> bool:
138 return isinstance(value, tuple)
139
140 def to_json(self, value: t.Any) -> t.Any:
141 return [self.serializer.tag(item) for item in value]
142
143 def to_python(self, value: t.Any) -> t.Any:
144 return tuple(value)
src/flask/json/tag.py 236

145
146
147 class PassList(JSONTag):
148 __slots__ = ()
149
150 def check(self, value: t.Any) -> bool:
151 return isinstance(value, list)
152
153 def to_json(self, value: t.Any) -> t.Any:
154 return [self.serializer.tag(item) for item in value]
155
156 tag = to_json
157
158
159 class TagBytes(JSONTag):
160 __slots__ = ()
161 key = " b"
162
163 def check(self, value: t.Any) -> bool:
164 return isinstance(value, bytes)
165
166 def to_json(self, value: t.Any) -> t.Any:
167 return b64encode(value).decode("ascii")
168
169 def to_python(self, value: t.Any) -> t.Any:
170 return b64decode(value)
171
172
173 class TagMarkup(JSONTag):
174 """Serialize anything matching the :class:`~markupsafe.Markup` API by
175 having a ``__html__`` method to the result of that method. Always
176 deserializes to an instance of :class:`~markupsafe.Markup`."""
177
178 __slots__ = ()
179 key = " m"
180
181 def check(self, value: t.Any) -> bool:
182 return callable(getattr(value, "__html__", None))
183
184 def to_json(self, value: t.Any) -> t.Any:
185 return str(value.__html__())
186
187 def to_python(self, value: t.Any) -> t.Any:
188 return Markup(value)
189
190
191 class TagUUID(JSONTag):
192 __slots__ = ()
193 key = " u"
194
195 def check(self, value: t.Any) -> bool:
196 return isinstance(value, UUID)
197
198 def to_json(self, value: t.Any) -> t.Any:
199 return value.hex
200
201 def to_python(self, value: t.Any) -> t.Any:
202 return UUID(value)
203
204
205 class TagDateTime(JSONTag):
206 __slots__ = ()
207 key = " d"
208
209 def check(self, value: t.Any) -> bool:
210 return isinstance(value, datetime)
211
212 def to_json(self, value: t.Any) -> t.Any:
213 return http_date(value)
214
215 def to_python(self, value: t.Any) -> t.Any:
src/flask/json/tag.py 237

216 return parse_date(value)


217
218
219 class TaggedJSONSerializer:
220 """Serializer that uses a tag system to compactly represent objects that
221 are not JSON types. Passed as the intermediate serializer to
222 :class:`itsdangerous.Serializer`.
223
224 The following extra types are supported:
225
226 * :class:`dict`
227 * :class:`tuple`
228 * :class:`bytes`
229 * :class:`~markupsafe.Markup`
230 * :class:`~uuid.UUID`
231 * :class:`~datetime.datetime`
232 """
233
234 __slots__ = ("tags", "order")
235
236 #: Tag classes to bind when creating the serializer. Other tags can be
237 #: added later using :meth:`~register`.
238 default_tags = [
239 TagDict,
240 PassDict,
241 TagTuple,
242 PassList,
243 TagBytes,
244 TagMarkup,
245 TagUUID,
246 TagDateTime,
247 ]
248
249 def __init__(self) -> None:
250 self.tags: dict[str, JSONTag] = {}
251 self.order: list[JSONTag] = []
252
253 for cls in self.default_tags:
254 self.register(cls)
255
256 def register(
257 self,
258 tag_class: type[JSONTag],
259 force: bool = False,
260 index: int | None = None,
261 ) -> None:
262 """Register a new tag with this serializer.
263
264 :param tag_class: tag class to register. Will be instantiated with this
265 serializer instance.
266 :param force: overwrite an existing tag. If false (default), a
267 :exc:`KeyError` is raised.
268 :param index: index to insert the new tag in the tag order. Useful when
269 the new tag is a special case of an existing tag. If ``None``
270 (default), the tag is appended to the end of the order.
271
272 :raise KeyError: if the tag key is already registered and ``force`` is
273 not true.
274 """
275 tag = tag_class(self)
276 key = tag.key
277
278 if key:
279 if not force and key in self.tags:
280 raise KeyError(f"Tag '{key}' is already registered.")
281
282 self.tags[key] = tag
283
284 if index is None:
285 self.order.append(tag)
286 else:
238

287 self.order.insert(index, tag)


288
289 def tag(self, value: t.Any) -> t.Any:
290 """Convert a value to a tagged representation if necessary."""
291 for tag in self.order:
292 if tag.check(value):
293 return tag.tag(value)
294
295 return value
296
297 def untag(self, value: dict[str, t.Any]) -> t.Any:
298 """Convert a tagged representation back to the original type."""
299 if len(value) != 1:
300 return value
301
302 key = next(iter(value))
303
304 if key not in self.tags:
305 return value
306
307 return self.tags[key].to_python(value[key])
308
309 def _untag_scan(self, value: t.Any) -> t.Any:
310 if isinstance(value, dict):
311 # untag each item recursively
312 value = {k: self._untag_scan(v) for k, v in value.items()}
313 # untag the dict itself
314 value = self.untag(value)
315 elif isinstance(value, list):
316 # untag each item recursively
317 value = [self._untag_scan(item) for item in value]
318
319 return value
320
321 def dumps(self, value: t.Any) -> str:
322 """Tag the value and dump it to a compact JSON string."""
323 return dumps(self.tag(value), separators=(",", ":"))
324
325 def loads(self, value: str) -> t.Any:
326 """Load data from a JSON string and deserialized any tagged objects."""
327 return self._untag_scan(loads(value))

src/flask/helpers.py
1 from __future__ import annotations
2
3 import importlib.util
4 import os
5 import sys
6 import typing as t
7 from datetime import datetime
8 from functools import cache
9 from functools import update_wrapper
10
11 import werkzeug.utils
12 from werkzeug.exceptions import abort as _wz_abort
13 from werkzeug.utils import redirect as _wz_redirect
14 from werkzeug.wrappers import Response as BaseResponse
15
16 from .globals import _cv_request
17 from .globals import current_app
18 from .globals import request
19 from .globals import request_ctx
20 from .globals import session
21 from .signals import message_flashed
22
23 if t.TYPE_CHECKING: # pragma: no cover
24 from .wrappers import Response
25
26
src/flask/helpers.py 239

27 def get_debug_flag() -> bool:


28 """Get whether debug mode should be enabled for the app, indicated by the
29 :envvar:`FLASK_DEBUG` environment variable. The default is ``False``.
30 """
31 val = os.environ.get("FLASK_DEBUG")
32 return bool(val and val.lower() not in {"0", "false", "no"})
33
34
35 def get_load_dotenv(default: bool = True) -> bool:
36 """Get whether the user has disabled loading default dotenv files by
37 setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load
38 the files.
39
40 :param default: What to return if the env var isn't set.
41 """
42 val = os.environ.get("FLASK_SKIP_DOTENV")
43
44 if not val:
45 return default
46
47 return val.lower() in ("0", "false", "no")
48
49
50 @t.overload
51 def stream_with_context(
52 generator_or_function: t.Iterator[t.AnyStr],
53 ) -> t.Iterator[t.AnyStr]: ...
54
55
56 @t.overload
57 def stream_with_context(
58 generator_or_function: t.Callable[..., t.Iterator[t.AnyStr]],
59 ) -> t.Callable[[t.Iterator[t.AnyStr]], t.Iterator[t.AnyStr]]: ...
60
61
62 def stream_with_context(
63 generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]],
64 ) -> t.Iterator[t.AnyStr] | t.Callable[[t.Iterator[t.AnyStr]], t.Iterator[t.AnyStr]]:
65 """Request contexts disappear when the response is started on the server.
66 This is done for efficiency reasons and to make it less likely to encounter
67 memory leaks with badly written WSGI middlewares. The downside is that if
68 you are using streamed responses, the generator cannot access request bound
69 information any more.
70
71 This function however can help you keep the context around for longer::
72
73 from flask import stream_with_context, request, Response
74
75 @app.route('/stream')
76 def streamed_response():
77 @stream_with_context
78 def generate():
79 yield 'Hello '
80 yield request.args['name']
81 yield '!'
82 return Response(generate())
83
84 Alternatively it can also be used around a specific generator::
85
86 from flask import stream_with_context, request, Response
87
88 @app.route('/stream')
89 def streamed_response():
90 def generate():
91 yield 'Hello '
92 yield request.args['name']
93 yield '!'
94 return Response(stream_with_context(generate()))
95
96 .. versionadded:: 0.9
97 """
src/flask/helpers.py 240

98 try:
99 gen = iter(generator_or_function) # type: ignore[arg-type]
100 except TypeError:
101
102 def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any:
103 gen = generator_or_function(*args, **kwargs) # type: ignore[operator]
104 return stream_with_context(gen)
105
106 return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type]
107
108 def generator() -> t.Iterator[t.AnyStr | None]:
109 ctx = _cv_request.get(None)
110 if ctx is None:
111 raise RuntimeError(
112 "'stream_with_context' can only be used when a request"
113 " context is active, such as in a view function."
114 )
115 with ctx:
116 # Dummy sentinel. Has to be inside the context block or we're
117 # not actually keeping the context around.
118 yield None
119
120 # The try/finally is here so that if someone passes a WSGI level
121 # iterator in we're still running the cleanup logic. Generators
122 # don't need that because they are closed on their destruction
123 # automatically.
124 try:
125 yield from gen
126 finally:
127 if hasattr(gen, "close"):
128 gen.close()
129
130 # The trick is to start the generator. Then the code execution runs until
131 # the first dummy None is yielded at which point the context was already
132 # pushed. This item is discarded. Then when the iteration continues the
133 # real generator is executed.
134 wrapped_g = generator()
135 next(wrapped_g)
136 return wrapped_g # type: ignore[return-value]
137
138
139 def make_response(*args: t.Any) -> Response:
140 """Sometimes it is necessary to set additional headers in a view. Because
141 views do not have to return response objects but can return a value that
142 is converted into a response object by Flask itself, it becomes tricky to
143 add headers to it. This function can be called instead of using a return
144 and you will get a response object which you can use to attach headers.
145
146 If view looked like this and you want to add a new header::
147
148 def index():
149 return render_template('index.html', foo=42)
150
151 You can now do something like this::
152
153 def index():
154 response = make_response(render_template('index.html', foo=42))
155 response.headers['X-Parachutes'] = 'parachutes are cool'
156 return response
157
158 This function accepts the very same arguments you can return from a
159 view function. This for example creates a response with a 404 error
160 code::
161
162 response = make_response(render_template('not_found.html'), 404)
163
164 The other use case of this function is to force the return value of a
165 view function into a response which is helpful with view
166 decorators::
167
168 response = make_response(view_function())
src/flask/helpers.py 241

169 response.headers['X-Parachutes'] = 'parachutes are cool'


170
171 Internally this function does the following things:
172
173 - if no arguments are passed, it creates a new response argument
174 - if one argument is passed, :meth:`flask.Flask.make_response`
175 is invoked with it.
176 - if more than one argument is passed, the arguments are passed
177 to the :meth:`flask.Flask.make_response` function as tuple.
178
179 .. versionadded:: 0.6
180 """
181 if not args:
182 return current_app.response_class()
183 if len(args) == 1:
184 args = args[0]
185 return current_app.make_response(args)
186
187
188 def url_for(
189 endpoint: str,
190 *,
191 _anchor: str | None = None,
192 _method: str | None = None,
193 _scheme: str | None = None,
194 _external: bool | None = None,
195 **values: t.Any,
196 ) -> str:
197 """Generate a URL to the given endpoint with the given values.
198
199 This requires an active request or application context, and calls
200 :meth:`current_app.url_for() <flask.Flask.url_for>`. See that method
201 for full documentation.
202
203 :param endpoint: The endpoint name associated with the URL to
204 generate. If this starts with a ``.``, the current blueprint
205 name (if any) will be used.
206 :param _anchor: If given, append this as ``#anchor`` to the URL.
207 :param _method: If given, generate the URL associated with this
208 method for the endpoint.
209 :param _scheme: If given, the URL will have this scheme if it is
210 external.
211 :param _external: If given, prefer the URL to be internal (False) or
212 require it to be external (True). External URLs include the
213 scheme and domain. When not in an active request, URLs are
214 external by default.
215 :param values: Values to use for the variable parts of the URL rule.
216 Unknown keys are appended as query string arguments, like
217 ``?a=b&c=d``.
218
219 .. versionchanged:: 2.2
220 Calls ``current_app.url_for``, allowing an app to override the
221 behavior.
222
223 .. versionchanged:: 0.10
224 The ``_scheme`` parameter was added.
225
226 .. versionchanged:: 0.9
227 The ``_anchor`` and ``_method`` parameters were added.
228
229 .. versionchanged:: 0.9
230 Calls ``app.handle_url_build_error`` on build errors.
231 """
232 return current_app.url_for(
233 endpoint,
234 _anchor=_anchor,
235 _method=_method,
236 _scheme=_scheme,
237 _external=_external,
238 **values,
239 )
src/flask/helpers.py 242

240
241
242 def redirect(
243 location: str, code: int = 302, Response: type[BaseResponse] | None = None
244 ) -> BaseResponse:
245 """Create a redirect response object.
246
247 If :data:`~flask.current_app` is available, it will use its
248 :meth:`~flask.Flask.redirect` method, otherwise it will use
249 :func:`werkzeug.utils.redirect`.
250
251 :param location: The URL to redirect to.
252 :param code: The status code for the redirect.
253 :param Response: The response class to use. Not used when
254 ``current_app`` is active, which uses ``app.response_class``.
255
256 .. versionadded:: 2.2
257 Calls ``current_app.redirect`` if available instead of always
258 using Werkzeug's default ``redirect``.
259 """
260 if current_app:
261 return current_app.redirect(location, code=code)
262
263 return _wz_redirect(location, code=code, Response=Response)
264
265
266 def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
267 """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given
268 status code.
269
270 If :data:`~flask.current_app` is available, it will call its
271 :attr:`~flask.Flask.aborter` object, otherwise it will use
272 :func:`werkzeug.exceptions.abort`.
273
274 :param code: The status code for the exception, which must be
275 registered in ``app.aborter``.
276 :param args: Passed to the exception.
277 :param kwargs: Passed to the exception.
278
279 .. versionadded:: 2.2
280 Calls ``current_app.aborter`` if available instead of always
281 using Werkzeug's default ``abort``.
282 """
283 if current_app:
284 current_app.aborter(code, *args, **kwargs)
285
286 _wz_abort(code, *args, **kwargs)
287
288
289 def get_template_attribute(template_name: str, attribute: str) -> t.Any:
290 """Loads a macro (or variable) a template exports. This can be used to
291 invoke a macro from within Python code. If you for example have a
292 template named :file:`_cider.html` with the following contents:
293
294 .. sourcecode:: html+jinja
295
296 {% macro hello(name) %}Hello {{ name }}!{% endmacro %}
297
298 You can access this from Python code like this::
299
300 hello = get_template_attribute('_cider.html', 'hello')
301 return hello('World')
302
303 .. versionadded:: 0.2
304
305 :param template_name: the name of the template
306 :param attribute: the name of the variable of macro to access
307 """
308 return getattr(current_app.jinja_env.get_template(template_name).module, attribute)
309
310
src/flask/helpers.py 243

311 def flash(message: str, category: str = "message") -> None:


312 """Flashes a message to the next request. In order to remove the
313 flashed message from the session and to display it to the user,
314 the template has to call :func:`get_flashed_messages`.
315
316 .. versionchanged:: 0.3
317 `category` parameter added.
318
319 :param message: the message to be flashed.
320 :param category: the category for the message. The following values
321 are recommended: ``'message'`` for any kind of message,
322 ``'error'`` for errors, ``'info'`` for information
323 messages and ``'warning'`` for warnings. However any
324 kind of string can be used as category.
325 """
326 # Original implementation:
327 #
328 # session.setdefault('_flashes', []).append((category, message))
329 #
330 # This assumed that changes made to mutable structures in the session are
331 # always in sync with the session object, which is not true for session
332 # implementations that use external storage for keeping their keys/values.
333 flashes = session.get("_flashes", [])
334 flashes.append((category, message))
335 session["_flashes"] = flashes
336 app = current_app._get_current_object() # type: ignore
337 message_flashed.send(
338 app,
339 _async_wrapper=app.ensure_sync,
340 message=message,
341 category=category,
342 )
343
344
345 def get_flashed_messages(
346 with_categories: bool = False, category_filter: t.Iterable[str] = ()
347 ) -> list[str] | list[tuple[str, str]]:
348 """Pulls all flashed messages from the session and returns them.
349 Further calls in the same request to the function will return
350 the same messages. By default just the messages are returned,
351 but when `with_categories` is set to ``True``, the return value will
352 be a list of tuples in the form ``(category, message)`` instead.
353
354 Filter the flashed messages to one or more categories by providing those
355 categories in `category_filter`. This allows rendering categories in
356 separate html blocks. The `with_categories` and `category_filter`
357 arguments are distinct:
358
359 * `with_categories` controls whether categories are returned with message
360 text (``True`` gives a tuple, where ``False`` gives just the message text).
361 * `category_filter` filters the messages down to only those matching the
362 provided categories.
363
364 See :doc:`/patterns/flashing` for examples.
365
366 .. versionchanged:: 0.3
367 `with_categories` parameter added.
368
369 .. versionchanged:: 0.9
370 `category_filter` parameter added.
371
372 :param with_categories: set to ``True`` to also receive categories.
373 :param category_filter: filter of categories to limit return values. Only
374 categories in the list will be returned.
375 """
376 flashes = request_ctx.flashes
377 if flashes is None:
378 flashes = session.pop("_flashes") if "_flashes" in session else []
379 request_ctx.flashes = flashes
380 if category_filter:
381 flashes = list(filter(lambda f: f[0] in category_filter, flashes))
src/flask/helpers.py 244

382 if not with_categories:


383 return [x[1] for x in flashes]
384 return flashes
385
386
387 def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]:
388 if kwargs.get("max_age") is None:
389 kwargs["max_age"] = current_app.get_send_file_max_age
390
391 kwargs.update(
392 environ=request.environ,
393 use_x_sendfile=current_app.config["USE_X_SENDFILE"],
394 response_class=current_app.response_class,
395 _root_path=current_app.root_path, # type: ignore
396 )
397 return kwargs
398
399
400 def send_file(
401 path_or_file: os.PathLike[t.AnyStr] | str | t.BinaryIO,
402 mimetype: str | None = None,
403 as_attachment: bool = False,
404 download_name: str | None = None,
405 conditional: bool = True,
406 etag: bool | str = True,
407 last_modified: datetime | int | float | None = None,
408 max_age: None | (int | t.Callable[[str | None], int | None]) = None,
409 ) -> Response:
410 """Send the contents of a file to the client.
411
412 The first argument can be a file path or a file-like object. Paths
413 are preferred in most cases because Werkzeug can manage the file and
414 get extra information from the path. Passing a file-like object
415 requires that the file is opened in binary mode, and is mostly
416 useful when building a file in memory with :class:`io.BytesIO`.
417
418 Never pass file paths provided by a user. The path is assumed to be
419 trusted, so a user could craft a path to access a file you didn't
420 intend. Use :func:`send_from_directory` to safely serve
421 user-requested paths from within a directory.
422
423 If the WSGI server sets a ``file_wrapper`` in ``environ``, it is
424 used, otherwise Werkzeug's built-in wrapper is used. Alternatively,
425 if the HTTP server supports ``X-Sendfile``, configuring Flask with
426 ``USE_X_SENDFILE = True`` will tell the server to send the given
427 path, which is much more efficient than reading it in Python.
428
429 :param path_or_file: The path to the file to send, relative to the
430 current working directory if a relative path is given.
431 Alternatively, a file-like object opened in binary mode. Make
432 sure the file pointer is seeked to the start of the data.
433 :param mimetype: The MIME type to send for the file. If not
434 provided, it will try to detect it from the file name.
435 :param as_attachment: Indicate to a browser that it should offer to
436 save the file instead of displaying it.
437 :param download_name: The default name browsers will use when saving
438 the file. Defaults to the passed file name.
439 :param conditional: Enable conditional and range responses based on
440 request headers. Requires passing a file path and ``environ``.
441 :param etag: Calculate an ETag for the file, which requires passing
442 a file path. Can also be a string to use instead.
443 :param last_modified: The last modified time to send for the file,
444 in seconds. If not provided, it will try to detect it from the
445 file path.
446 :param max_age: How long the client should cache the file, in
447 seconds. If set, ``Cache-Control`` will be ``public``, otherwise
448 it will be ``no-cache`` to prefer conditional caching.
449
450 .. versionchanged:: 2.0
451 ``download_name`` replaces the ``attachment_filename``
452 parameter. If ``as_attachment=False``, it is passed with
src/flask/helpers.py 245

453 ``Content-Disposition: inline`` instead.


454
455 .. versionchanged:: 2.0
456 ``max_age`` replaces the ``cache_timeout`` parameter.
457 ``conditional`` is enabled and ``max_age`` is not set by
458 default.
459
460 .. versionchanged:: 2.0
461 ``etag`` replaces the ``add_etags`` parameter. It can be a
462 string to use instead of generating one.
463
464 .. versionchanged:: 2.0
465 Passing a file-like object that inherits from
466 :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather
467 than sending an empty file.
468
469 .. versionadded:: 2.0
470 Moved the implementation to Werkzeug. This is now a wrapper to
471 pass some Flask-specific arguments.
472
473 .. versionchanged:: 1.1
474 ``filename`` may be a :class:`~os.PathLike` object.
475
476 .. versionchanged:: 1.1
477 Passing a :class:`~io.BytesIO` object supports range requests.
478
479 .. versionchanged:: 1.0.3
480 Filenames are encoded with ASCII instead of Latin-1 for broader
481 compatibility with WSGI servers.
482
483 .. versionchanged:: 1.0
484 UTF-8 filenames as specified in :rfc:`2231` are supported.
485
486 .. versionchanged:: 0.12
487 The filename is no longer automatically inferred from file
488 objects. If you want to use automatic MIME and etag support,
489 pass a filename via ``filename_or_fp`` or
490 ``attachment_filename``.
491
492 .. versionchanged:: 0.12
493 ``attachment_filename`` is preferred over ``filename`` for MIME
494 detection.
495
496 .. versionchanged:: 0.9
497 ``cache_timeout`` defaults to
498 :meth:`Flask.get_send_file_max_age`.
499
500 .. versionchanged:: 0.7
501 MIME guessing and etag support for file-like objects was
502 removed because it was unreliable. Pass a filename if you are
503 able to, otherwise attach an etag yourself.
504
505 .. versionchanged:: 0.5
506 The ``add_etags``, ``cache_timeout`` and ``conditional``
507 parameters were added. The default behavior is to add etags.
508
509 .. versionadded:: 0.2
510 """
511 return werkzeug.utils.send_file( # type: ignore[return-value]
512 **_prepare_send_file_kwargs(
513 path_or_file=path_or_file,
514 environ=request.environ,
515 mimetype=mimetype,
516 as_attachment=as_attachment,
517 download_name=download_name,
518 conditional=conditional,
519 etag=etag,
520 last_modified=last_modified,
521 max_age=max_age,
522 )
523 )
src/flask/helpers.py 246

524
525
526 def send_from_directory(
527 directory: os.PathLike[str] | str,
528 path: os.PathLike[str] | str,
529 **kwargs: t.Any,
530 ) -> Response:
531 """Send a file from within a directory using :func:`send_file`.
532
533 .. code-block:: python
534
535 @app.route("/uploads/<path:name>")
536 def download_file(name):
537 return send_from_directory(
538 app.config['UPLOAD_FOLDER'], name, as_attachment=True
539 )
540
541 This is a secure way to serve files from a folder, such as static
542 files or uploads. Uses :func:`~werkzeug.security.safe_join` to
543 ensure the path coming from the client is not maliciously crafted to
544 point outside the specified directory.
545
546 If the final path does not point to an existing regular file,
547 raises a 404 :exc:`~werkzeug.exceptions.NotFound` error.
548
549 :param directory: The directory that ``path`` must be located under,
550 relative to the current application's root path. This *must not*
551 be a value provided by the client, otherwise it becomes insecure.
552 :param path: The path to the file to send, relative to
553 ``directory``.
554 :param kwargs: Arguments to pass to :func:`send_file`.
555
556 .. versionchanged:: 2.0
557 ``path`` replaces the ``filename`` parameter.
558
559 .. versionadded:: 2.0
560 Moved the implementation to Werkzeug. This is now a wrapper to
561 pass some Flask-specific arguments.
562
563 .. versionadded:: 0.5
564 """
565 return werkzeug.utils.send_from_directory( # type: ignore[return-value]
566 directory, path, **_prepare_send_file_kwargs(**kwargs)
567 )
568
569
570 def get_root_path(import_name: str) -> str:
571 """Find the root path of a package, or the path that contains a
572 module. If it cannot be found, returns the current working
573 directory.
574
575 Not to be confused with the value returned by :func:`find_package`.
576
577 :meta private:
578 """
579 # Module already imported and has a file attribute. Use that first.
580 mod = sys.modules.get(import_name)
581
582 if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None:
583 return os.path.dirname(os.path.abspath(mod.__file__))
584
585 # Next attempt: check the loader.
586 try:
587 spec = importlib.util.find_spec(import_name)
588
589 if spec is None:
590 raise ValueError
591 except (ImportError, ValueError):
592 loader = None
593 else:
594 loader = spec.loader
247

595
596 # Loader does not exist or we're referring to an unloaded main
597 # module or a main module without path (interactive sessions), go
598 # with the current working directory.
599 if loader is None:
600 return os.getcwd()
601
602 if hasattr(loader, "get_filename"):
603 filepath = loader.get_filename(import_name) # pyright: ignore
604 else:
605 # Fall back to imports.
606 __import__(import_name)
607 mod = sys.modules[import_name]
608 filepath = getattr(mod, "__file__", None)
609
610 # If we don't have a file path it might be because it is a
611 # namespace package. In this case pick the root path from the
612 # first module that is contained in the package.
613 if filepath is None:
614 raise RuntimeError(
615 "No root path can be found for the provided module"
616 f" {import_name!r}. This can happen because the module"
617 " came from an import hook that does not provide file"
618 " name information or because it's a namespace package."
619 " In this case the root path needs to be explicitly"
620 " provided."
621 )
622
623 # filepath is import_name.py for a module, or __init__.py for a package.
624 return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return]
625
626
627 @cache
628 def _split_blueprint_path(name: str) -> list[str]:
629 out: list[str] = [name]
630
631 if "." in name:
632 out.extend(_split_blueprint_path(name.rpartition(".")[0]))
633
634 return out

src/flask/cli.py
1 from __future__ import annotations
2
3 import ast
4 import collections.abc as cabc
5 import importlib.metadata
6 import inspect
7 import os
8 import platform
9 import re
10 import sys
11 import traceback
12 import typing as t
13 from functools import update_wrapper
14 from operator import itemgetter
15 from types import ModuleType
16
17 import click
18 from click.core import ParameterSource
19 from werkzeug import run_simple
20 from werkzeug.serving import is_running_from_reloader
21 from werkzeug.utils import import_string
22
23 from .globals import current_app
24 from .helpers import get_debug_flag
25 from .helpers import get_load_dotenv
26
27 if t.TYPE_CHECKING:
src/flask/cli.py 248

28 import ssl
29
30 from _typeshed.wsgi import StartResponse
31 from _typeshed.wsgi import WSGIApplication
32 from _typeshed.wsgi import WSGIEnvironment
33
34 from .app import Flask
35
36
37 class NoAppException(click.UsageError):
38 """Raised if an application cannot be found or loaded."""
39
40
41 def find_best_app(module: ModuleType) -> Flask:
42 """Given a module instance this tries to find the best possible
43 application in the module or raises an exception.
44 """
45 from . import Flask
46
47 # Search for the most common names first.
48 for attr_name in ("app", "application"):
49 app = getattr(module, attr_name, None)
50
51 if isinstance(app, Flask):
52 return app
53
54 # Otherwise find the only object that is a Flask instance.
55 matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]
56
57 if len(matches) == 1:
58 return matches[0]
59 elif len(matches) > 1:
60 raise NoAppException(
61 "Detected multiple Flask applications in module"
62 f" '{module.__name__}'. Use '{module.__name__}:name'"
63 " to specify the correct one."
64 )
65
66 # Search for app factory functions.
67 for attr_name in ("create_app", "make_app"):
68 app_factory = getattr(module, attr_name, None)
69
70 if inspect.isfunction(app_factory):
71 try:
72 app = app_factory()
73
74 if isinstance(app, Flask):
75 return app
76 except TypeError as e:
77 if not _called_with_wrong_args(app_factory):
78 raise
79
80 raise NoAppException(
81 f"Detected factory '{attr_name}' in module '{module.__name__}',"
82 " but could not call it without arguments. Use"
83 f" '{module.__name__}:{attr_name}(args)'"
84 " to specify arguments."
85 ) from e
86
87 raise NoAppException(
88 "Failed to find Flask application or factory in module"
89 f" '{module.__name__}'. Use '{module.__name__}:name'"
90 " to specify one."
91 )
92
93
94 def _called_with_wrong_args(f: t.Callable[..., Flask]) -> bool:
95 """Check whether calling a function raised a ``TypeError`` because
96 the call failed or because something in the factory raised the
97 error.
98
src/flask/cli.py 249

99 :param f: The function that was called.


100 :return: ``True`` if the call failed.
101 """
102 tb = sys.exc_info()[2]
103
104 try:
105 while tb is not None:
106 if tb.tb_frame.f_code is f.__code__:
107 # In the function, it was called successfully.
108 return False
109
110 tb = tb.tb_next
111
112 # Didn't reach the function.
113 return True
114 finally:
115 # Delete tb to break a circular reference.
116 # https://docs.python.org/2/library/sys.html#sys.exc_info
117 del tb
118
119
120 def find_app_by_string(module: ModuleType, app_name: str) -> Flask:
121 """Check if the given string is a variable name or a function. Call
122 a function to get the app instance, or return the variable directly.
123 """
124 from . import Flask
125
126 # Parse app_name as a single expression to determine if it's a valid
127 # attribute name or function call.
128 try:
129 expr = ast.parse(app_name.strip(), mode="eval").body
130 except SyntaxError:
131 raise NoAppException(
132 f"Failed to parse {app_name!r} as an attribute name or function call."
133 ) from None
134
135 if isinstance(expr, ast.Name):
136 name = expr.id
137 args = []
138 kwargs = {}
139 elif isinstance(expr, ast.Call):
140 # Ensure the function name is an attribute name only.
141 if not isinstance(expr.func, ast.Name):
142 raise NoAppException(
143 f"Function reference must be a simple name: {app_name!r}."
144 )
145
146 name = expr.func.id
147
148 # Parse the positional and keyword arguments as literals.
149 try:
150 args = [ast.literal_eval(arg) for arg in expr.args]
151 kwargs = {
152 kw.arg: ast.literal_eval(kw.value)
153 for kw in expr.keywords
154 if kw.arg is not None
155 }
156 except ValueError:
157 # literal_eval gives cryptic error messages, show a generic
158 # message with the full expression instead.
159 raise NoAppException(
160 f"Failed to parse arguments as literal values: {app_name!r}."
161 ) from None
162 else:
163 raise NoAppException(
164 f"Failed to parse {app_name!r} as an attribute name or function call."
165 )
166
167 try:
168 attr = getattr(module, name)
169 except AttributeError as e:
src/flask/cli.py 250

170 raise NoAppException(


171 f"Failed to find attribute {name!r} in {module.__name__!r}."
172 ) from e
173
174 # If the attribute is a function, call it with any args and kwargs
175 # to get the real application.
176 if inspect.isfunction(attr):
177 try:
178 app = attr(*args, **kwargs)
179 except TypeError as e:
180 if not _called_with_wrong_args(attr):
181 raise
182
183 raise NoAppException(
184 f"The factory {app_name!r} in module"
185 f" {module.__name__!r} could not be called with the"
186 " specified arguments."
187 ) from e
188 else:
189 app = attr
190
191 if isinstance(app, Flask):
192 return app
193
194 raise NoAppException(
195 "A valid Flask application was not obtained from"
196 f" '{module.__name__}:{app_name}'."
197 )
198
199
200 def prepare_import(path: str) -> str:
201 """Given a filename this will try to calculate the python path, add it
202 to the search path and return the actual module name that is expected.
203 """
204 path = os.path.realpath(path)
205
206 fname, ext = os.path.splitext(path)
207 if ext == ".py":
208 path = fname
209
210 if os.path.basename(path) == "__init__":
211 path = os.path.dirname(path)
212
213 module_name = []
214
215 # move up until outside package structure (no __init__.py)
216 while True:
217 path, name = os.path.split(path)
218 module_name.append(name)
219
220 if not os.path.exists(os.path.join(path, "__init__.py")):
221 break
222
223 if sys.path[0] != path:
224 sys.path.insert(0, path)
225
226 return ".".join(module_name[::-1])
227
228
229 @t.overload
230 def locate_app(
231 module_name: str, app_name: str | None, raise_if_not_found: t.Literal[True] = True
232 ) -> Flask: ...
233
234
235 @t.overload
236 def locate_app(
237 module_name: str, app_name: str | None, raise_if_not_found: t.Literal[False] = ...
238 ) -> Flask | None: ...
239
240
src/flask/cli.py 251

241 def locate_app(


242 module_name: str, app_name: str | None, raise_if_not_found: bool = True
243 ) -> Flask | None:
244 try:
245 __import__(module_name)
246 except ImportError:
247 # Reraise the ImportError if it occurred within the imported module.
248 # Determine this by checking whether the trace has a depth > 1.
249 if sys.exc_info()[2].tb_next: # type: ignore[union-attr]
250 raise NoAppException(
251 f"While importing {module_name!r}, an ImportError was"
252 f" raised:\n\n{traceback.format_exc()}"
253 ) from None
254 elif raise_if_not_found:
255 raise NoAppException(f"Could not import {module_name!r}.") from None
256 else:
257 return None
258
259 module = sys.modules[module_name]
260
261 if app_name is None:
262 return find_best_app(module)
263 else:
264 return find_app_by_string(module, app_name)
265
266
267 def get_version(ctx: click.Context, param: click.Parameter, value: t.Any) -> None:
268 if not value or ctx.resilient_parsing:
269 return
270
271 flask_version = importlib.metadata.version("flask")
272 werkzeug_version = importlib.metadata.version("werkzeug")
273
274 click.echo(
275 f"Python {platform.python_version()}\n"
276 f"Flask {flask_version}\n"
277 f"Werkzeug {werkzeug_version}",
278 color=ctx.color,
279 )
280 ctx.exit()
281
282
283 version_option = click.Option(
284 ["--version"],
285 help="Show the Flask version.",
286 expose_value=False,
287 callback=get_version,
288 is_flag=True,
289 is_eager=True,
290 )
291
292
293 class ScriptInfo:
294 """Helper object to deal with Flask applications. This is usually not
295 necessary to interface with as it's used internally in the dispatching
296 to click. In future versions of Flask this object will most likely play
297 a bigger role. Typically it's created automatically by the
298 :class:`FlaskGroup` but you can also manually create it and pass it
299 onwards as click object.
300
301 .. versionchanged:: 3.1
302 Added the ``load_dotenv_defaults`` parameter and attribute.
303 """
304
305 def __init__(
306 self,
307 app_import_path: str | None = None,
308 create_app: t.Callable[..., Flask] | None = None,
309 set_debug_flag: bool = True,
310 load_dotenv_defaults: bool = True,
311 ) -> None:
src/flask/cli.py 252

312 #: Optionally the import path for the Flask application.


313 self.app_import_path = app_import_path
314 #: Optionally a function that is passed the script info to create
315 #: the instance of the application.
316 self.create_app = create_app
317 #: A dictionary with arbitrary data that can be associated with
318 #: this script info.
319 self.data: dict[t.Any, t.Any] = {}
320 self.set_debug_flag = set_debug_flag
321
322 self.load_dotenv_defaults = get_load_dotenv(load_dotenv_defaults)
323 """Whether default ``.flaskenv`` and ``.env`` files should be loaded.
324
325 ``ScriptInfo`` doesn't load anything, this is for reference when doing
326 the load elsewhere during processing.
327
328 .. versionadded:: 3.1
329 """
330
331 self._loaded_app: Flask | None = None
332
333 def load_app(self) -> Flask:
334 """Loads the Flask app (if not yet loaded) and returns it. Calling
335 this multiple times will just result in the already loaded app to
336 be returned.
337 """
338 if self._loaded_app is not None:
339 return self._loaded_app
340 app: Flask | None = None
341 if self.create_app is not None:
342 app = self.create_app()
343 else:
344 if self.app_import_path:
345 path, name = (
346 re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None]
347 )[:2]
348 import_name = prepare_import(path)
349 app = locate_app(import_name, name)
350 else:
351 for path in ("wsgi.py", "app.py"):
352 import_name = prepare_import(path)
353 app = locate_app(import_name, None, raise_if_not_found=False)
354
355 if app is not None:
356 break
357
358 if app is None:
359 raise NoAppException(
360 "Could not locate a Flask application. Use the"
361 " 'flask --app' option, 'FLASK_APP' environment"
362 " variable, or a 'wsgi.py' or 'app.py' file in the"
363 " current directory."
364 )
365
366 if self.set_debug_flag:
367 # Update the app's debug flag through the descriptor so that
368 # other values repopulate as well.
369 app.debug = get_debug_flag()
370
371 self._loaded_app = app
372 return app
373
374
375 pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
376
377 F = t.TypeVar("F", bound=t.Callable[..., t.Any])
378
379
380 def with_appcontext(f: F) -> F:
381 """Wraps a callback so that it's guaranteed to be executed with the
382 script's application context.
src/flask/cli.py 253

383
384 Custom commands (and their options) registered under ``app.cli`` or
385 ``blueprint.cli`` will always have an app context available, this
386 decorator is not required in that case.
387
388 .. versionchanged:: 2.2
389 The app context is active for subcommands as well as the
390 decorated callback. The app context is always available to
391 ``app.cli`` command and parameter callbacks.
392 """
393
394 @click.pass_context
395 def decorator(ctx: click.Context, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
396 if not current_app:
397 app = ctx.ensure_object(ScriptInfo).load_app()
398 ctx.with_resource(app.app_context())
399
400 return ctx.invoke(f, *args, **kwargs)
401
402 return update_wrapper(decorator, f) # type: ignore[return-value]
403
404
405 class AppGroup(click.Group):
406 """This works similar to a regular click :class:`~click.Group` but it
407 changes the behavior of the :meth:`command` decorator so that it
408 automatically wraps the functions in :func:`with_appcontext`.
409
410 Not to be confused with :class:`FlaskGroup`.
411 """
412
413 def command( # type: ignore[override]
414 self, *args: t.Any, **kwargs: t.Any
415 ) -> t.Callable[[t.Callable[..., t.Any]], click.Command]:
416 """This works exactly like the method of the same name on a regular
417 :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
418 unless it's disabled by passing ``with_appcontext=False``.
419 """
420 wrap_for_ctx = kwargs.pop("with_appcontext", True)
421
422 def decorator(f: t.Callable[..., t.Any]) -> click.Command:
423 if wrap_for_ctx:
424 f = with_appcontext(f)
425 return super(AppGroup, self).command(*args, **kwargs)(f) # type: ignore[no-any-return]
426
427 return decorator
428
429 def group( # type: ignore[override]
430 self, *args: t.Any, **kwargs: t.Any
431 ) -> t.Callable[[t.Callable[..., t.Any]], click.Group]:
432 """This works exactly like the method of the same name on a regular
433 :class:`click.Group` but it defaults the group class to
434 :class:`AppGroup`.
435 """
436 kwargs.setdefault("cls", AppGroup)
437 return super().group(*args, **kwargs) # type: ignore[no-any-return]
438
439
440 def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:
441 if value is None:
442 return None
443
444 info = ctx.ensure_object(ScriptInfo)
445 info.app_import_path = value
446 return value
447
448
449 # This option is eager so the app will be available if --help is given.
450 # --help is also eager, so --app must be before it in the param list.
451 # no_args_is_help bypasses eager processing, so this option must be
452 # processed manually in that case to ensure FLASK_APP gets picked up.
453 _app_option = click.Option(
src/flask/cli.py 254

454 ["-A", "--app"],


455 metavar="IMPORT",
456 help=(
457 "The Flask application or factory function to load, in the form 'module:name'."
458 " Module can be a dotted import or file path. Name is not required if it is"
459 " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"
460 " pass arguments."
461 ),
462 is_eager=True,
463 expose_value=False,
464 callback=_set_app,
465 )
466
467
468 def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
469 # If the flag isn't provided, it will default to False. Don't use
470 # that, let debug be set by env in that case.
471 source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
472
473 if source is not None and source in (
474 ParameterSource.DEFAULT,
475 ParameterSource.DEFAULT_MAP,
476 ):
477 return None
478
479 # Set with env var instead of ScriptInfo.load so that it can be
480 # accessed early during a factory function.
481 os.environ["FLASK_DEBUG"] = "1" if value else "0"
482 return value
483
484
485 _debug_option = click.Option(
486 ["--debug/--no-debug"],
487 help="Set debug mode.",
488 expose_value=False,
489 callback=_set_debug,
490 )
491
492
493 def _env_file_callback(
494 ctx: click.Context, param: click.Option, value: str | None
495 ) -> str | None:
496 try:
497 import dotenv # noqa: F401
498 except ImportError:
499 # Only show an error if a value was passed, otherwise we still want to
500 # call load_dotenv and show a message without exiting.
501 if value is not None:
502 raise click.BadParameter(
503 "python-dotenv must be installed to load an env file.",
504 ctx=ctx,
505 param=param,
506 ) from None
507
508 # Load if a value was passed, or we want to load default files, or both.
509 if value is not None or ctx.obj.load_dotenv_defaults:
510 load_dotenv(value, load_defaults=ctx.obj.load_dotenv_defaults)
511
512 return value
513
514
515 # This option is eager so env vars are loaded as early as possible to be
516 # used by other options.
517 _env_file_option = click.Option(
518 ["-e", "--env-file"],
519 type=click.Path(exists=True, dir_okay=False),
520 help=(
521 "Load environment variables from this file, taking precedence over"
522 " those set by '.env' and '.flaskenv'. Variables set directly in the"
523 " environment take highest precedence. python-dotenv must be installed."
524 ),
src/flask/cli.py 255

525 is_eager=True,
526 expose_value=False,
527 callback=_env_file_callback,
528 )
529
530
531 class FlaskGroup(AppGroup):
532 """Special subclass of the :class:`AppGroup` group that supports
533 loading more commands from the configured Flask app. Normally a
534 developer does not have to interface with this class but there are
535 some very advanced use cases for which it makes sense to create an
536 instance of this. see :ref:`custom-scripts`.
537
538 :param add_default_commands: if this is True then the default run and
539 shell commands will be added.
540 :param add_version_option: adds the ``--version`` option.
541 :param create_app: an optional callback that is passed the script info and
542 returns the loaded app.
543 :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
544 files to set environment variables. Will also change the working
545 directory to the directory containing the first file found.
546 :param set_debug_flag: Set the app's debug flag.
547
548 .. versionchanged:: 3.1
549 ``-e path`` takes precedence over default ``.env`` and ``.flaskenv`` files.
550
551 .. versionchanged:: 2.2
552 Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
553
554 .. versionchanged:: 2.2
555 An app context is pushed when running ``app.cli`` commands, so
556 ``@with_appcontext`` is no longer required for those commands.
557
558 .. versionchanged:: 1.0
559 If installed, python-dotenv will be used to load environment variables
560 from :file:`.env` and :file:`.flaskenv` files.
561 """
562
563 def __init__(
564 self,
565 add_default_commands: bool = True,
566 create_app: t.Callable[..., Flask] | None = None,
567 add_version_option: bool = True,
568 load_dotenv: bool = True,
569 set_debug_flag: bool = True,
570 **extra: t.Any,
571 ) -> None:
572 params: list[click.Parameter] = list(extra.pop("params", None) or ())
573 # Processing is done with option callbacks instead of a group
574 # callback. This allows users to make a custom group callback
575 # without losing the behavior. --env-file must come first so
576 # that it is eagerly evaluated before --app.
577 params.extend((_env_file_option, _app_option, _debug_option))
578
579 if add_version_option:
580 params.append(version_option)
581
582 if "context_settings" not in extra:
583 extra["context_settings"] = {}
584
585 extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK")
586
587 super().__init__(params=params, **extra)
588
589 self.create_app = create_app
590 self.load_dotenv = load_dotenv
591 self.set_debug_flag = set_debug_flag
592
593 if add_default_commands:
594 self.add_command(run_command)
595 self.add_command(shell_command)
src/flask/cli.py 256

596 self.add_command(routes_command)
597
598 self._loaded_plugin_commands = False
599
600 def _load_plugin_commands(self) -> None:
601 if self._loaded_plugin_commands:
602 return
603
604 if sys.version_info >= (3, 10):
605 from importlib import metadata
606 else:
607 # Use a backport on Python < 3.10. We technically have
608 # importlib.metadata on 3.8+, but the API changed in 3.10,
609 # so use the backport for consistency.
610 import importlib_metadata as metadata # pyright: ignore
611
612 for ep in metadata.entry_points(group="flask.commands"):
613 self.add_command(ep.load(), ep.name)
614
615 self._loaded_plugin_commands = True
616
617 def get_command(self, ctx: click.Context, name: str) -> click.Command | None:
618 self._load_plugin_commands()
619 # Look up built-in and plugin commands, which should be
620 # available even if the app fails to load.
621 rv = super().get_command(ctx, name)
622
623 if rv is not None:
624 return rv
625
626 info = ctx.ensure_object(ScriptInfo)
627
628 # Look up commands provided by the app, showing an error and
629 # continuing if the app couldn't be loaded.
630 try:
631 app = info.load_app()
632 except NoAppException as e:
633 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
634 return None
635
636 # Push an app context for the loaded app unless it is already
637 # active somehow. This makes the context available to parameter
638 # and command callbacks without needing @with_appcontext.
639 if not current_app or current_app._get_current_object() is not app: # type: ignore[attr-defined]
640 ctx.with_resource(app.app_context())
641
642 return app.cli.get_command(ctx, name)
643
644 def list_commands(self, ctx: click.Context) -> list[str]:
645 self._load_plugin_commands()
646 # Start with the built-in and plugin commands.
647 rv = set(super().list_commands(ctx))
648 info = ctx.ensure_object(ScriptInfo)
649
650 # Add commands provided by the app, showing an error and
651 # continuing if the app couldn't be loaded.
652 try:
653 rv.update(info.load_app().cli.list_commands(ctx))
654 except NoAppException as e:
655 # When an app couldn't be loaded, show the error message
656 # without the traceback.
657 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
658 except Exception:
659 # When any other errors occurred during loading, show the
660 # full traceback.
661 click.secho(f"{traceback.format_exc()}\n", err=True, fg="red")
662
663 return sorted(rv)
664
665 def make_context(
666 self,
src/flask/cli.py 257

667 info_name: str | None,


668 args: list[str],
669 parent: click.Context | None = None,
670 **extra: t.Any,
671 ) -> click.Context:
672 # Set a flag to tell app.run to become a no-op. If app.run was
673 # not in a __name__ == __main__ guard, it would start the server
674 # when importing, blocking whatever command is being called.
675 os.environ["FLASK_RUN_FROM_CLI"] = "true"
676
677 if "obj" not in extra and "obj" not in self.context_settings:
678 extra["obj"] = ScriptInfo(
679 create_app=self.create_app,
680 set_debug_flag=self.set_debug_flag,
681 load_dotenv_defaults=self.load_dotenv,
682 )
683
684 return super().make_context(info_name, args, parent=parent, **extra)
685
686 def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
687 if not args and self.no_args_is_help:
688 # Attempt to load --env-file and --app early in case they
689 # were given as env vars. Otherwise no_args_is_help will not
690 # see commands from app.cli.
691 _env_file_option.handle_parse_result(ctx, {}, [])
692 _app_option.handle_parse_result(ctx, {}, [])
693
694 return super().parse_args(ctx, args)
695
696
697 def _path_is_ancestor(path: str, other: str) -> bool:
698 """Take ``other`` and remove the length of ``path`` from it. Then join it
699 to ``path``. If it is the original value, ``path`` is an ancestor of
700 ``other``."""
701 return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
702
703
704 def load_dotenv(
705 path: str | os.PathLike[str] | None = None, load_defaults: bool = True
706 ) -> bool:
707 """Load "dotenv" files to set environment variables. A given path takes
708 precedence over ``.env``, which takes precedence over ``.flaskenv``. After
709 loading and combining these files, values are only set if the key is not
710 already set in ``os.environ``.
711
712 This is a no-op if `python-dotenv`_ is not installed.
713
714 .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
715
716 :param path: Load the file at this location.
717 :param load_defaults: Search for and load the default ``.flaskenv`` and
718 ``.env`` files.
719 :return: ``True`` if at least one env var was loaded.
720
721 .. versionchanged:: 3.1
722 Added the ``load_defaults`` parameter. A given path takes precedence
723 over default files.
724
725 .. versionchanged:: 2.0
726 The current directory is not changed to the location of the
727 loaded file.
728
729 .. versionchanged:: 2.0
730 When loading the env files, set the default encoding to UTF-8.
731
732 .. versionchanged:: 1.1.0
733 Returns ``False`` when python-dotenv is not installed, or when
734 the given path isn't a file.
735
736 .. versionadded:: 1.0
737 """
src/flask/cli.py 258

738 try:
739 import dotenv
740 except ImportError:
741 if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
742 click.secho(
743 " * Tip: There are .env files present. Install python-dotenv"
744 " to use them.",
745 fg="yellow",
746 err=True,
747 )
748
749 return False
750
751 data: dict[str, str | None] = {}
752
753 if load_defaults:
754 for default_name in (".flaskenv", ".env"):
755 if not (default_path := dotenv.find_dotenv(default_name, usecwd=True)):
756 continue
757
758 data |= dotenv.dotenv_values(default_path, encoding="utf-8")
759
760 if path is not None and os.path.isfile(path):
761 data |= dotenv.dotenv_values(path, encoding="utf-8")
762
763 for key, value in data.items():
764 if key in os.environ or value is None:
765 continue
766
767 os.environ[key] = value
768
769 return bool(data) # True if at least one env var was loaded.
770
771
772 def show_server_banner(debug: bool, app_import_path: str | None) -> None:
773 """Show extra startup messages the first time the server is run,
774 ignoring the reloader.
775 """
776 if is_running_from_reloader():
777 return
778
779 if app_import_path is not None:
780 click.echo(f" * Serving Flask app '{app_import_path}'")
781
782 if debug is not None:
783 click.echo(f" * Debug mode: {'on' if debug else 'off'}")
784
785
786 class CertParamType(click.ParamType):
787 """Click option type for the ``--cert`` option. Allows either an
788 existing file, the string ``'adhoc'``, or an import for a
789 :class:`~ssl.SSLContext` object.
790 """
791
792 name = "path"
793
794 def __init__(self) -> None:
795 self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
796
797 def convert(
798 self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None
799 ) -> t.Any:
800 try:
801 import ssl
802 except ImportError:
803 raise click.BadParameter(
804 'Using "--cert" requires Python to be compiled with SSL support.',
805 ctx,
806 param,
807 ) from None
808
src/flask/cli.py 259

809 try:
810 return self.path_type(value, param, ctx)
811 except click.BadParameter:
812 value = click.STRING(value, param, ctx).lower()
813
814 if value == "adhoc":
815 try:
816 import cryptography # noqa: F401
817 except ImportError:
818 raise click.BadParameter(
819 "Using ad-hoc certificates requires the cryptography library.",
820 ctx,
821 param,
822 ) from None
823
824 return value
825
826 obj = import_string(value, silent=True)
827
828 if isinstance(obj, ssl.SSLContext):
829 return obj
830
831 raise
832
833
834 def _validate_key(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any:
835 """The ``--key`` option must be specified when ``--cert`` is a file.
836 Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
837 """
838 cert = ctx.params.get("cert")
839 is_adhoc = cert == "adhoc"
840
841 try:
842 import ssl
843 except ImportError:
844 is_context = False
845 else:
846 is_context = isinstance(cert, ssl.SSLContext)
847
848 if value is not None:
849 if is_adhoc:
850 raise click.BadParameter(
851 'When "--cert" is "adhoc", "--key" is not used.', ctx, param
852 )
853
854 if is_context:
855 raise click.BadParameter(
856 'When "--cert" is an SSLContext object, "--key" is not used.',
857 ctx,
858 param,
859 )
860
861 if not cert:
862 raise click.BadParameter('"--cert" must also be specified.', ctx, param)
863
864 ctx.params["cert"] = cert, value
865
866 else:
867 if cert and not (is_adhoc or is_context):
868 raise click.BadParameter('Required when using "--cert".', ctx, param)
869
870 return value
871
872
873 class SeparatedPathType(click.Path):
874 """Click option type that accepts a list of values separated by the
875 OS's path separator (``:``, ``;`` on Windows). Each value is
876 validated as a :class:`click.Path` type.
877 """
878
879 def convert(
src/flask/cli.py 260

880 self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None
881 ) -> t.Any:
882 items = self.split_envvar_value(value)
883 # can't call no-arg super() inside list comprehension until Python 3.12
884 super_convert = super().convert
885 return [super_convert(item, param, ctx) for item in items]
886
887
888 @click.command("run", short_help="Run a development server.")
889 @click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.")
890 @click.option("--port", "-p", default=5000, help="The port to bind to.")
891 @click.option(
892 "--cert",
893 type=CertParamType(),
894 help="Specify a certificate file to use HTTPS.",
895 is_eager=True,
896 )
897 @click.option(
898 "--key",
899 type=click.Path(exists=True, dir_okay=False, resolve_path=True),
900 callback=_validate_key,
901 expose_value=False,
902 help="The key file to use when specifying a certificate.",
903 )
904 @click.option(
905 "--reload/--no-reload",
906 default=None,
907 help="Enable or disable the reloader. By default the reloader "
908 "is active if debug is enabled.",
909 )
910 @click.option(
911 "--debugger/--no-debugger",
912 default=None,
913 help="Enable or disable the debugger. By default the debugger "
914 "is active if debug is enabled.",
915 )
916 @click.option(
917 "--with-threads/--without-threads",
918 default=True,
919 help="Enable or disable multithreading.",
920 )
921 @click.option(
922 "--extra-files",
923 default=None,
924 type=SeparatedPathType(),
925 help=(
926 "Extra files that trigger a reload on change. Multiple paths"
927 f" are separated by {os.path.pathsep!r}."
928 ),
929 )
930 @click.option(
931 "--exclude-patterns",
932 default=None,
933 type=SeparatedPathType(),
934 help=(
935 "Files matching these fnmatch patterns will not trigger a reload"
936 " on change. Multiple patterns are separated by"
937 f" {os.path.pathsep!r}."
938 ),
939 )
940 @pass_script_info
941 def run_command(
942 info: ScriptInfo,
943 host: str,
944 port: int,
945 reload: bool,
946 debugger: bool,
947 with_threads: bool,
948 cert: ssl.SSLContext | tuple[str, str | None] | t.Literal["adhoc"] | None,
949 extra_files: list[str] | None,
950 exclude_patterns: list[str] | None,
src/flask/cli.py 261

951 ) -> None:


952 """Run a local development server.
953
954 This server is for development purposes only. It does not provide
955 the stability, security, or performance of production WSGI servers.
956
957 The reloader and debugger are enabled by default with the '--debug'
958 option.
959 """
960 try:
961 app: WSGIApplication = info.load_app() # pyright: ignore
962 except Exception as e:
963 if is_running_from_reloader():
964 # When reloading, print out the error immediately, but raise
965 # it later so the debugger or server can handle it.
966 traceback.print_exc()
967 err = e
968
969 def app(
970 environ: WSGIEnvironment, start_response: StartResponse
971 ) -> cabc.Iterable[bytes]:
972 raise err from None
973
974 else:
975 # When not reloading, raise the error immediately so the
976 # command fails.
977 raise e from None
978
979 debug = get_debug_flag()
980
981 if reload is None:
982 reload = debug
983
984 if debugger is None:
985 debugger = debug
986
987 show_server_banner(debug, info.app_import_path)
988
989 run_simple(
990 host,
991 port,
992 app,
993 use_reloader=reload,
994 use_debugger=debugger,
995 threaded=with_threads,
996 ssl_context=cert,
997 extra_files=extra_files,
998 exclude_patterns=exclude_patterns,
999 )
1000
1001
1002 run_command.params.insert(0, _debug_option)
1003
1004
1005 @click.command("shell", short_help="Run a shell in the app context.")
1006 @with_appcontext
1007 def shell_command() -> None:
1008 """Run an interactive Python shell in the context of a given
1009 Flask application. The application will populate the default
1010 namespace of this shell according to its configuration.
1011
1012 This is useful for executing small snippets of management code
1013 without having to manually configure the application.
1014 """
1015 import code
1016
1017 banner = (
1018 f"Python {sys.version} on {sys.platform}\n"
1019 f"App: {current_app.import_name}\n"
1020 f"Instance: {current_app.instance_path}"
1021 )
src/flask/cli.py 262

1022 ctx: dict[str, t.Any] = {}


1023
1024 # Support the regular Python interpreter startup script if someone
1025 # is using it.
1026 startup = os.environ.get("PYTHONSTARTUP")
1027 if startup and os.path.isfile(startup):
1028 with open(startup) as f:
1029 eval(compile(f.read(), startup, "exec"), ctx)
1030
1031 ctx.update(current_app.make_shell_context())
1032
1033 # Site, customize, or startup script can set a hook to call when
1034 # entering interactive mode. The default one sets up readline with
1035 # tab and history completion.
1036 interactive_hook = getattr(sys, "__interactivehook__", None)
1037
1038 if interactive_hook is not None:
1039 try:
1040 import readline
1041 from rlcompleter import Completer
1042 except ImportError:
1043 pass
1044 else:
1045 # rlcompleter uses __main__.__dict__ by default, which is
1046 # flask.__main__. Use the shell context instead.
1047 readline.set_completer(Completer(ctx).complete)
1048
1049 interactive_hook()
1050
1051 code.interact(banner=banner, local=ctx)
1052
1053
1054 @click.command("routes", short_help="Show the routes for the app.")
1055 @click.option(
1056 "--sort",
1057 "-s",
1058 type=click.Choice(("endpoint", "methods", "domain", "rule", "match")),
1059 default="endpoint",
1060 help=(
1061 "Method to sort routes by. 'match' is the order that Flask will match routes"
1062 " when dispatching a request."
1063 ),
1064 )
1065 @click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
1066 @with_appcontext
1067 def routes_command(sort: str, all_methods: bool) -> None:
1068 """Show all registered routes with endpoints and methods."""
1069 rules = list(current_app.url_map.iter_rules())
1070
1071 if not rules:
1072 click.echo("No routes were registered.")
1073 return
1074
1075 ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"}
1076 host_matching = current_app.url_map.host_matching
1077 has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules)
1078 rows = []
1079
1080 for rule in rules:
1081 row = [
1082 rule.endpoint,
1083 ", ".join(sorted((rule.methods or set()) - ignored_methods)),
1084 ]
1085
1086 if has_domain:
1087 row.append((rule.host if host_matching else rule.subdomain) or "")
1088
1089 row.append(rule.rule)
1090 rows.append(row)
1091
1092 headers = ["Endpoint", "Methods"]
263

1093 sorts = ["endpoint", "methods"]


1094
1095 if has_domain:
1096 headers.append("Host" if host_matching else "Subdomain")
1097 sorts.append("domain")
1098
1099 headers.append("Rule")
1100 sorts.append("rule")
1101
1102 try:
1103 rows.sort(key=itemgetter(sorts.index(sort)))
1104 except ValueError:
1105 pass
1106
1107 rows.insert(0, headers)
1108 widths = [max(len(row[i]) for row in rows) for i in range(len(headers))]
1109 rows.insert(1, ["-" * w for w in widths])
1110 template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths))
1111
1112 for row in rows:
1113 click.echo(template.format(*row))
1114
1115
1116 cli = FlaskGroup(
1117 name="flask",
1118 help="""\
1119 A general utility script for Flask applications.
1120
1121 An application to load must be given with the '--app' option,
1122 'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file
1123 in the current directory.
1124 """,
1125 )
1126
1127
1128 def main() -> None:
1129 cli.main()
1130
1131
1132 if __name__ == "__main__":
1133 main()

src/flask/app.py
1 from __future__ import annotations
2
3 import collections.abc as cabc
4 import os
5 import sys
6 import typing as t
7 import weakref
8 from datetime import timedelta
9 from inspect import iscoroutinefunction
10 from itertools import chain
11 from types import TracebackType
12 from urllib.parse import quote as _url_quote
13
14 import click
15 from werkzeug.datastructures import Headers
16 from werkzeug.datastructures import ImmutableDict
17 from werkzeug.exceptions import BadRequestKeyError
18 from werkzeug.exceptions import HTTPException
19 from werkzeug.exceptions import InternalServerError
20 from werkzeug.routing import BuildError
21 from werkzeug.routing import MapAdapter
22 from werkzeug.routing import RequestRedirect
23 from werkzeug.routing import RoutingException
24 from werkzeug.routing import Rule
25 from werkzeug.serving import is_running_from_reloader
26 from werkzeug.wrappers import Response as BaseResponse
src/flask/app.py 264

27 from werkzeug.wsgi import get_host


28
29 from . import cli
30 from . import typing as ft
31 from .ctx import AppContext
32 from .ctx import RequestContext
33 from .globals import _cv_app
34 from .globals import _cv_request
35 from .globals import current_app
36 from .globals import g
37 from .globals import request
38 from .globals import request_ctx
39 from .globals import session
40 from .helpers import get_debug_flag
41 from .helpers import get_flashed_messages
42 from .helpers import get_load_dotenv
43 from .helpers import send_from_directory
44 from .sansio.app import App
45 from .sansio.scaffold import _sentinel
46 from .sessions import SecureCookieSessionInterface
47 from .sessions import SessionInterface
48 from .signals import appcontext_tearing_down
49 from .signals import got_request_exception
50 from .signals import request_finished
51 from .signals import request_started
52 from .signals import request_tearing_down
53 from .templating import Environment
54 from .wrappers import Request
55 from .wrappers import Response
56
57 if t.TYPE_CHECKING: # pragma: no cover
58 from _typeshed.wsgi import StartResponse
59 from _typeshed.wsgi import WSGIEnvironment
60
61 from .testing import FlaskClient
62 from .testing import FlaskCliRunner
63 from .typing import HeadersValue
64
65 T_shell_context_processor = t.TypeVar(
66 "T_shell_context_processor", bound=ft.ShellContextProcessorCallable
67 )
68 T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
69 T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
70 T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
71 T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
72
73
74 def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
75 if value is None or isinstance(value, timedelta):
76 return value
77
78 return timedelta(seconds=value)
79
80
81 class Flask(App):
82 """The flask object implements a WSGI application and acts as the central
83 object. It is passed the name of the module or package of the
84 application. Once it is created it will act as a central registry for
85 the view functions, the URL rules, template configuration and much more.
86
87 The name of the package is used to resolve resources from inside the
88 package or the folder the module is contained in depending on if the
89 package parameter resolves to an actual python package (a folder with
90 an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).
91
92 For more information about resource loading, see :func:`open_resource`.
93
94 Usually you create a :class:`Flask` instance in your main module or
95 in the :file:`__init__.py` file of your package like this::
96
97 from flask import Flask
src/flask/app.py 265

98 app = Flask(__name__)
99
100 .. admonition:: About the First Parameter
101
102 The idea of the first parameter is to give Flask an idea of what
103 belongs to your application. This name is used to find resources
104 on the filesystem, can be used by extensions to improve debugging
105 information and a lot more.
106
107 So it's important what you provide there. If you are using a single
108 module, `__name__` is always the correct value. If you however are
109 using a package, it's usually recommended to hardcode the name of
110 your package there.
111
112 For example if your application is defined in :file:`yourapplication/app.py`
113 you should create it with one of the two versions below::
114
115 app = Flask('yourapplication')
116 app = Flask(__name__.split('.')[0])
117
118 Why is that? The application will work even with `__name__`, thanks
119 to how resources are looked up. However it will make debugging more
120 painful. Certain extensions can make assumptions based on the
121 import name of your application. For example the Flask-SQLAlchemy
122 extension will look for the code in your application that triggered
123 an SQL query in debug mode. If the import name is not properly set
124 up, that debugging information is lost. (For example it would only
125 pick up SQL queries in `yourapplication.app` and not
126 `yourapplication.views.frontend`)
127
128 .. versionadded:: 0.7
129 The `static_url_path`, `static_folder`, and `template_folder`
130 parameters were added.
131
132 .. versionadded:: 0.8
133 The `instance_path` and `instance_relative_config` parameters were
134 added.
135
136 .. versionadded:: 0.11
137 The `root_path` parameter was added.
138
139 .. versionadded:: 1.0
140 The ``host_matching`` and ``static_host`` parameters were added.
141
142 .. versionadded:: 1.0
143 The ``subdomain_matching`` parameter was added. Subdomain
144 matching needs to be enabled manually now. Setting
145 :data:`SERVER_NAME` does not implicitly enable it.
146
147 :param import_name: the name of the application package
148 :param static_url_path: can be used to specify a different path for the
149 static files on the web. Defaults to the name
150 of the `static_folder` folder.
151 :param static_folder: The folder with static files that is served at
152 ``static_url_path``. Relative to the application ``root_path``
153 or an absolute path. Defaults to ``'static'``.
154 :param static_host: the host to use when adding the static route.
155 Defaults to None. Required when using ``host_matching=True``
156 with a ``static_folder`` configured.
157 :param host_matching: set ``url_map.host_matching`` attribute.
158 Defaults to False.
159 :param subdomain_matching: consider the subdomain relative to
160 :data:`SERVER_NAME` when matching routes. Defaults to False.
161 :param template_folder: the folder that contains the templates that should
162 be used by the application. Defaults to
163 ``'templates'`` folder in the root path of the
164 application.
165 :param instance_path: An alternative instance path for the application.
166 By default the folder ``'instance'`` next to the
167 package or module is assumed to be the instance
168 path.
src/flask/app.py 266

169 :param instance_relative_config: if set to ``True`` relative filenames


170 for loading the config are assumed to
171 be relative to the instance path instead
172 of the application root.
173 :param root_path: The path to the root of the application files.
174 This should only be set manually when it can't be detected
175 automatically, such as for namespace packages.
176 """
177
178 default_config = ImmutableDict(
179 {
180 "DEBUG": None,
181 "TESTING": False,
182 "PROPAGATE_EXCEPTIONS": None,
183 "SECRET_KEY": None,
184 "SECRET_KEY_FALLBACKS": None,
185 "PERMANENT_SESSION_LIFETIME": timedelta(days=31),
186 "USE_X_SENDFILE": False,
187 "TRUSTED_HOSTS": None,
188 "SERVER_NAME": None,
189 "APPLICATION_ROOT": "/",
190 "SESSION_COOKIE_NAME": "session",
191 "SESSION_COOKIE_DOMAIN": None,
192 "SESSION_COOKIE_PATH": None,
193 "SESSION_COOKIE_HTTPONLY": True,
194 "SESSION_COOKIE_SECURE": False,
195 "SESSION_COOKIE_PARTITIONED": False,
196 "SESSION_COOKIE_SAMESITE": None,
197 "SESSION_REFRESH_EACH_REQUEST": True,
198 "MAX_CONTENT_LENGTH": None,
199 "MAX_FORM_MEMORY_SIZE": 500_000,
200 "MAX_FORM_PARTS": 1_000,
201 "SEND_FILE_MAX_AGE_DEFAULT": None,
202 "TRAP_BAD_REQUEST_ERRORS": None,
203 "TRAP_HTTP_EXCEPTIONS": False,
204 "EXPLAIN_TEMPLATE_LOADING": False,
205 "PREFERRED_URL_SCHEME": "http",
206 "TEMPLATES_AUTO_RELOAD": None,
207 "MAX_COOKIE_SIZE": 4093,
208 "PROVIDE_AUTOMATIC_OPTIONS": True,
209 }
210 )
211
212 #: The class that is used for request objects. See :class:`~flask.Request`
213 #: for more information.
214 request_class: type[Request] = Request
215
216 #: The class that is used for response objects. See
217 #: :class:`~flask.Response` for more information.
218 response_class: type[Response] = Response
219
220 #: the session interface to use. By default an instance of
221 #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
222 #:
223 #: .. versionadded:: 0.8
224 session_interface: SessionInterface = SecureCookieSessionInterface()
225
226 def __init__(
227 self,
228 import_name: str,
229 static_url_path: str | None = None,
230 static_folder: str | os.PathLike[str] | None = "static",
231 static_host: str | None = None,
232 host_matching: bool = False,
233 subdomain_matching: bool = False,
234 template_folder: str | os.PathLike[str] | None = "templates",
235 instance_path: str | None = None,
236 instance_relative_config: bool = False,
237 root_path: str | None = None,
238 ):
239 super().__init__(
src/flask/app.py 267

240 import_name=import_name,
241 static_url_path=static_url_path,
242 static_folder=static_folder,
243 static_host=static_host,
244 host_matching=host_matching,
245 subdomain_matching=subdomain_matching,
246 template_folder=template_folder,
247 instance_path=instance_path,
248 instance_relative_config=instance_relative_config,
249 root_path=root_path,
250 )
251
252 #: The Click command group for registering CLI commands for this
253 #: object. The commands are available from the ``flask`` command
254 #: once the application has been discovered and blueprints have
255 #: been registered.
256 self.cli = cli.AppGroup()
257
258 # Set the name of the Click group in case someone wants to add
259 # the app's commands to another CLI tool.
260 self.cli.name = self.name
261
262 # Add a static route using the provided static_url_path, static_host,
263 # and static_folder if there is a configured static_folder.
264 # Note we do this without checking if static_folder exists.
265 # For one, it might be created while the server is running (e.g. during
266 # development). Also, Google App Engine stores static files somewhere
267 if self.has_static_folder:
268 assert (
269 bool(static_host) == host_matching
270 ), "Invalid static_host/host_matching combination"
271 # Use a weakref to avoid creating a reference cycle between the app
272 # and the view function (see #3761).
273 self_ref = weakref.ref(self)
274 self.add_url_rule(
275 f"{self.static_url_path}/<path:filename>",
276 endpoint="static",
277 host=static_host,
278 view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950
279 )
280
281 def get_send_file_max_age(self, filename: str | None) -> int | None:
282 """Used by :func:`send_file` to determine the ``max_age`` cache
283 value for a given file path if it wasn't passed.
284
285 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
286 the configuration of :data:`~flask.current_app`. This defaults
287 to ``None``, which tells the browser to use conditional requests
288 instead of a timed cache, which is usually preferable.
289
290 Note this is a duplicate of the same method in the Flask
291 class.
292
293 .. versionchanged:: 2.0
294 The default configuration is ``None`` instead of 12 hours.
295
296 .. versionadded:: 0.9
297 """
298 value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
299
300 if value is None:
301 return None
302
303 if isinstance(value, timedelta):
304 return int(value.total_seconds())
305
306 return value # type: ignore[no-any-return]
307
308 def send_static_file(self, filename: str) -> Response:
309 """The view function used to serve files from
310 :attr:`static_folder`. A route is automatically registered for
src/flask/app.py 268

311 this view at :attr:`static_url_path` if :attr:`static_folder` is


312 set.
313
314 Note this is a duplicate of the same method in the Flask
315 class.
316
317 .. versionadded:: 0.5
318
319 """
320 if not self.has_static_folder:
321 raise RuntimeError("'static_folder' must be set to serve static_files.")
322
323 # send_file only knows to call get_send_file_max_age on the app,
324 # call it here so it works for blueprints too.
325 max_age = self.get_send_file_max_age(filename)
326 return send_from_directory(
327 t.cast(str, self.static_folder), filename, max_age=max_age
328 )
329
330 def open_resource(
331 self, resource: str, mode: str = "rb", encoding: str | None = None
332 ) -> t.IO[t.AnyStr]:
333 """Open a resource file relative to :attr:`root_path` for reading.
334
335 For example, if the file ``schema.sql`` is next to the file
336 ``app.py`` where the ``Flask`` app is defined, it can be opened
337 with:
338
339 .. code-block:: python
340
341 with app.open_resource("schema.sql") as f:
342 conn.executescript(f.read())
343
344 :param resource: Path to the resource relative to :attr:`root_path`.
345 :param mode: Open the file in this mode. Only reading is supported,
346 valid values are ``"r"`` (or ``"rt"``) and ``"rb"``.
347 :param encoding: Open the file with this encoding when opening in text
348 mode. This is ignored when opening in binary mode.
349
350 .. versionchanged:: 3.1
351 Added the ``encoding`` parameter.
352 """
353 if mode not in {"r", "rt", "rb"}:
354 raise ValueError("Resources can only be opened for reading.")
355
356 path = os.path.join(self.root_path, resource)
357
358 if mode == "rb":
359 return open(path, mode) # pyright: ignore
360
361 return open(path, mode, encoding=encoding)
362
363 def open_instance_resource(
364 self, resource: str, mode: str = "rb", encoding: str | None = "utf-8"
365 ) -> t.IO[t.AnyStr]:
366 """Open a resource file relative to the application's instance folder
367 :attr:`instance_path`. Unlike :meth:`open_resource`, files in the
368 instance folder can be opened for writing.
369
370 :param resource: Path to the resource relative to :attr:`instance_path`.
371 :param mode: Open the file in this mode.
372 :param encoding: Open the file with this encoding when opening in text
373 mode. This is ignored when opening in binary mode.
374
375 .. versionchanged:: 3.1
376 Added the ``encoding`` parameter.
377 """
378 path = os.path.join(self.instance_path, resource)
379
380 if "b" in mode:
381 return open(path, mode)
src/flask/app.py 269

382
383 return open(path, mode, encoding=encoding)
384
385 def create_jinja_environment(self) -> Environment:
386 """Create the Jinja environment based on :attr:`jinja_options`
387 and the various Jinja-related methods of the app. Changing
388 :attr:`jinja_options` after this will have no effect. Also adds
389 Flask-related globals and filters to the environment.
390
391 .. versionchanged:: 0.11
392 ``Environment.auto_reload`` set in accordance with
393 ``TEMPLATES_AUTO_RELOAD`` configuration option.
394
395 .. versionadded:: 0.5
396 """
397 options = dict(self.jinja_options)
398
399 if "autoescape" not in options:
400 options["autoescape"] = self.select_jinja_autoescape
401
402 if "auto_reload" not in options:
403 auto_reload = self.config["TEMPLATES_AUTO_RELOAD"]
404
405 if auto_reload is None:
406 auto_reload = self.debug
407
408 options["auto_reload"] = auto_reload
409
410 rv = self.jinja_environment(self, **options)
411 rv.globals.update(
412 url_for=self.url_for,
413 get_flashed_messages=get_flashed_messages,
414 config=self.config,
415 # request, session and g are normally added with the
416 # context processor for efficiency reasons but for imported
417 # templates we also want the proxies in there.
418 request=request,
419 session=session,
420 g=g,
421 )
422 rv.policies["json.dumps_function"] = self.json.dumps
423 return rv
424
425 def create_url_adapter(self, request: Request | None) -> MapAdapter | None:
426 """Creates a URL adapter for the given request. The URL adapter
427 is created at a point where the request context is not yet set
428 up so the request is passed explicitly.
429
430 .. versionchanged:: 3.1
431 If :data:`SERVER_NAME` is set, it does not restrict requests to
432 only that domain, for both ``subdomain_matching`` and
433 ``host_matching``.
434
435 .. versionchanged:: 1.0
436 :data:`SERVER_NAME` no longer implicitly enables subdomain
437 matching. Use :attr:`subdomain_matching` instead.
438
439 .. versionchanged:: 0.9
440 This can be called outside a request when the URL adapter is created
441 for an application context.
442
443 .. versionadded:: 0.6
444 """
445 if request is not None:
446 if (trusted_hosts := self.config["TRUSTED_HOSTS"]) is not None:
447 request.trusted_hosts = trusted_hosts
448
449 # Check trusted_hosts here until bind_to_environ does.
450 request.host = get_host(request.environ, request.trusted_hosts) # pyright: ignore
451 subdomain = None
452 server_name = self.config["SERVER_NAME"]
src/flask/app.py 270

453
454 if self.url_map.host_matching:
455 # Don't pass SERVER_NAME, otherwise it's used and the actual
456 # host is ignored, which breaks host matching.
457 server_name = None
458 elif not self.subdomain_matching:
459 # Werkzeug doesn't implement subdomain matching yet. Until then,
460 # disable it by forcing the current subdomain to the default, or
461 # the empty string.
462 subdomain = self.url_map.default_subdomain or ""
463
464 return self.url_map.bind_to_environ(
465 request.environ, server_name=server_name, subdomain=subdomain
466 )
467
468 # Need at least SERVER_NAME to match/build outside a request.
469 if self.config["SERVER_NAME"] is not None:
470 return self.url_map.bind(
471 self.config["SERVER_NAME"],
472 script_name=self.config["APPLICATION_ROOT"],
473 url_scheme=self.config["PREFERRED_URL_SCHEME"],
474 )
475
476 return None
477
478 def raise_routing_exception(self, request: Request) -> t.NoReturn:
479 """Intercept routing exceptions and possibly do something else.
480
481 In debug mode, intercept a routing redirect and replace it with
482 an error if the body will be discarded.
483
484 With modern Werkzeug this shouldn't occur, since it now uses a
485 308 status which tells the browser to resend the method and
486 body.
487
488 .. versionchanged:: 2.1
489 Don't intercept 307 and 308 redirects.
490
491 :meta private:
492 :internal:
493 """
494 if (
495 not self.debug
496 or not isinstance(request.routing_exception, RequestRedirect)
497 or request.routing_exception.code in {307, 308}
498 or request.method in {"GET", "HEAD", "OPTIONS"}
499 ):
500 raise request.routing_exception # type: ignore[misc]
501
502 from .debughelpers import FormDataRoutingRedirect
503
504 raise FormDataRoutingRedirect(request)
505
506 def update_template_context(self, context: dict[str, t.Any]) -> None:
507 """Update the template context with some commonly used variables.
508 This injects request, session, config and g into the template
509 context as well as everything template context processors want
510 to inject. Note that the as of Flask 0.6, the original values
511 in the context will not be overridden if a context processor
512 decides to return a value with the same key.
513
514 :param context: the context as a dictionary that is updated in place
515 to add extra variables.
516 """
517 names: t.Iterable[str | None] = (None,)
518
519 # A template may be rendered outside a request context.
520 if request:
521 names = chain(names, reversed(request.blueprints))
522
523 # The values passed to render_template take precedence. Keep a
src/flask/app.py 271

524 # copy to re-apply after all context functions.


525 orig_ctx = context.copy()
526
527 for name in names:
528 if name in self.template_context_processors:
529 for func in self.template_context_processors[name]:
530 context.update(self.ensure_sync(func)())
531
532 context.update(orig_ctx)
533
534 def make_shell_context(self) -> dict[str, t.Any]:
535 """Returns the shell context for an interactive shell for this
536 application. This runs all the registered shell context
537 processors.
538
539 .. versionadded:: 0.11
540 """
541 rv = {"app": self, "g": g}
542 for processor in self.shell_context_processors:
543 rv.update(processor())
544 return rv
545
546 def run(
547 self,
548 host: str | None = None,
549 port: int | None = None,
550 debug: bool | None = None,
551 load_dotenv: bool = True,
552 **options: t.Any,
553 ) -> None:
554 """Runs the application on a local development server.
555
556 Do not use ``run()`` in a production setting. It is not intended to
557 meet security and performance requirements for a production server.
558 Instead, see :doc:`/deploying/index` for WSGI server recommendations.
559
560 If the :attr:`debug` flag is set the server will automatically reload
561 for code changes and show a debugger in case an exception happened.
562
563 If you want to run the application in debug mode, but disable the
564 code execution on the interactive debugger, you can pass
565 ``use_evalex=False`` as parameter. This will keep the debugger's
566 traceback screen active, but disable code execution.
567
568 It is not recommended to use this function for development with
569 automatic reloading as this is badly supported. Instead you should
570 be using the :command:`flask` command line script's ``run`` support.
571
572 .. admonition:: Keep in Mind
573
574 Flask will suppress any server error with a generic error page
575 unless it is in debug mode. As such to enable just the
576 interactive debugger without the code reloading, you have to
577 invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.
578 Setting ``use_debugger`` to ``True`` without being in debug mode
579 won't catch any exceptions because there won't be any to
580 catch.
581
582 :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
583 have the server available externally as well. Defaults to
584 ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable
585 if present.
586 :param port: the port of the webserver. Defaults to ``5000`` or the
587 port defined in the ``SERVER_NAME`` config variable if present.
588 :param debug: if given, enable or disable debug mode. See
589 :attr:`debug`.
590 :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
591 files to set environment variables. Will also change the working
592 directory to the directory containing the first file found.
593 :param options: the options to be forwarded to the underlying Werkzeug
594 server. See :func:`werkzeug.serving.run_simple` for more
src/flask/app.py 272

595 information.
596
597 .. versionchanged:: 1.0
598 If installed, python-dotenv will be used to load environment
599 variables from :file:`.env` and :file:`.flaskenv` files.
600
601 The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`.
602
603 Threaded mode is enabled by default.
604
605 .. versionchanged:: 0.10
606 The default port is now picked from the ``SERVER_NAME``
607 variable.
608 """
609 # Ignore this call so that it doesn't start another server if
610 # the 'flask run' command is used.
611 if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
612 if not is_running_from_reloader():
613 click.secho(
614 " * Ignoring a call to 'app.run()' that would block"
615 " the current 'flask' CLI command.\n"
616 " Only call 'app.run()' in an 'if __name__ =="
617 ' "__main__"\' guard.',
618 fg="red",
619 )
620
621 return
622
623 if get_load_dotenv(load_dotenv):
624 cli.load_dotenv()
625
626 # if set, env var overrides existing value
627 if "FLASK_DEBUG" in os.environ:
628 self.debug = get_debug_flag()
629
630 # debug passed to method overrides all other sources
631 if debug is not None:
632 self.debug = bool(debug)
633
634 server_name = self.config.get("SERVER_NAME")
635 sn_host = sn_port = None
636
637 if server_name:
638 sn_host, _, sn_port = server_name.partition(":")
639
640 if not host:
641 if sn_host:
642 host = sn_host
643 else:
644 host = "127.0.0.1"
645
646 if port or port == 0:
647 port = int(port)
648 elif sn_port:
649 port = int(sn_port)
650 else:
651 port = 5000
652
653 options.setdefault("use_reloader", self.debug)
654 options.setdefault("use_debugger", self.debug)
655 options.setdefault("threaded", True)
656
657 cli.show_server_banner(self.debug, self.name)
658
659 from werkzeug.serving import run_simple
660
661 try:
662 run_simple(t.cast(str, host), port, self, **options)
663 finally:
664 # reset the first request information if the development server
665 # reset normally. This makes it possible to restart the server
src/flask/app.py 273

666 # without reloader and that stuff from an interactive shell.


667 self._got_first_request = False
668
669 def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient:
670 """Creates a test client for this application. For information
671 about unit testing head over to :doc:`/testing`.
672
673 Note that if you are testing for assertions or exceptions in your
674 application code, you must set ``app.testing = True`` in order for the
675 exceptions to propagate to the test client. Otherwise, the exception
676 will be handled by the application (not visible to the test client) and
677 the only indication of an AssertionError or other exception will be a
678 500 status code response to the test client. See the :attr:`testing`
679 attribute. For example::
680
681 app.testing = True
682 client = app.test_client()
683
684 The test client can be used in a ``with`` block to defer the closing down
685 of the context until the end of the ``with`` block. This is useful if
686 you want to access the context locals for testing::
687
688 with app.test_client() as c:
689 rv = c.get('/?vodka=42')
690 assert request.args['vodka'] == '42'
691
692 Additionally, you may pass optional keyword arguments that will then
693 be passed to the application's :attr:`test_client_class` constructor.
694 For example::
695
696 from flask.testing import FlaskClient
697
698 class CustomClient(FlaskClient):
699 def __init__(self, *args, **kwargs):
700 self._authentication = kwargs.pop("authentication")
701 super(CustomClient,self).__init__( *args, **kwargs)
702
703 app.test_client_class = CustomClient
704 client = app.test_client(authentication='Basic ....')
705
706 See :class:`~flask.testing.FlaskClient` for more information.
707
708 .. versionchanged:: 0.4
709 added support for ``with`` block usage for the client.
710
711 .. versionadded:: 0.7
712 The `use_cookies` parameter was added as well as the ability
713 to override the client to be used by setting the
714 :attr:`test_client_class` attribute.
715
716 .. versionchanged:: 0.11
717 Added `**kwargs` to support passing additional keyword arguments to
718 the constructor of :attr:`test_client_class`.
719 """
720 cls = self.test_client_class
721 if cls is None:
722 from .testing import FlaskClient as cls
723 return cls( # type: ignore
724 self, self.response_class, use_cookies=use_cookies, **kwargs
725 )
726
727 def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner:
728 """Create a CLI runner for testing CLI commands.
729 See :ref:`testing-cli`.
730
731 Returns an instance of :attr:`test_cli_runner_class`, by default
732 :class:`~flask.testing.FlaskCliRunner`. The Flask app object is
733 passed as the first argument.
734
735 .. versionadded:: 1.0
736 """
src/flask/app.py 274

737 cls = self.test_cli_runner_class


738
739 if cls is None:
740 from .testing import FlaskCliRunner as cls
741
742 return cls(self, **kwargs) # type: ignore
743
744 def handle_http_exception(
745 self, e: HTTPException
746 ) -> HTTPException | ft.ResponseReturnValue:
747 """Handles an HTTP exception. By default this will invoke the
748 registered error handlers and fall back to returning the
749 exception as response.
750
751 .. versionchanged:: 1.0.3
752 ``RoutingException``, used internally for actions such as
753 slash redirects during routing, is not passed to error
754 handlers.
755
756 .. versionchanged:: 1.0
757 Exceptions are looked up by code *and* by MRO, so
758 ``HTTPException`` subclasses can be handled with a catch-all
759 handler for the base ``HTTPException``.
760
761 .. versionadded:: 0.3
762 """
763 # Proxy exceptions don't have error codes. We want to always return
764 # those unchanged as errors
765 if e.code is None:
766 return e
767
768 # RoutingExceptions are used internally to trigger routing
769 # actions, such as slash redirects raising RequestRedirect. They
770 # are not raised or handled in user code.
771 if isinstance(e, RoutingException):
772 return e
773
774 handler = self._find_error_handler(e, request.blueprints)
775 if handler is None:
776 return e
777 return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
778
779 def handle_user_exception(
780 self, e: Exception
781 ) -> HTTPException | ft.ResponseReturnValue:
782 """This method is called whenever an exception occurs that
783 should be handled. A special case is :class:`~werkzeug
784 .exceptions.HTTPException` which is forwarded to the
785 :meth:`handle_http_exception` method. This function will either
786 return a response value or reraise the exception with the same
787 traceback.
788
789 .. versionchanged:: 1.0
790 Key errors raised from request data like ``form`` show the
791 bad key in debug mode rather than a generic bad request
792 message.
793
794 .. versionadded:: 0.7
795 """
796 if isinstance(e, BadRequestKeyError) and (
797 self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]
798 ):
799 e.show_exception = True
800
801 if isinstance(e, HTTPException) and not self.trap_http_exception(e):
802 return self.handle_http_exception(e)
803
804 handler = self._find_error_handler(e, request.blueprints)
805
806 if handler is None:
807 raise
src/flask/app.py 275

808
809 return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
810
811 def handle_exception(self, e: Exception) -> Response:
812 """Handle an exception that did not have an error handler
813 associated with it, or that was raised from an error handler.
814 This always causes a 500 ``InternalServerError``.
815
816 Always sends the :data:`got_request_exception` signal.
817
818 If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug
819 mode, the error will be re-raised so that the debugger can
820 display it. Otherwise, the original exception is logged, and
821 an :exc:`~werkzeug.exceptions.InternalServerError` is returned.
822
823 If an error handler is registered for ``InternalServerError`` or
824 ``500``, it will be used. For consistency, the handler will
825 always receive the ``InternalServerError``. The original
826 unhandled exception is available as ``e.original_exception``.
827
828 .. versionchanged:: 1.1.0
829 Always passes the ``InternalServerError`` instance to the
830 handler, setting ``original_exception`` to the unhandled
831 error.
832
833 .. versionchanged:: 1.1.0
834 ``after_request`` functions and other finalization is done
835 even for the default 500 response when there is no handler.
836
837 .. versionadded:: 0.3
838 """
839 exc_info = sys.exc_info()
840 got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e)
841 propagate = self.config["PROPAGATE_EXCEPTIONS"]
842
843 if propagate is None:
844 propagate = self.testing or self.debug
845
846 if propagate:
847 # Re-raise if called with an active exception, otherwise
848 # raise the passed in exception.
849 if exc_info[1] is e:
850 raise
851
852 raise e
853
854 self.log_exception(exc_info)
855 server_error: InternalServerError | ft.ResponseReturnValue
856 server_error = InternalServerError(original_exception=e)
857 handler = self._find_error_handler(server_error, request.blueprints)
858
859 if handler is not None:
860 server_error = self.ensure_sync(handler)(server_error)
861
862 return self.finalize_request(server_error, from_error_handler=True)
863
864 def log_exception(
865 self,
866 exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),
867 ) -> None:
868 """Logs an exception. This is called by :meth:`handle_exception`
869 if debugging is disabled and right before the handler is called.
870 The default implementation logs the exception as error on the
871 :attr:`logger`.
872
873 .. versionadded:: 0.8
874 """
875 self.logger.error(
876 f"Exception on {request.path} [{request.method}]", exc_info=exc_info
877 )
878
src/flask/app.py 276

879 def dispatch_request(self) -> ft.ResponseReturnValue:


880 """Does the request dispatching. Matches the URL and returns the
881 return value of the view or error handler. This does not have to
882 be a response object. In order to convert the return value to a
883 proper response object, call :func:`make_response`.
884
885 .. versionchanged:: 0.7
886 This no longer does the exception handling, this code was
887 moved to the new :meth:`full_dispatch_request`.
888 """
889 req = request_ctx.request
890 if req.routing_exception is not None:
891 self.raise_routing_exception(req)
892 rule: Rule = req.url_rule # type: ignore[assignment]
893 # if we provide automatic options for this URL and the
894 # request came with the OPTIONS method, reply automatically
895 if (
896 getattr(rule, "provide_automatic_options", False)
897 and req.method == "OPTIONS"
898 ):
899 return self.make_default_options_response()
900 # otherwise dispatch to the handler for that endpoint
901 view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
902 return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
903
904 def full_dispatch_request(self) -> Response:
905 """Dispatches the request and on top of that performs request
906 pre and postprocessing as well as HTTP exception catching and
907 error handling.
908
909 .. versionadded:: 0.7
910 """
911 self._got_first_request = True
912
913 try:
914 request_started.send(self, _async_wrapper=self.ensure_sync)
915 rv = self.preprocess_request()
916 if rv is None:
917 rv = self.dispatch_request()
918 except Exception as e:
919 rv = self.handle_user_exception(e)
920 return self.finalize_request(rv)
921
922 def finalize_request(
923 self,
924 rv: ft.ResponseReturnValue | HTTPException,
925 from_error_handler: bool = False,
926 ) -> Response:
927 """Given the return value from a view function this finalizes
928 the request by converting it into a response and invoking the
929 postprocessing functions. This is invoked for both normal
930 request dispatching as well as error handlers.
931
932 Because this means that it might be called as a result of a
933 failure a special safe mode is available which can be enabled
934 with the `from_error_handler` flag. If enabled, failures in
935 response processing will be logged and otherwise ignored.
936
937 :internal:
938 """
939 response = self.make_response(rv)
940 try:
941 response = self.process_response(response)
942 request_finished.send(
943 self, _async_wrapper=self.ensure_sync, response=response
944 )
945 except Exception:
946 if not from_error_handler:
947 raise
948 self.logger.exception(
949 "Request finalizing failed with an error while handling an error"
src/flask/app.py 277

950 )
951 return response
952
953 def make_default_options_response(self) -> Response:
954 """This method is called to create the default ``OPTIONS`` response.
955 This can be changed through subclassing to change the default
956 behavior of ``OPTIONS`` responses.
957
958 .. versionadded:: 0.7
959 """
960 adapter = request_ctx.url_adapter
961 methods = adapter.allowed_methods() # type: ignore[union-attr]
962 rv = self.response_class()
963 rv.allow.update(methods)
964 return rv
965
966 def ensure_sync(self, func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
967 """Ensure that the function is synchronous for WSGI workers.
968 Plain ``def`` functions are returned as-is. ``async def``
969 functions are wrapped to run and wait for the response.
970
971 Override this method to change how the app runs async views.
972
973 .. versionadded:: 2.0
974 """
975 if iscoroutinefunction(func):
976 return self.async_to_sync(func)
977
978 return func
979
980 def async_to_sync(
981 self, func: t.Callable[..., t.Coroutine[t.Any, t.Any, t.Any]]
982 ) -> t.Callable[..., t.Any]:
983 """Return a sync function that will run the coroutine function.
984
985 .. code-block:: python
986
987 result = app.async_to_sync(func)(*args, **kwargs)
988
989 Override this method to change how the app converts async code
990 to be synchronously callable.
991
992 .. versionadded:: 2.0
993 """
994 try:
995 from asgiref.sync import async_to_sync as asgiref_async_to_sync
996 except ImportError:
997 raise RuntimeError(
998 "Install Flask with the 'async' extra in order to use async views."
999 ) from None
1000
1001 return asgiref_async_to_sync(func)
1002
1003 def url_for(
1004 self,
1005 /,
1006 endpoint: str,
1007 *,
1008 _anchor: str | None = None,
1009 _method: str | None = None,
1010 _scheme: str | None = None,
1011 _external: bool | None = None,
1012 **values: t.Any,
1013 ) -> str:
1014 """Generate a URL to the given endpoint with the given values.
1015
1016 This is called by :func:`flask.url_for`, and can be called
1017 directly as well.
1018
1019 An *endpoint* is the name of a URL rule, usually added with
1020 :meth:`@app.route() <route>`, and usually the same name as the
src/flask/app.py 278

1021 view function. A route defined in a :class:`~flask.Blueprint`


1022 will prepend the blueprint's name separated by a ``.`` to the
1023 endpoint.
1024
1025 In some cases, such as email messages, you want URLs to include
1026 the scheme and domain, like ``https://example.com/hello``. When
1027 not in an active request, URLs will be external by default, but
1028 this requires setting :data:`SERVER_NAME` so Flask knows what
1029 domain to use. :data:`APPLICATION_ROOT` and
1030 :data:`PREFERRED_URL_SCHEME` should also be configured as
1031 needed. This config is only used when not in an active request.
1032
1033 Functions can be decorated with :meth:`url_defaults` to modify
1034 keyword arguments before the URL is built.
1035
1036 If building fails for some reason, such as an unknown endpoint
1037 or incorrect values, the app's :meth:`handle_url_build_error`
1038 method is called. If that returns a string, that is returned,
1039 otherwise a :exc:`~werkzeug.routing.BuildError` is raised.
1040
1041 :param endpoint: The endpoint name associated with the URL to
1042 generate. If this starts with a ``.``, the current blueprint
1043 name (if any) will be used.
1044 :param _anchor: If given, append this as ``#anchor`` to the URL.
1045 :param _method: If given, generate the URL associated with this
1046 method for the endpoint.
1047 :param _scheme: If given, the URL will have this scheme if it
1048 is external.
1049 :param _external: If given, prefer the URL to be internal
1050 (False) or require it to be external (True). External URLs
1051 include the scheme and domain. When not in an active
1052 request, URLs are external by default.
1053 :param values: Values to use for the variable parts of the URL
1054 rule. Unknown keys are appended as query string arguments,
1055 like ``?a=b&c=d``.
1056
1057 .. versionadded:: 2.2
1058 Moved from ``flask.url_for``, which calls this method.
1059 """
1060 req_ctx = _cv_request.get(None)
1061
1062 if req_ctx is not None:
1063 url_adapter = req_ctx.url_adapter
1064 blueprint_name = req_ctx.request.blueprint
1065
1066 # If the endpoint starts with "." and the request matches a
1067 # blueprint, the endpoint is relative to the blueprint.
1068 if endpoint[:1] == ".":
1069 if blueprint_name is not None:
1070 endpoint = f"{blueprint_name}{endpoint}"
1071 else:
1072 endpoint = endpoint[1:]
1073
1074 # When in a request, generate a URL without scheme and
1075 # domain by default, unless a scheme is given.
1076 if _external is None:
1077 _external = _scheme is not None
1078 else:
1079 app_ctx = _cv_app.get(None)
1080
1081 # If called by helpers.url_for, an app context is active,
1082 # use its url_adapter. Otherwise, app.url_for was called
1083 # directly, build an adapter.
1084 if app_ctx is not None:
1085 url_adapter = app_ctx.url_adapter
1086 else:
1087 url_adapter = self.create_url_adapter(None)
1088
1089 if url_adapter is None:
1090 raise RuntimeError(
1091 "Unable to build URLs outside an active request"
src/flask/app.py 279

1092 " without 'SERVER_NAME' configured. Also configure"


1093 " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as"
1094 " needed."
1095 )
1096
1097 # When outside a request, generate a URL with scheme and
1098 # domain by default.
1099 if _external is None:
1100 _external = True
1101
1102 # It is an error to set _scheme when _external=False, in order
1103 # to avoid accidental insecure URLs.
1104 if _scheme is not None and not _external:
1105 raise ValueError("When specifying '_scheme', '_external' must be True.")
1106
1107 self.inject_url_defaults(endpoint, values)
1108
1109 try:
1110 rv = url_adapter.build( # type: ignore[union-attr]
1111 endpoint,
1112 values,
1113 method=_method,
1114 url_scheme=_scheme,
1115 force_external=_external,
1116 )
1117 except BuildError as error:
1118 values.update(
1119 _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external
1120 )
1121 return self.handle_url_build_error(error, endpoint, values)
1122
1123 if _anchor is not None:
1124 _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@")
1125 rv = f"{rv}#{_anchor}"
1126
1127 return rv
1128
1129 def make_response(self, rv: ft.ResponseReturnValue) -> Response:
1130 """Convert the return value from a view function to an instance of
1131 :attr:`response_class`.
1132
1133 :param rv: the return value from the view function. The view function
1134 must return a response. Returning ``None``, or the view ending
1135 without returning, is not allowed. The following types are allowed
1136 for ``view_rv``:
1137
1138 ``str``
1139 A response object is created with the string encoded to UTF-8
1140 as the body.
1141
1142 ``bytes``
1143 A response object is created with the bytes as the body.
1144
1145 ``dict``
1146 A dictionary that will be jsonify'd before being returned.
1147
1148 ``list``
1149 A list that will be jsonify'd before being returned.
1150
1151 ``generator`` or ``iterator``
1152 A generator that returns ``str`` or ``bytes`` to be
1153 streamed as the response.
1154
1155 ``tuple``
1156 Either ``(body, status, headers)``, ``(body, status)``, or
1157 ``(body, headers)``, where ``body`` is any of the other types
1158 allowed here, ``status`` is a string or an integer, and
1159 ``headers`` is a dictionary or a list of ``(key, value)``
1160 tuples. If ``body`` is a :attr:`response_class` instance,
1161 ``status`` overwrites the exiting value and ``headers`` are
1162 extended.
src/flask/app.py 280

1163
1164 :attr:`response_class`
1165 The object is returned unchanged.
1166
1167 other :class:`~werkzeug.wrappers.Response` class
1168 The object is coerced to :attr:`response_class`.
1169
1170 :func:`callable`
1171 The function is called as a WSGI application. The result is
1172 used to create a response object.
1173
1174 .. versionchanged:: 2.2
1175 A generator will be converted to a streaming response.
1176 A list will be converted to a JSON response.
1177
1178 .. versionchanged:: 1.1
1179 A dict will be converted to a JSON response.
1180
1181 .. versionchanged:: 0.9
1182 Previously a tuple was interpreted as the arguments for the
1183 response object.
1184 """
1185
1186 status: int | None = None
1187 headers: HeadersValue | None = None
1188
1189 # unpack tuple returns
1190 if isinstance(rv, tuple):
1191 len_rv = len(rv)
1192
1193 # a 3-tuple is unpacked directly
1194 if len_rv == 3:
1195 rv, status, headers = rv # type: ignore[misc]
1196 # decide if a 2-tuple has status or headers
1197 elif len_rv == 2:
1198 if isinstance(rv[1], (Headers, dict, tuple, list)):
1199 rv, headers = rv # pyright: ignore
1200 else:
1201 rv, status = rv # type: ignore[assignment,misc]
1202 # other sized tuples are not allowed
1203 else:
1204 raise TypeError(
1205 "The view function did not return a valid response tuple."
1206 " The tuple must have the form (body, status, headers),"
1207 " (body, status), or (body, headers)."
1208 )
1209
1210 # the body must not be None
1211 if rv is None:
1212 raise TypeError(
1213 f"The view function for {request.endpoint!r} did not"
1214 " return a valid response. The function either returned"
1215 " None or ended without a return statement."
1216 )
1217
1218 # make sure the body is an instance of the response class
1219 if not isinstance(rv, self.response_class):
1220 if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, cabc.Iterator):
1221 # let the response class set the status and headers instead of
1222 # waiting to do it manually, so that the class can handle any
1223 # special logic
1224 rv = self.response_class(
1225 rv,
1226 status=status,
1227 headers=headers, # type: ignore[arg-type]
1228 )
1229 status = headers = None
1230 elif isinstance(rv, (dict, list)):
1231 rv = self.json.response(rv)
1232 elif isinstance(rv, BaseResponse) or callable(rv):
1233 # evaluate a WSGI callable, or coerce a different response
src/flask/app.py 281

1234 # class to the correct type


1235 try:
1236 rv = self.response_class.force_type(
1237 rv, # type: ignore[arg-type]
1238 request.environ,
1239 )
1240 except TypeError as e:
1241 raise TypeError(
1242 f"{e}\nThe view function did not return a valid"
1243 " response. The return type must be a string,"
1244 " dict, list, tuple with headers or status,"
1245 " Response instance, or WSGI callable, but it"
1246 f" was a {type(rv).__name__}."
1247 ).with_traceback(sys.exc_info()[2]) from None
1248 else:
1249 raise TypeError(
1250 "The view function did not return a valid"
1251 " response. The return type must be a string,"
1252 " dict, list, tuple with headers or status,"
1253 " Response instance, or WSGI callable, but it was a"
1254 f" {type(rv).__name__}."
1255 )
1256
1257 rv = t.cast(Response, rv)
1258 # prefer the status if it was provided
1259 if status is not None:
1260 if isinstance(status, (str, bytes, bytearray)):
1261 rv.status = status
1262 else:
1263 rv.status_code = status
1264
1265 # extend existing headers with provided headers
1266 if headers:
1267 rv.headers.update(headers)
1268
1269 return rv
1270
1271 def preprocess_request(self) -> ft.ResponseReturnValue | None:
1272 """Called before the request is dispatched. Calls
1273 :attr:`url_value_preprocessors` registered with the app and the
1274 current blueprint (if any). Then calls :attr:`before_request_funcs`
1275 registered with the app and the blueprint.
1276
1277 If any :meth:`before_request` handler returns a non-None value, the
1278 value is handled as if it was the return value from the view, and
1279 further request handling is stopped.
1280 """
1281 names = (None, *reversed(request.blueprints))
1282
1283 for name in names:
1284 if name in self.url_value_preprocessors:
1285 for url_func in self.url_value_preprocessors[name]:
1286 url_func(request.endpoint, request.view_args)
1287
1288 for name in names:
1289 if name in self.before_request_funcs:
1290 for before_func in self.before_request_funcs[name]:
1291 rv = self.ensure_sync(before_func)()
1292
1293 if rv is not None:
1294 return rv # type: ignore[no-any-return]
1295
1296 return None
1297
1298 def process_response(self, response: Response) -> Response:
1299 """Can be overridden in order to modify the response object
1300 before it's sent to the WSGI server. By default this will
1301 call all the :meth:`after_request` decorated functions.
1302
1303 .. versionchanged:: 0.5
1304 As of Flask 0.5 the functions registered for after request
src/flask/app.py 282

1305 execution are called in reverse order of registration.


1306
1307 :param response: a :attr:`response_class` object.
1308 :return: a new response object or the same, has to be an
1309 instance of :attr:`response_class`.
1310 """
1311 ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
1312
1313 for func in ctx._after_request_functions:
1314 response = self.ensure_sync(func)(response)
1315
1316 for name in chain(request.blueprints, (None,)):
1317 if name in self.after_request_funcs:
1318 for func in reversed(self.after_request_funcs[name]):
1319 response = self.ensure_sync(func)(response)
1320
1321 if not self.session_interface.is_null_session(ctx.session):
1322 self.session_interface.save_session(self, ctx.session, response)
1323
1324 return response
1325
1326 def do_teardown_request(
1327 self,
1328 exc: BaseException | None = _sentinel, # type: ignore[assignment]
1329 ) -> None:
1330 """Called after the request is dispatched and the response is
1331 returned, right before the request context is popped.
1332
1333 This calls all functions decorated with
1334 :meth:`teardown_request`, and :meth:`Blueprint.teardown_request`
1335 if a blueprint handled the request. Finally, the
1336 :data:`request_tearing_down` signal is sent.
1337
1338 This is called by
1339 :meth:`RequestContext.pop() <flask.ctx.RequestContext.pop>`,
1340 which may be delayed during testing to maintain access to
1341 resources.
1342
1343 :param exc: An unhandled exception raised while dispatching the
1344 request. Detected from the current exception information if
1345 not passed. Passed to each teardown function.
1346
1347 .. versionchanged:: 0.9
1348 Added the ``exc`` argument.
1349 """
1350 if exc is _sentinel:
1351 exc = sys.exc_info()[1]
1352
1353 for name in chain(request.blueprints, (None,)):
1354 if name in self.teardown_request_funcs:
1355 for func in reversed(self.teardown_request_funcs[name]):
1356 self.ensure_sync(func)(exc)
1357
1358 request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
1359
1360 def do_teardown_appcontext(
1361 self,
1362 exc: BaseException | None = _sentinel, # type: ignore[assignment]
1363 ) -> None:
1364 """Called right before the application context is popped.
1365
1366 When handling a request, the application context is popped
1367 after the request context. See :meth:`do_teardown_request`.
1368
1369 This calls all functions decorated with
1370 :meth:`teardown_appcontext`. Then the
1371 :data:`appcontext_tearing_down` signal is sent.
1372
1373 This is called by
1374 :meth:`AppContext.pop() <flask.ctx.AppContext.pop>`.
1375
src/flask/app.py 283

1376 .. versionadded:: 0.9


1377 """
1378 if exc is _sentinel:
1379 exc = sys.exc_info()[1]
1380
1381 for func in reversed(self.teardown_appcontext_funcs):
1382 self.ensure_sync(func)(exc)
1383
1384 appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
1385
1386 def app_context(self) -> AppContext:
1387 """Create an :class:`~flask.ctx.AppContext`. Use as a ``with``
1388 block to push the context, which will make :data:`current_app`
1389 point at this application.
1390
1391 An application context is automatically pushed by
1392 :meth:`RequestContext.push() <flask.ctx.RequestContext.push>`
1393 when handling a request, and when running a CLI command. Use
1394 this to manually create a context outside of these situations.
1395
1396 ::
1397
1398 with app.app_context():
1399 init_db()
1400
1401 See :doc:`/appcontext`.
1402
1403 .. versionadded:: 0.9
1404 """
1405 return AppContext(self)
1406
1407 def request_context(self, environ: WSGIEnvironment) -> RequestContext:
1408 """Create a :class:`~flask.ctx.RequestContext` representing a
1409 WSGI environment. Use a ``with`` block to push the context,
1410 which will make :data:`request` point at this request.
1411
1412 See :doc:`/reqcontext`.
1413
1414 Typically you should not call this from your own code. A request
1415 context is automatically pushed by the :meth:`wsgi_app` when
1416 handling a request. Use :meth:`test_request_context` to create
1417 an environment and context instead of this method.
1418
1419 :param environ: a WSGI environment
1420 """
1421 return RequestContext(self, environ)
1422
1423 def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext:
1424 """Create a :class:`~flask.ctx.RequestContext` for a WSGI
1425 environment created from the given values. This is mostly useful
1426 during testing, where you may want to run a function that uses
1427 request data without dispatching a full request.
1428
1429 See :doc:`/reqcontext`.
1430
1431 Use a ``with`` block to push the context, which will make
1432 :data:`request` point at the request for the created
1433 environment. ::
1434
1435 with app.test_request_context(...):
1436 generate_report()
1437
1438 When using the shell, it may be easier to push and pop the
1439 context manually to avoid indentation. ::
1440
1441 ctx = app.test_request_context(...)
1442 ctx.push()
1443 ...
1444 ctx.pop()
1445
1446 Takes the same arguments as Werkzeug's
src/flask/app.py 284

1447 :class:`~werkzeug.test.EnvironBuilder`, with some defaults from


1448 the application. See the linked Werkzeug docs for most of the
1449 available arguments. Flask-specific behavior is listed here.
1450
1451 :param path: URL path being requested.
1452 :param base_url: Base URL where the app is being served, which
1453 ``path`` is relative to. If not given, built from
1454 :data:`PREFERRED_URL_SCHEME`, ``subdomain``,
1455 :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
1456 :param subdomain: Subdomain name to append to
1457 :data:`SERVER_NAME`.
1458 :param url_scheme: Scheme to use instead of
1459 :data:`PREFERRED_URL_SCHEME`.
1460 :param data: The request body, either as a string or a dict of
1461 form keys and values.
1462 :param json: If given, this is serialized as JSON and passed as
1463 ``data``. Also defaults ``content_type`` to
1464 ``application/json``.
1465 :param args: other positional arguments passed to
1466 :class:`~werkzeug.test.EnvironBuilder`.
1467 :param kwargs: other keyword arguments passed to
1468 :class:`~werkzeug.test.EnvironBuilder`.
1469 """
1470 from .testing import EnvironBuilder
1471
1472 builder = EnvironBuilder(self, *args, **kwargs)
1473
1474 try:
1475 return self.request_context(builder.get_environ())
1476 finally:
1477 builder.close()
1478
1479 def wsgi_app(
1480 self, environ: WSGIEnvironment, start_response: StartResponse
1481 ) -> cabc.Iterable[bytes]:
1482 """The actual WSGI application. This is not implemented in
1483 :meth:`__call__` so that middlewares can be applied without
1484 losing a reference to the app object. Instead of doing this::
1485
1486 app = MyMiddleware(app)
1487
1488 It's a better idea to do this instead::
1489
1490 app.wsgi_app = MyMiddleware(app.wsgi_app)
1491
1492 Then you still have the original application object around and
1493 can continue to call methods on it.
1494
1495 .. versionchanged:: 0.7
1496 Teardown events for the request and app contexts are called
1497 even if an unhandled error occurs. Other events may not be
1498 called depending on when an error occurs during dispatch.
1499 See :ref:`callbacks-and-errors`.
1500
1501 :param environ: A WSGI environment.
1502 :param start_response: A callable accepting a status code,
1503 a list of headers, and an optional exception context to
1504 start the response.
1505 """
1506 ctx = self.request_context(environ)
1507 error: BaseException | None = None
1508 try:
1509 try:
1510 ctx.push()
1511 response = self.full_dispatch_request()
1512 except Exception as e:
1513 error = e
1514 response = self.handle_exception(e)
1515 except: # noqa: B001
1516 error = sys.exc_info()[1]
1517 raise
285

1518 return response(environ, start_response)


1519 finally:
1520 if "werkzeug.debug.preserve_context" in environ:
1521 environ["werkzeug.debug.preserve_context"](_cv_app.get())
1522 environ["werkzeug.debug.preserve_context"](_cv_request.get())
1523
1524 if error is not None and self.should_ignore_error(error):
1525 error = None
1526
1527 ctx.pop(error)
1528
1529 def __call__(
1530 self, environ: WSGIEnvironment, start_response: StartResponse
1531 ) -> cabc.Iterable[bytes]:
1532 """The WSGI server calls the Flask application object as the
1533 WSGI application. This calls :meth:`wsgi_app`, which can be
1534 wrapped to apply middleware.
1535 """
1536 return self.wsgi_app(environ, start_response)

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.

src/flask/sansio/blueprints.py
1 from __future__ import annotations
2
3 import os
4 import typing as t
5 from collections import defaultdict
6 from functools import update_wrapper
7
8 from .. import typing as ft
9 from .scaffold import _endpoint_from_view_func
10 from .scaffold import _sentinel
11 from .scaffold import Scaffold
12 from .scaffold import setupmethod
13
14 if t.TYPE_CHECKING: # pragma: no cover
15 from .app import App
16
17 DeferredSetupFunction = t.Callable[["BlueprintSetupState"], None]
18 T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any])
19 T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
20 T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
21 T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
22 T_template_context_processor = t.TypeVar(
23 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
24 )
25 T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
26 T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
27 T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
28 T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
29 T_url_value_preprocessor = t.TypeVar(
30 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
31 )
32
33
34 class BlueprintSetupState:
35 """Temporary holder object for registering a blueprint with the
36 application. An instance of this class is created by the
37 :meth:`~flask.Blueprint.make_setup_state` method and later passed
src/flask/sansio/blueprints.py 286

38 to all register callback functions.


39 """
40
41 def __init__(
42 self,
43 blueprint: Blueprint,
44 app: App,
45 options: t.Any,
46 first_registration: bool,
47 ) -> None:
48 #: a reference to the current application
49 self.app = app
50
51 #: a reference to the blueprint that created this setup state.
52 self.blueprint = blueprint
53
54 #: a dictionary with all options that were passed to the
55 #: :meth:`~flask.Flask.register_blueprint` method.
56 self.options = options
57
58 #: as blueprints can be registered multiple times with the
59 #: application and not everything wants to be registered
60 #: multiple times on it, this attribute can be used to figure
61 #: out if the blueprint was registered in the past already.
62 self.first_registration = first_registration
63
64 subdomain = self.options.get("subdomain")
65 if subdomain is None:
66 subdomain = self.blueprint.subdomain
67
68 #: The subdomain that the blueprint should be active for, ``None``
69 #: otherwise.
70 self.subdomain = subdomain
71
72 url_prefix = self.options.get("url_prefix")
73 if url_prefix is None:
74 url_prefix = self.blueprint.url_prefix
75 #: The prefix that should be used for all URLs defined on the
76 #: blueprint.
77 self.url_prefix = url_prefix
78
79 self.name = self.options.get("name", blueprint.name)
80 self.name_prefix = self.options.get("name_prefix", "")
81
82 #: A dictionary with URL defaults that is added to each and every
83 #: URL that was defined with the blueprint.
84 self.url_defaults = dict(self.blueprint.url_values_defaults)
85 self.url_defaults.update(self.options.get("url_defaults", ()))
86
87 def add_url_rule(
88 self,
89 rule: str,
90 endpoint: str | None = None,
91 view_func: ft.RouteCallable | None = None,
92 **options: t.Any,
93 ) -> None:
94 """A helper method to register a rule (and optionally a view function)
95 to the application. The endpoint is automatically prefixed with the
96 blueprint's name.
97 """
98 if self.url_prefix is not None:
99 if rule:
100 rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
101 else:
102 rule = self.url_prefix
103 options.setdefault("subdomain", self.subdomain)
104 if endpoint is None:
105 endpoint = _endpoint_from_view_func(view_func) # type: ignore
106 defaults = self.url_defaults
107 if "defaults" in options:
108 defaults = dict(defaults, **options.pop("defaults"))
src/flask/sansio/blueprints.py 287

109
110 self.app.add_url_rule(
111 rule,
112 f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
113 view_func,
114 defaults=defaults,
115 **options,
116 )
117
118
119 class Blueprint(Scaffold):
120 """Represents a blueprint, a collection of routes and other
121 app-related functions that can be registered on a real application
122 later.
123
124 A blueprint is an object that allows defining application functions
125 without requiring an application object ahead of time. It uses the
126 same decorators as :class:`~flask.Flask`, but defers the need for an
127 application by recording them for later registration.
128
129 Decorating a function with a blueprint creates a deferred function
130 that is called with :class:`~flask.blueprints.BlueprintSetupState`
131 when the blueprint is registered on an application.
132
133 See :doc:`/blueprints` for more information.
134
135 :param name: The name of the blueprint. Will be prepended to each
136 endpoint name.
137 :param import_name: The name of the blueprint package, usually
138 ``__name__``. This helps locate the ``root_path`` for the
139 blueprint.
140 :param static_folder: A folder with static files that should be
141 served by the blueprint's static route. The path is relative to
142 the blueprint's root path. Blueprint static files are disabled
143 by default.
144 :param static_url_path: The url to serve static files from.
145 Defaults to ``static_folder``. If the blueprint does not have
146 a ``url_prefix``, the app's static route will take precedence,
147 and the blueprint's static files won't be accessible.
148 :param template_folder: A folder with templates that should be added
149 to the app's template search path. The path is relative to the
150 blueprint's root path. Blueprint templates are disabled by
151 default. Blueprint templates have a lower precedence than those
152 in the app's templates folder.
153 :param url_prefix: A path to prepend to all of the blueprint's URLs,
154 to make them distinct from the rest of the app's routes.
155 :param subdomain: A subdomain that blueprint routes will match on by
156 default.
157 :param url_defaults: A dict of default values that blueprint routes
158 will receive by default.
159 :param root_path: By default, the blueprint will automatically set
160 this based on ``import_name``. In certain situations this
161 automatic detection can fail, so the path can be specified
162 manually instead.
163
164 .. versionchanged:: 1.1.0
165 Blueprints have a ``cli`` group to register nested CLI commands.
166 The ``cli_group`` parameter controls the name of the group under
167 the ``flask`` command.
168
169 .. versionadded:: 0.7
170 """
171
172 _got_registered_once = False
173
174 def __init__(
175 self,
176 name: str,
177 import_name: str,
178 static_folder: str | os.PathLike[str] | None = None,
179 static_url_path: str | None = None,
src/flask/sansio/blueprints.py 288

180 template_folder: str | os.PathLike[str] | None = None,


181 url_prefix: str | None = None,
182 subdomain: str | None = None,
183 url_defaults: dict[str, t.Any] | None = None,
184 root_path: str | None = None,
185 cli_group: str | None = _sentinel, # type: ignore[assignment]
186 ):
187 super().__init__(
188 import_name=import_name,
189 static_folder=static_folder,
190 static_url_path=static_url_path,
191 template_folder=template_folder,
192 root_path=root_path,
193 )
194
195 if not name:
196 raise ValueError("'name' may not be empty.")
197
198 if "." in name:
199 raise ValueError("'name' may not contain a dot '.' character.")
200
201 self.name = name
202 self.url_prefix = url_prefix
203 self.subdomain = subdomain
204 self.deferred_functions: list[DeferredSetupFunction] = []
205
206 if url_defaults is None:
207 url_defaults = {}
208
209 self.url_values_defaults = url_defaults
210 self.cli_group = cli_group
211 self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = []
212
213 def _check_setup_finished(self, f_name: str) -> None:
214 if self._got_registered_once:
215 raise AssertionError(
216 f"The setup method '{f_name}' can no longer be called on the blueprint"
217 f" '{self.name}'. It has already been registered at least once, any"
218 " changes will not be applied consistently.\n"
219 "Make sure all imports, decorators, functions, etc. needed to set up"
220 " the blueprint are done before registering it."
221 )
222
223 @setupmethod
224 def record(self, func: DeferredSetupFunction) -> None:
225 """Registers a function that is called when the blueprint is
226 registered on the application. This function is called with the
227 state as argument as returned by the :meth:`make_setup_state`
228 method.
229 """
230 self.deferred_functions.append(func)
231
232 @setupmethod
233 def record_once(self, func: DeferredSetupFunction) -> None:
234 """Works like :meth:`record` but wraps the function in another
235 function that will ensure the function is only called once. If the
236 blueprint is registered a second time on the application, the
237 function passed is not called.
238 """
239
240 def wrapper(state: BlueprintSetupState) -> None:
241 if state.first_registration:
242 func(state)
243
244 self.record(update_wrapper(wrapper, func))
245
246 def make_setup_state(
247 self, app: App, options: dict[str, t.Any], first_registration: bool = False
248 ) -> BlueprintSetupState:
249 """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
250 object that is later passed to the register callback functions.
src/flask/sansio/blueprints.py 289

251 Subclasses can override this to return a subclass of the setup state.
252 """
253 return BlueprintSetupState(self, app, options, first_registration)
254
255 @setupmethod
256 def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
257 """Register a :class:`~flask.Blueprint` on this blueprint. Keyword
258 arguments passed to this method will override the defaults set
259 on the blueprint.
260
261 .. versionchanged:: 2.0.1
262 The ``name`` option can be used to change the (pre-dotted)
263 name the blueprint is registered with. This allows the same
264 blueprint to be registered multiple times with unique names
265 for ``url_for``.
266
267 .. versionadded:: 2.0
268 """
269 if blueprint is self:
270 raise ValueError("Cannot register a blueprint on itself")
271 self._blueprints.append((blueprint, options))
272
273 def register(self, app: App, options: dict[str, t.Any]) -> None:
274 """Called by :meth:`Flask.register_blueprint` to register all
275 views and callbacks registered on the blueprint with the
276 application. Creates a :class:`.BlueprintSetupState` and calls
277 each :meth:`record` callback with it.
278
279 :param app: The application this blueprint is being registered
280 with.
281 :param options: Keyword arguments forwarded from
282 :meth:`~Flask.register_blueprint`.
283
284 .. versionchanged:: 2.3
285 Nested blueprints now correctly apply subdomains.
286
287 .. versionchanged:: 2.1
288 Registering the same blueprint with the same name multiple
289 times is an error.
290
291 .. versionchanged:: 2.0.1
292 Nested blueprints are registered with their dotted name.
293 This allows different blueprints with the same name to be
294 nested at different locations.
295
296 .. versionchanged:: 2.0.1
297 The ``name`` option can be used to change the (pre-dotted)
298 name the blueprint is registered with. This allows the same
299 blueprint to be registered multiple times with unique names
300 for ``url_for``.
301 """
302 name_prefix = options.get("name_prefix", "")
303 self_name = options.get("name", self.name)
304 name = f"{name_prefix}.{self_name}".lstrip(".")
305
306 if name in app.blueprints:
307 bp_desc = "this" if app.blueprints[name] is self else "a different"
308 existing_at = f" '{name}'" if self_name != name else ""
309
310 raise ValueError(
311 f"The name '{self_name}' is already registered for"
312 f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
313 f" provide a unique name."
314 )
315
316 first_bp_registration = not any(bp is self for bp in app.blueprints.values())
317 first_name_registration = name not in app.blueprints
318
319 app.blueprints[name] = self
320 self._got_registered_once = True
321 state = self.make_setup_state(app, options, first_bp_registration)
src/flask/sansio/blueprints.py 290

322
323 if self.has_static_folder:
324 state.add_url_rule(
325 f"{self.static_url_path}/<path:filename>",
326 view_func=self.send_static_file, # type: ignore[attr-defined]
327 endpoint="static",
328 )
329
330 # Merge blueprint data into parent.
331 if first_bp_registration or first_name_registration:
332 self._merge_blueprint_funcs(app, name)
333
334 for deferred in self.deferred_functions:
335 deferred(state)
336
337 cli_resolved_group = options.get("cli_group", self.cli_group)
338
339 if self.cli.commands:
340 if cli_resolved_group is None:
341 app.cli.commands.update(self.cli.commands)
342 elif cli_resolved_group is _sentinel:
343 self.cli.name = name
344 app.cli.add_command(self.cli)
345 else:
346 self.cli.name = cli_resolved_group
347 app.cli.add_command(self.cli)
348
349 for blueprint, bp_options in self._blueprints:
350 bp_options = bp_options.copy()
351 bp_url_prefix = bp_options.get("url_prefix")
352 bp_subdomain = bp_options.get("subdomain")
353
354 if bp_subdomain is None:
355 bp_subdomain = blueprint.subdomain
356
357 if state.subdomain is not None and bp_subdomain is not None:
358 bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
359 elif bp_subdomain is not None:
360 bp_options["subdomain"] = bp_subdomain
361 elif state.subdomain is not None:
362 bp_options["subdomain"] = state.subdomain
363
364 if bp_url_prefix is None:
365 bp_url_prefix = blueprint.url_prefix
366
367 if state.url_prefix is not None and bp_url_prefix is not None:
368 bp_options["url_prefix"] = (
369 state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
370 )
371 elif bp_url_prefix is not None:
372 bp_options["url_prefix"] = bp_url_prefix
373 elif state.url_prefix is not None:
374 bp_options["url_prefix"] = state.url_prefix
375
376 bp_options["name_prefix"] = name
377 blueprint.register(app, bp_options)
378
379 def _merge_blueprint_funcs(self, app: App, name: str) -> None:
380 def extend(
381 bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]],
382 parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]],
383 ) -> None:
384 for key, values in bp_dict.items():
385 key = name if key is None else f"{name}.{key}"
386 parent_dict[key].extend(values)
387
388 for key, value in self.error_handler_spec.items():
389 key = name if key is None else f"{name}.{key}"
390 value = defaultdict(
391 dict,
392 {
src/flask/sansio/blueprints.py 291

393 code: {exc_class: func for exc_class, func in code_values.items()}


394 for code, code_values in value.items()
395 },
396 )
397 app.error_handler_spec[key] = value
398
399 for endpoint, func in self.view_functions.items():
400 app.view_functions[endpoint] = func
401
402 extend(self.before_request_funcs, app.before_request_funcs)
403 extend(self.after_request_funcs, app.after_request_funcs)
404 extend(
405 self.teardown_request_funcs,
406 app.teardown_request_funcs,
407 )
408 extend(self.url_default_functions, app.url_default_functions)
409 extend(self.url_value_preprocessors, app.url_value_preprocessors)
410 extend(self.template_context_processors, app.template_context_processors)
411
412 @setupmethod
413 def add_url_rule(
414 self,
415 rule: str,
416 endpoint: str | None = None,
417 view_func: ft.RouteCallable | None = None,
418 provide_automatic_options: bool | None = None,
419 **options: t.Any,
420 ) -> None:
421 """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
422 full documentation.
423
424 The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
425 used with :func:`url_for`, is prefixed with the blueprint's name.
426 """
427 if endpoint and "." in endpoint:
428 raise ValueError("'endpoint' may not contain a dot '.' character.")
429
430 if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
431 raise ValueError("'view_func' name may not contain a dot '.' character.")
432
433 self.record(
434 lambda s: s.add_url_rule(
435 rule,
436 endpoint,
437 view_func,
438 provide_automatic_options=provide_automatic_options,
439 **options,
440 )
441 )
442
443 @setupmethod
444 def app_template_filter(
445 self, name: str | None = None
446 ) -> t.Callable[[T_template_filter], T_template_filter]:
447 """Register a template filter, available in any template rendered by the
448 application. Equivalent to :meth:`.Flask.template_filter`.
449
450 :param name: the optional name of the filter, otherwise the
451 function name will be used.
452 """
453
454 def decorator(f: T_template_filter) -> T_template_filter:
455 self.add_app_template_filter(f, name=name)
456 return f
457
458 return decorator
459
460 @setupmethod
461 def add_app_template_filter(
462 self, f: ft.TemplateFilterCallable, name: str | None = None
463 ) -> None:
src/flask/sansio/blueprints.py 292

464 """Register a template filter, available in any template rendered by the


465 application. Works like the :meth:`app_template_filter` decorator. Equivalent to
466 :meth:`.Flask.add_template_filter`.
467
468 :param name: the optional name of the filter, otherwise the
469 function name will be used.
470 """
471
472 def register_template(state: BlueprintSetupState) -> None:
473 state.app.jinja_env.filters[name or f.__name__] = f
474
475 self.record_once(register_template)
476
477 @setupmethod
478 def app_template_test(
479 self, name: str | None = None
480 ) -> t.Callable[[T_template_test], T_template_test]:
481 """Register a template test, available in any template rendered by the
482 application. Equivalent to :meth:`.Flask.template_test`.
483
484 .. versionadded:: 0.10
485
486 :param name: the optional name of the test, otherwise the
487 function name will be used.
488 """
489
490 def decorator(f: T_template_test) -> T_template_test:
491 self.add_app_template_test(f, name=name)
492 return f
493
494 return decorator
495
496 @setupmethod
497 def add_app_template_test(
498 self, f: ft.TemplateTestCallable, name: str | None = None
499 ) -> None:
500 """Register a template test, available in any template rendered by the
501 application. Works like the :meth:`app_template_test` decorator. Equivalent to
502 :meth:`.Flask.add_template_test`.
503
504 .. versionadded:: 0.10
505
506 :param name: the optional name of the test, otherwise the
507 function name will be used.
508 """
509
510 def register_template(state: BlueprintSetupState) -> None:
511 state.app.jinja_env.tests[name or f.__name__] = f
512
513 self.record_once(register_template)
514
515 @setupmethod
516 def app_template_global(
517 self, name: str | None = None
518 ) -> t.Callable[[T_template_global], T_template_global]:
519 """Register a template global, available in any template rendered by the
520 application. Equivalent to :meth:`.Flask.template_global`.
521
522 .. versionadded:: 0.10
523
524 :param name: the optional name of the global, otherwise the
525 function name will be used.
526 """
527
528 def decorator(f: T_template_global) -> T_template_global:
529 self.add_app_template_global(f, name=name)
530 return f
531
532 return decorator
533
534 @setupmethod
src/flask/sansio/blueprints.py 293

535 def add_app_template_global(


536 self, f: ft.TemplateGlobalCallable, name: str | None = None
537 ) -> None:
538 """Register a template global, available in any template rendered by the
539 application. Works like the :meth:`app_template_global` decorator. Equivalent to
540 :meth:`.Flask.add_template_global`.
541
542 .. versionadded:: 0.10
543
544 :param name: the optional name of the global, otherwise the
545 function name will be used.
546 """
547
548 def register_template(state: BlueprintSetupState) -> None:
549 state.app.jinja_env.globals[name or f.__name__] = f
550
551 self.record_once(register_template)
552
553 @setupmethod
554 def before_app_request(self, f: T_before_request) -> T_before_request:
555 """Like :meth:`before_request`, but before every request, not only those handled
556 by the blueprint. Equivalent to :meth:`.Flask.before_request`.
557 """
558 self.record_once(
559 lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
560 )
561 return f
562
563 @setupmethod
564 def after_app_request(self, f: T_after_request) -> T_after_request:
565 """Like :meth:`after_request`, but after every request, not only those handled
566 by the blueprint. Equivalent to :meth:`.Flask.after_request`.
567 """
568 self.record_once(
569 lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
570 )
571 return f
572
573 @setupmethod
574 def teardown_app_request(self, f: T_teardown) -> T_teardown:
575 """Like :meth:`teardown_request`, but after every request, not only those
576 handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
577 """
578 self.record_once(
579 lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
580 )
581 return f
582
583 @setupmethod
584 def app_context_processor(
585 self, f: T_template_context_processor
586 ) -> T_template_context_processor:
587 """Like :meth:`context_processor`, but for templates rendered by every view, not
588 only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
589 """
590 self.record_once(
591 lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
592 )
593 return f
594
595 @setupmethod
596 def app_errorhandler(
597 self, code: type[Exception] | int
598 ) -> t.Callable[[T_error_handler], T_error_handler]:
599 """Like :meth:`errorhandler`, but for every request, not only those handled by
600 the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
601 """
602
603 def decorator(f: T_error_handler) -> T_error_handler:
604 def from_blueprint(state: BlueprintSetupState) -> None:
605 state.app.errorhandler(code)(f)
294

606
607 self.record_once(from_blueprint)
608 return f
609
610 return decorator
611
612 @setupmethod
613 def app_url_value_preprocessor(
614 self, f: T_url_value_preprocessor
615 ) -> T_url_value_preprocessor:
616 """Like :meth:`url_value_preprocessor`, but for every request, not only those
617 handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
618 """
619 self.record_once(
620 lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
621 )
622 return f
623
624 @setupmethod
625 def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
626 """Like :meth:`url_defaults`, but for every request, not only those handled by
627 the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
628 """
629 self.record_once(
630 lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
631 )
632 return f

src/flask/sansio/scaffold.py
1 from __future__ import annotations
2
3 import importlib.util
4 import os
5 import pathlib
6 import sys
7 import typing as t
8 from collections import defaultdict
9 from functools import update_wrapper
10
11 from jinja2 import BaseLoader
12 from jinja2 import FileSystemLoader
13 from werkzeug.exceptions import default_exceptions
14 from werkzeug.exceptions import HTTPException
15 from werkzeug.utils import cached_property
16
17 from .. import typing as ft
18 from ..helpers import get_root_path
19 from ..templating import _default_template_ctx_processor
20
21 if t.TYPE_CHECKING: # pragma: no cover
22 from click import Group
23
24 # a singleton sentinel value for parameter defaults
25 _sentinel = object()
26
27 F = t.TypeVar("F", bound=t.Callable[..., t.Any])
28 T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any])
29 T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
30 T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
31 T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
32 T_template_context_processor = t.TypeVar(
33 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
34 )
35 T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
36 T_url_value_preprocessor = t.TypeVar(
37 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
38 )
39 T_route = t.TypeVar("T_route", bound=ft.RouteCallable)
40
src/flask/sansio/scaffold.py 295

41
42 def setupmethod(f: F) -> F:
43 f_name = f.__name__
44
45 def wrapper_func(self: Scaffold, *args: t.Any, **kwargs: t.Any) -> t.Any:
46 self._check_setup_finished(f_name)
47 return f(self, *args, **kwargs)
48
49 return t.cast(F, update_wrapper(wrapper_func, f))
50
51
52 class Scaffold:
53 """Common behavior shared between :class:`~flask.Flask` and
54 :class:`~flask.blueprints.Blueprint`.
55
56 :param import_name: The import name of the module where this object
57 is defined. Usually :attr:`__name__` should be used.
58 :param static_folder: Path to a folder of static files to serve.
59 If this is set, a static route will be added.
60 :param static_url_path: URL prefix for the static route.
61 :param template_folder: Path to a folder containing template files.
62 for rendering. If this is set, a Jinja loader will be added.
63 :param root_path: The path that static, template, and resource files
64 are relative to. Typically not set, it is discovered based on
65 the ``import_name``.
66
67 .. versionadded:: 2.0
68 """
69
70 cli: Group
71 name: str
72 _static_folder: str | None = None
73 _static_url_path: str | None = None
74
75 def __init__(
76 self,
77 import_name: str,
78 static_folder: str | os.PathLike[str] | None = None,
79 static_url_path: str | None = None,
80 template_folder: str | os.PathLike[str] | None = None,
81 root_path: str | None = None,
82 ):
83 #: The name of the package or module that this object belongs
84 #: to. Do not change this once it is set by the constructor.
85 self.import_name = import_name
86
87 self.static_folder = static_folder # type: ignore
88 self.static_url_path = static_url_path
89
90 #: The path to the templates folder, relative to
91 #: :attr:`root_path`, to add to the template loader. ``None`` if
92 #: templates should not be added.
93 self.template_folder = template_folder
94
95 if root_path is None:
96 root_path = get_root_path(self.import_name)
97
98 #: Absolute path to the package on the filesystem. Used to look
99 #: up resources contained in the package.
100 self.root_path = root_path
101
102 #: A dictionary mapping endpoint names to view functions.
103 #:
104 #: To register a view function, use the :meth:`route` decorator.
105 #:
106 #: This data structure is internal. It should not be modified
107 #: directly and its format may change at any time.
108 self.view_functions: dict[str, ft.RouteCallable] = {}
109
110 #: A data structure of registered error handlers, in the format
111 #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
src/flask/sansio/scaffold.py 296

112 #: the name of a blueprint the handlers are active for, or


113 #: ``None`` for all requests. The ``code`` key is the HTTP
114 #: status code for ``HTTPException``, or ``None`` for
115 #: other exceptions. The innermost dictionary maps exception
116 #: classes to handler functions.
117 #:
118 #: To register an error handler, use the :meth:`errorhandler`
119 #: decorator.
120 #:
121 #: This data structure is internal. It should not be modified
122 #: directly and its format may change at any time.
123 self.error_handler_spec: dict[
124 ft.AppOrBlueprintKey,
125 dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]],
126 ] = defaultdict(lambda: defaultdict(dict))
127
128 #: A data structure of functions to call at the beginning of
129 #: each request, in the format ``{scope: [functions]}``. The
130 #: ``scope`` key is the name of a blueprint the functions are
131 #: active for, or ``None`` for all requests.
132 #:
133 #: To register a function, use the :meth:`before_request`
134 #: decorator.
135 #:
136 #: This data structure is internal. It should not be modified
137 #: directly and its format may change at any time.
138 self.before_request_funcs: dict[
139 ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable]
140 ] = defaultdict(list)
141
142 #: A data structure of functions to call at the end of each
143 #: request, in the format ``{scope: [functions]}``. The
144 #: ``scope`` key is the name of a blueprint the functions are
145 #: active for, or ``None`` for all requests.
146 #:
147 #: To register a function, use the :meth:`after_request`
148 #: decorator.
149 #:
150 #: This data structure is internal. It should not be modified
151 #: directly and its format may change at any time.
152 self.after_request_funcs: dict[
153 ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]]
154 ] = defaultdict(list)
155
156 #: A data structure of functions to call at the end of each
157 #: request even if an exception is raised, in the format
158 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
159 #: blueprint the functions are active for, or ``None`` for all
160 #: requests.
161 #:
162 #: To register a function, use the :meth:`teardown_request`
163 #: decorator.
164 #:
165 #: This data structure is internal. It should not be modified
166 #: directly and its format may change at any time.
167 self.teardown_request_funcs: dict[
168 ft.AppOrBlueprintKey, list[ft.TeardownCallable]
169 ] = defaultdict(list)
170
171 #: A data structure of functions to call to pass extra context
172 #: values when rendering templates, in the format
173 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
174 #: blueprint the functions are active for, or ``None`` for all
175 #: requests.
176 #:
177 #: To register a function, use the :meth:`context_processor`
178 #: decorator.
179 #:
180 #: This data structure is internal. It should not be modified
181 #: directly and its format may change at any time.
182 self.template_context_processors: dict[
src/flask/sansio/scaffold.py 297

183 ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable]


184 ] = defaultdict(list, {None: [_default_template_ctx_processor]})
185
186 #: A data structure of functions to call to modify the keyword
187 #: arguments passed to the view function, in the format
188 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
189 #: blueprint the functions are active for, or ``None`` for all
190 #: requests.
191 #:
192 #: To register a function, use the
193 #: :meth:`url_value_preprocessor` decorator.
194 #:
195 #: This data structure is internal. It should not be modified
196 #: directly and its format may change at any time.
197 self.url_value_preprocessors: dict[
198 ft.AppOrBlueprintKey,
199 list[ft.URLValuePreprocessorCallable],
200 ] = defaultdict(list)
201
202 #: A data structure of functions to call to modify the keyword
203 #: arguments when generating URLs, in the format
204 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
205 #: blueprint the functions are active for, or ``None`` for all
206 #: requests.
207 #:
208 #: To register a function, use the :meth:`url_defaults`
209 #: decorator.
210 #:
211 #: This data structure is internal. It should not be modified
212 #: directly and its format may change at any time.
213 self.url_default_functions: dict[
214 ft.AppOrBlueprintKey, list[ft.URLDefaultCallable]
215 ] = defaultdict(list)
216
217 def __repr__(self) -> str:
218 return f"<{type(self).__name__} {self.name!r}>"
219
220 def _check_setup_finished(self, f_name: str) -> None:
221 raise NotImplementedError
222
223 @property
224 def static_folder(self) -> str | None:
225 """The absolute path to the configured static folder. ``None``
226 if no static folder is set.
227 """
228 if self._static_folder is not None:
229 return os.path.join(self.root_path, self._static_folder)
230 else:
231 return None
232
233 @static_folder.setter
234 def static_folder(self, value: str | os.PathLike[str] | None) -> None:
235 if value is not None:
236 value = os.fspath(value).rstrip(r"\/")
237
238 self._static_folder = value
239
240 @property
241 def has_static_folder(self) -> bool:
242 """``True`` if :attr:`static_folder` is set.
243
244 .. versionadded:: 0.5
245 """
246 return self.static_folder is not None
247
248 @property
249 def static_url_path(self) -> str | None:
250 """The URL prefix that the static route will be accessible from.
251
252 If it was not configured during init, it is derived from
253 :attr:`static_folder`.
src/flask/sansio/scaffold.py 298

254 """
255 if self._static_url_path is not None:
256 return self._static_url_path
257
258 if self.static_folder is not None:
259 basename = os.path.basename(self.static_folder)
260 return f"/{basename}".rstrip("/")
261
262 return None
263
264 @static_url_path.setter
265 def static_url_path(self, value: str | None) -> None:
266 if value is not None:
267 value = value.rstrip("/")
268
269 self._static_url_path = value
270
271 @cached_property
272 def jinja_loader(self) -> BaseLoader | None:
273 """The Jinja loader for this object's templates. By default this
274 is a class :class:`jinja2.loaders.FileSystemLoader` to
275 :attr:`template_folder` if it is set.
276
277 .. versionadded:: 0.5
278 """
279 if self.template_folder is not None:
280 return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
281 else:
282 return None
283
284 def _method_route(
285 self,
286 method: str,
287 rule: str,
288 options: dict[str, t.Any],
289 ) -> t.Callable[[T_route], T_route]:
290 if "methods" in options:
291 raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
292
293 return self.route(rule, methods=[method], **options)
294
295 @setupmethod
296 def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
297 """Shortcut for :meth:`route` with ``methods=["GET"]``.
298
299 .. versionadded:: 2.0
300 """
301 return self._method_route("GET", rule, options)
302
303 @setupmethod
304 def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
305 """Shortcut for :meth:`route` with ``methods=["POST"]``.
306
307 .. versionadded:: 2.0
308 """
309 return self._method_route("POST", rule, options)
310
311 @setupmethod
312 def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
313 """Shortcut for :meth:`route` with ``methods=["PUT"]``.
314
315 .. versionadded:: 2.0
316 """
317 return self._method_route("PUT", rule, options)
318
319 @setupmethod
320 def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
321 """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
322
323 .. versionadded:: 2.0
324 """
src/flask/sansio/scaffold.py 299

325 return self._method_route("DELETE", rule, options)


326
327 @setupmethod
328 def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
329 """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
330
331 .. versionadded:: 2.0
332 """
333 return self._method_route("PATCH", rule, options)
334
335 @setupmethod
336 def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
337 """Decorate a view function to register it with the given URL
338 rule and options. Calls :meth:`add_url_rule`, which has more
339 details about the implementation.
340
341 .. code-block:: python
342
343 @app.route("/")
344 def index():
345 return "Hello, World!"
346
347 See :ref:`url-route-registrations`.
348
349 The endpoint name for the route defaults to the name of the view
350 function if the ``endpoint`` parameter isn't passed.
351
352 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
353 ``OPTIONS`` are added automatically.
354
355 :param rule: The URL rule string.
356 :param options: Extra options passed to the
357 :class:`~werkzeug.routing.Rule` object.
358 """
359
360 def decorator(f: T_route) -> T_route:
361 endpoint = options.pop("endpoint", None)
362 self.add_url_rule(rule, endpoint, f, **options)
363 return f
364
365 return decorator
366
367 @setupmethod
368 def add_url_rule(
369 self,
370 rule: str,
371 endpoint: str | None = None,
372 view_func: ft.RouteCallable | None = None,
373 provide_automatic_options: bool | None = None,
374 **options: t.Any,
375 ) -> None:
376 """Register a rule for routing incoming requests and building
377 URLs. The :meth:`route` decorator is a shortcut to call this
378 with the ``view_func`` argument. These are equivalent:
379
380 .. code-block:: python
381
382 @app.route("/")
383 def index():
384 ...
385
386 .. code-block:: python
387
388 def index():
389 ...
390
391 app.add_url_rule("/", view_func=index)
392
393 See :ref:`url-route-registrations`.
394
395 The endpoint name for the route defaults to the name of the view
src/flask/sansio/scaffold.py 300

396 function if the ``endpoint`` parameter isn't passed. An error


397 will be raised if a function has already been registered for the
398 endpoint.
399
400 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
401 always added automatically, and ``OPTIONS`` is added
402 automatically by default.
403
404 ``view_func`` does not necessarily need to be passed, but if the
405 rule should participate in routing an endpoint name must be
406 associated with a view function at some point with the
407 :meth:`endpoint` decorator.
408
409 .. code-block:: python
410
411 app.add_url_rule("/", endpoint="index")
412
413 @app.endpoint("index")
414 def index():
415 ...
416
417 If ``view_func`` has a ``required_methods`` attribute, those
418 methods are added to the passed and automatic methods. If it
419 has a ``provide_automatic_methods`` attribute, it is used as the
420 default if the parameter is not passed.
421
422 :param rule: The URL rule string.
423 :param endpoint: The endpoint name to associate with the rule
424 and view function. Used when routing and building URLs.
425 Defaults to ``view_func.__name__``.
426 :param view_func: The view function to associate with the
427 endpoint name.
428 :param provide_automatic_options: Add the ``OPTIONS`` method and
429 respond to ``OPTIONS`` requests automatically.
430 :param options: Extra options passed to the
431 :class:`~werkzeug.routing.Rule` object.
432 """
433 raise NotImplementedError
434
435 @setupmethod
436 def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
437 """Decorate a view function to register it for the given
438 endpoint. Used if a rule is added without a ``view_func`` with
439 :meth:`add_url_rule`.
440
441 .. code-block:: python
442
443 app.add_url_rule("/ex", endpoint="example")
444
445 @app.endpoint("example")
446 def example():
447 ...
448
449 :param endpoint: The endpoint name to associate with the view
450 function.
451 """
452
453 def decorator(f: F) -> F:
454 self.view_functions[endpoint] = f
455 return f
456
457 return decorator
458
459 @setupmethod
460 def before_request(self, f: T_before_request) -> T_before_request:
461 """Register a function to run before each request.
462
463 For example, this can be used to open a database connection, or
464 to load the logged in user from the session.
465
466 .. code-block:: python
src/flask/sansio/scaffold.py 301

467
468 @app.before_request
469 def load_user():
470 if "user_id" in session:
471 g.user = db.session.get(session["user_id"])
472
473 The function will be called without any arguments. If it returns
474 a non-``None`` value, the value is handled as if it was the
475 return value from the view, and further request handling is
476 stopped.
477
478 This is available on both app and blueprint objects. When used on an app, this
479 executes before every request. When used on a blueprint, this executes before
480 every request that the blueprint handles. To register with a blueprint and
481 execute before every request, use :meth:`.Blueprint.before_app_request`.
482 """
483 self.before_request_funcs.setdefault(None, []).append(f)
484 return f
485
486 @setupmethod
487 def after_request(self, f: T_after_request) -> T_after_request:
488 """Register a function to run after each request to this object.
489
490 The function is called with the response object, and must return
491 a response object. This allows the functions to modify or
492 replace the response before it is sent.
493
494 If a function raises an exception, any remaining
495 ``after_request`` functions will not be called. Therefore, this
496 should not be used for actions that must execute, such as to
497 close resources. Use :meth:`teardown_request` for that.
498
499 This is available on both app and blueprint objects. When used on an app, this
500 executes after every request. When used on a blueprint, this executes after
501 every request that the blueprint handles. To register with a blueprint and
502 execute after every request, use :meth:`.Blueprint.after_app_request`.
503 """
504 self.after_request_funcs.setdefault(None, []).append(f)
505 return f
506
507 @setupmethod
508 def teardown_request(self, f: T_teardown) -> T_teardown:
509 """Register a function to be called when the request context is
510 popped. Typically this happens at the end of each request, but
511 contexts may be pushed manually as well during testing.
512
513 .. code-block:: python
514
515 with app.test_request_context():
516 ...
517
518 When the ``with`` block exits (or ``ctx.pop()`` is called), the
519 teardown functions are called just before the request context is
520 made inactive.
521
522 When a teardown function was called because of an unhandled
523 exception it will be passed an error object. If an
524 :meth:`errorhandler` is registered, it will handle the exception
525 and the teardown will not receive it.
526
527 Teardown functions must avoid raising exceptions. If they
528 execute code that might fail they must surround that code with a
529 ``try``/``except`` block and log any errors.
530
531 The return values of teardown functions are ignored.
532
533 This is available on both app and blueprint objects. When used on an app, this
534 executes after every request. When used on a blueprint, this executes after
535 every request that the blueprint handles. To register with a blueprint and
536 execute after every request, use :meth:`.Blueprint.teardown_app_request`.
537 """
src/flask/sansio/scaffold.py 302

538 self.teardown_request_funcs.setdefault(None, []).append(f)


539 return f
540
541 @setupmethod
542 def context_processor(
543 self,
544 f: T_template_context_processor,
545 ) -> T_template_context_processor:
546 """Registers a template context processor function. These functions run before
547 rendering a template. The keys of the returned dict are added as variables
548 available in the template.
549
550 This is available on both app and blueprint objects. When used on an app, this
551 is called for every rendered template. When used on a blueprint, this is called
552 for templates rendered from the blueprint's views. To register with a blueprint
553 and affect every template, use :meth:`.Blueprint.app_context_processor`.
554 """
555 self.template_context_processors[None].append(f)
556 return f
557
558 @setupmethod
559 def url_value_preprocessor(
560 self,
561 f: T_url_value_preprocessor,
562 ) -> T_url_value_preprocessor:
563 """Register a URL value preprocessor function for all view
564 functions in the application. These functions will be called before the
565 :meth:`before_request` functions.
566
567 The function can modify the values captured from the matched url before
568 they are passed to the view. For example, this can be used to pop a
569 common language code value and place it in ``g`` rather than pass it to
570 every view.
571
572 The function is passed the endpoint name and values dict. The return
573 value is ignored.
574
575 This is available on both app and blueprint objects. When used on an app, this
576 is called for every request. When used on a blueprint, this is called for
577 requests that the blueprint handles. To register with a blueprint and affect
578 every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
579 """
580 self.url_value_preprocessors[None].append(f)
581 return f
582
583 @setupmethod
584 def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
585 """Callback function for URL defaults for all view functions of the
586 application. It's called with the endpoint and values and should
587 update the values passed in place.
588
589 This is available on both app and blueprint objects. When used on an app, this
590 is called for every request. When used on a blueprint, this is called for
591 requests that the blueprint handles. To register with a blueprint and affect
592 every request, use :meth:`.Blueprint.app_url_defaults`.
593 """
594 self.url_default_functions[None].append(f)
595 return f
596
597 @setupmethod
598 def errorhandler(
599 self, code_or_exception: type[Exception] | int
600 ) -> t.Callable[[T_error_handler], T_error_handler]:
601 """Register a function to handle errors by code or exception class.
602
603 A decorator that is used to register a function given an
604 error code. Example::
605
606 @app.errorhandler(404)
607 def page_not_found(error):
608 return 'This page does not exist', 404
src/flask/sansio/scaffold.py 303

609
610 You can also register handlers for arbitrary exceptions::
611
612 @app.errorhandler(DatabaseError)
613 def special_exception_handler(error):
614 return 'Database connection failed', 500
615
616 This is available on both app and blueprint objects. When used on an app, this
617 can handle errors from every request. When used on a blueprint, this can handle
618 errors from requests that the blueprint handles. To register with a blueprint
619 and affect every request, use :meth:`.Blueprint.app_errorhandler`.
620
621 .. versionadded:: 0.7
622 Use :meth:`register_error_handler` instead of modifying
623 :attr:`error_handler_spec` directly, for application wide error
624 handlers.
625
626 .. versionadded:: 0.7
627 One can now additionally also register custom exception types
628 that do not necessarily have to be a subclass of the
629 :class:`~werkzeug.exceptions.HTTPException` class.
630
631 :param code_or_exception: the code as integer for the handler, or
632 an arbitrary exception
633 """
634
635 def decorator(f: T_error_handler) -> T_error_handler:
636 self.register_error_handler(code_or_exception, f)
637 return f
638
639 return decorator
640
641 @setupmethod
642 def register_error_handler(
643 self,
644 code_or_exception: type[Exception] | int,
645 f: ft.ErrorHandlerCallable,
646 ) -> None:
647 """Alternative error attach function to the :meth:`errorhandler`
648 decorator that is more straightforward to use for non decorator
649 usage.
650
651 .. versionadded:: 0.7
652 """
653 exc_class, code = self._get_exc_class_and_code(code_or_exception)
654 self.error_handler_spec[None][code][exc_class] = f
655
656 @staticmethod
657 def _get_exc_class_and_code(
658 exc_class_or_code: type[Exception] | int,
659 ) -> tuple[type[Exception], int | None]:
660 """Get the exception class being handled. For HTTP status codes
661 or ``HTTPException`` subclasses, return both the exception and
662 status code.
663
664 :param exc_class_or_code: Any exception class, or an HTTP status
665 code as an integer.
666 """
667 exc_class: type[Exception]
668
669 if isinstance(exc_class_or_code, int):
670 try:
671 exc_class = default_exceptions[exc_class_or_code]
672 except KeyError:
673 raise ValueError(
674 f"'{exc_class_or_code}' is not a recognized HTTP"
675 " error code. Use a subclass of HTTPException with"
676 " that code instead."
677 ) from None
678 else:
679 exc_class = exc_class_or_code
src/flask/sansio/scaffold.py 304

680
681 if isinstance(exc_class, Exception):
682 raise TypeError(
683 f"{exc_class!r} is an instance, not a class. Handlers"
684 " can only be registered for Exception classes or HTTP"
685 " error codes."
686 )
687
688 if not issubclass(exc_class, Exception):
689 raise ValueError(
690 f"'{exc_class.__name__}' is not a subclass of Exception."
691 " Handlers can only be registered for Exception classes"
692 " or HTTP error codes."
693 )
694
695 if issubclass(exc_class, HTTPException):
696 return exc_class, exc_class.code
697 else:
698 return exc_class, None
699
700
701 def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str:
702 """Internal helper that returns the default endpoint for a given
703 function. This always is the function name.
704 """
705 assert view_func is not None, "expected view func if endpoint is not provided."
706 return view_func.__name__
707
708
709 def _find_package_path(import_name: str) -> str:
710 """Find the path that contains the package or module."""
711 root_mod_name, _, _ = import_name.partition(".")
712
713 try:
714 root_spec = importlib.util.find_spec(root_mod_name)
715
716 if root_spec is None:
717 raise ValueError("not found")
718 except (ImportError, ValueError):
719 # ImportError: the machinery told us it does not exist
720 # ValueError:
721 # - the module name was invalid
722 # - the module name is __main__
723 # - we raised `ValueError` due to `root_spec` being `None`
724 return os.getcwd()
725
726 if root_spec.submodule_search_locations:
727 if root_spec.origin is None or root_spec.origin == "namespace":
728 # namespace package
729 package_spec = importlib.util.find_spec(import_name)
730
731 if package_spec is not None and package_spec.submodule_search_locations:
732 # Pick the path in the namespace that contains the submodule.
733 package_path = pathlib.Path(
734 os.path.commonpath(package_spec.submodule_search_locations)
735 )
736 search_location = next(
737 location
738 for location in root_spec.submodule_search_locations
739 if package_path.is_relative_to(location)
740 )
741 else:
742 # Pick the first path.
743 search_location = root_spec.submodule_search_locations[0]
744
745 return os.path.dirname(search_location)
746 else:
747 # package with __init__.py
748 return os.path.dirname(os.path.dirname(root_spec.origin))
749 else:
750 # module
305

751 return os.path.dirname(root_spec.origin) # type: ignore[type-var, return-value]


752
753
754 def find_package(import_name: str) -> tuple[str | None, str]:
755 """Find the prefix that a package is installed under, and the path
756 that it would be imported from.
757
758 The prefix is the directory containing the standard directory
759 hierarchy (lib, bin, etc.). If the package is not installed to the
760 system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
761 ``None`` is returned.
762
763 The path is the entry in :attr:`sys.path` that contains the package
764 for import. If the package is not installed, it's assumed that the
765 package was imported from the current working directory.
766 """
767 package_path = _find_package_path(import_name)
768 py_prefix = os.path.abspath(sys.prefix)
769
770 # installed to the system
771 if pathlib.PurePath(package_path).is_relative_to(py_prefix):
772 return py_prefix, package_path
773
774 site_parent, site_folder = os.path.split(package_path)
775
776 # installed to a virtualenv
777 if site_folder.lower() == "site-packages":
778 parent, folder = os.path.split(site_parent)
779
780 # Windows (prefix/lib/site-packages)
781 if folder.lower() == "lib":
782 return parent, package_path
783
784 # Unix (prefix/lib/pythonX.Y/site-packages)
785 if os.path.basename(parent).lower() == "lib":
786 return os.path.dirname(parent), package_path
787
788 # something else (prefix/site-packages)
789 return site_parent, package_path
790
791 # not installed
792 return None, package_path

src/flask/sansio/app.py
1 from __future__ import annotations
2
3 import logging
4 import os
5 import sys
6 import typing as t
7 from datetime import timedelta
8 from itertools import chain
9
10 from werkzeug.exceptions import Aborter
11 from werkzeug.exceptions import BadRequest
12 from werkzeug.exceptions import BadRequestKeyError
13 from werkzeug.routing import BuildError
14 from werkzeug.routing import Map
15 from werkzeug.routing import Rule
16 from werkzeug.sansio.response import Response
17 from werkzeug.utils import cached_property
18 from werkzeug.utils import redirect as _wz_redirect
19
20 from .. import typing as ft
21 from ..config import Config
22 from ..config import ConfigAttribute
23 from ..ctx import _AppCtxGlobals
24 from ..helpers import _split_blueprint_path
25 from ..helpers import get_debug_flag
src/flask/sansio/app.py 306

26 from ..json.provider import DefaultJSONProvider


27 from ..json.provider import JSONProvider
28 from ..logging import create_logger
29 from ..templating import DispatchingJinjaLoader
30 from ..templating import Environment
31 from .scaffold import _endpoint_from_view_func
32 from .scaffold import find_package
33 from .scaffold import Scaffold
34 from .scaffold import setupmethod
35
36 if t.TYPE_CHECKING: # pragma: no cover
37 from werkzeug.wrappers import Response as BaseResponse
38
39 from ..testing import FlaskClient
40 from ..testing import FlaskCliRunner
41 from .blueprints import Blueprint
42
43 T_shell_context_processor = t.TypeVar(
44 "T_shell_context_processor", bound=ft.ShellContextProcessorCallable
45 )
46 T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
47 T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
48 T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
49 T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
50
51
52 def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
53 if value is None or isinstance(value, timedelta):
54 return value
55
56 return timedelta(seconds=value)
57
58
59 class App(Scaffold):
60 """The flask object implements a WSGI application and acts as the central
61 object. It is passed the name of the module or package of the
62 application. Once it is created it will act as a central registry for
63 the view functions, the URL rules, template configuration and much more.
64
65 The name of the package is used to resolve resources from inside the
66 package or the folder the module is contained in depending on if the
67 package parameter resolves to an actual python package (a folder with
68 an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).
69
70 For more information about resource loading, see :func:`open_resource`.
71
72 Usually you create a :class:`Flask` instance in your main module or
73 in the :file:`__init__.py` file of your package like this::
74
75 from flask import Flask
76 app = Flask(__name__)
77
78 .. admonition:: About the First Parameter
79
80 The idea of the first parameter is to give Flask an idea of what
81 belongs to your application. This name is used to find resources
82 on the filesystem, can be used by extensions to improve debugging
83 information and a lot more.
84
85 So it's important what you provide there. If you are using a single
86 module, `__name__` is always the correct value. If you however are
87 using a package, it's usually recommended to hardcode the name of
88 your package there.
89
90 For example if your application is defined in :file:`yourapplication/app.py`
91 you should create it with one of the two versions below::
92
93 app = Flask('yourapplication')
94 app = Flask(__name__.split('.')[0])
95
96 Why is that? The application will work even with `__name__`, thanks
src/flask/sansio/app.py 307

97 to how resources are looked up. However it will make debugging more
98 painful. Certain extensions can make assumptions based on the
99 import name of your application. For example the Flask-SQLAlchemy
100 extension will look for the code in your application that triggered
101 an SQL query in debug mode. If the import name is not properly set
102 up, that debugging information is lost. (For example it would only
103 pick up SQL queries in `yourapplication.app` and not
104 `yourapplication.views.frontend`)
105
106 .. versionadded:: 0.7
107 The `static_url_path`, `static_folder`, and `template_folder`
108 parameters were added.
109
110 .. versionadded:: 0.8
111 The `instance_path` and `instance_relative_config` parameters were
112 added.
113
114 .. versionadded:: 0.11
115 The `root_path` parameter was added.
116
117 .. versionadded:: 1.0
118 The ``host_matching`` and ``static_host`` parameters were added.
119
120 .. versionadded:: 1.0
121 The ``subdomain_matching`` parameter was added. Subdomain
122 matching needs to be enabled manually now. Setting
123 :data:`SERVER_NAME` does not implicitly enable it.
124
125 :param import_name: the name of the application package
126 :param static_url_path: can be used to specify a different path for the
127 static files on the web. Defaults to the name
128 of the `static_folder` folder.
129 :param static_folder: The folder with static files that is served at
130 ``static_url_path``. Relative to the application ``root_path``
131 or an absolute path. Defaults to ``'static'``.
132 :param static_host: the host to use when adding the static route.
133 Defaults to None. Required when using ``host_matching=True``
134 with a ``static_folder`` configured.
135 :param host_matching: set ``url_map.host_matching`` attribute.
136 Defaults to False.
137 :param subdomain_matching: consider the subdomain relative to
138 :data:`SERVER_NAME` when matching routes. Defaults to False.
139 :param template_folder: the folder that contains the templates that should
140 be used by the application. Defaults to
141 ``'templates'`` folder in the root path of the
142 application.
143 :param instance_path: An alternative instance path for the application.
144 By default the folder ``'instance'`` next to the
145 package or module is assumed to be the instance
146 path.
147 :param instance_relative_config: if set to ``True`` relative filenames
148 for loading the config are assumed to
149 be relative to the instance path instead
150 of the application root.
151 :param root_path: The path to the root of the application files.
152 This should only be set manually when it can't be detected
153 automatically, such as for namespace packages.
154 """
155
156 #: The class of the object assigned to :attr:`aborter`, created by
157 #: :meth:`create_aborter`. That object is called by
158 #: :func:`flask.abort` to raise HTTP errors, and can be
159 #: called directly as well.
160 #:
161 #: Defaults to :class:`werkzeug.exceptions.Aborter`.
162 #:
163 #: .. versionadded:: 2.2
164 aborter_class = Aborter
165
166 #: The class that is used for the Jinja environment.
167 #:
src/flask/sansio/app.py 308

168 #: .. versionadded:: 0.11


169 jinja_environment = Environment
170
171 #: The class that is used for the :data:`~flask.g` instance.
172 #:
173 #: Example use cases for a custom class:
174 #:
175 #: 1. Store arbitrary attributes on flask.g.
176 #: 2. Add a property for lazy per-request database connectors.
177 #: 3. Return None instead of AttributeError on unexpected attributes.
178 #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g.
179 #:
180 #: In Flask 0.9 this property was called `request_globals_class` but it
181 #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
182 #: flask.g object is now application context scoped.
183 #:
184 #: .. versionadded:: 0.10
185 app_ctx_globals_class = _AppCtxGlobals
186
187 #: The class that is used for the ``config`` attribute of this app.
188 #: Defaults to :class:`~flask.Config`.
189 #:
190 #: Example use cases for a custom class:
191 #:
192 #: 1. Default values for certain config options.
193 #: 2. Access to config values through attributes in addition to keys.
194 #:
195 #: .. versionadded:: 0.11
196 config_class = Config
197
198 #: The testing flag. Set this to ``True`` to enable the test mode of
199 #: Flask extensions (and in the future probably also Flask itself).
200 #: For example this might activate test helpers that have an
201 #: additional runtime cost which should not be enabled by default.
202 #:
203 #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
204 #: default it's implicitly enabled.
205 #:
206 #: This attribute can also be configured from the config with the
207 #: ``TESTING`` configuration key. Defaults to ``False``.
208 testing = ConfigAttribute[bool]("TESTING")
209
210 #: If a secret key is set, cryptographic components can use this to
211 #: sign cookies and other things. Set this to a complex random value
212 #: when you want to use the secure cookie for instance.
213 #:
214 #: This attribute can also be configured from the config with the
215 #: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
216 secret_key = ConfigAttribute[t.Union[str, bytes, None]]("SECRET_KEY")
217
218 #: A :class:`~datetime.timedelta` which is used to set the expiration
219 #: date of a permanent session. The default is 31 days which makes a
220 #: permanent session survive for roughly one month.
221 #:
222 #: This attribute can also be configured from the config with the
223 #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to
224 #: ``timedelta(days=31)``
225 permanent_session_lifetime = ConfigAttribute[timedelta](
226 "PERMANENT_SESSION_LIFETIME",
227 get_converter=_make_timedelta, # type: ignore[arg-type]
228 )
229
230 json_provider_class: type[JSONProvider] = DefaultJSONProvider
231 """A subclass of :class:`~flask.json.provider.JSONProvider`. An
232 instance is created and assigned to :attr:`app.json` when creating
233 the app.
234
235 The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses
236 Python's built-in :mod:`json` library. A different provider can use
237 a different JSON library.
238
src/flask/sansio/app.py 309

239 .. versionadded:: 2.2


240 """
241
242 #: Options that are passed to the Jinja environment in
243 #: :meth:`create_jinja_environment`. Changing these options after
244 #: the environment is created (accessing :attr:`jinja_env`) will
245 #: have no effect.
246 #:
247 #: .. versionchanged:: 1.1.0
248 #: This is a ``dict`` instead of an ``ImmutableDict`` to allow
249 #: easier configuration.
250 #:
251 jinja_options: dict[str, t.Any] = {}
252
253 #: The rule object to use for URL rules created. This is used by
254 #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`.
255 #:
256 #: .. versionadded:: 0.7
257 url_rule_class = Rule
258
259 #: The map object to use for storing the URL rules and routing
260 #: configuration parameters. Defaults to :class:`werkzeug.routing.Map`.
261 #:
262 #: .. versionadded:: 1.1.0
263 url_map_class = Map
264
265 #: The :meth:`test_client` method creates an instance of this test
266 #: client class. Defaults to :class:`~flask.testing.FlaskClient`.
267 #:
268 #: .. versionadded:: 0.7
269 test_client_class: type[FlaskClient] | None = None
270
271 #: The :class:`~click.testing.CliRunner` subclass, by default
272 #: :class:`~flask.testing.FlaskCliRunner` that is used by
273 #: :meth:`test_cli_runner`. Its ``__init__`` method should take a
274 #: Flask app object as the first argument.
275 #:
276 #: .. versionadded:: 1.0
277 test_cli_runner_class: type[FlaskCliRunner] | None = None
278
279 default_config: dict[str, t.Any]
280 response_class: type[Response]
281
282 def __init__(
283 self,
284 import_name: str,
285 static_url_path: str | None = None,
286 static_folder: str | os.PathLike[str] | None = "static",
287 static_host: str | None = None,
288 host_matching: bool = False,
289 subdomain_matching: bool = False,
290 template_folder: str | os.PathLike[str] | None = "templates",
291 instance_path: str | None = None,
292 instance_relative_config: bool = False,
293 root_path: str | None = None,
294 ) -> None:
295 super().__init__(
296 import_name=import_name,
297 static_folder=static_folder,
298 static_url_path=static_url_path,
299 template_folder=template_folder,
300 root_path=root_path,
301 )
302
303 if instance_path is None:
304 instance_path = self.auto_find_instance_path()
305 elif not os.path.isabs(instance_path):
306 raise ValueError(
307 "If an instance path is provided it must be absolute."
308 " A relative path was given instead."
309 )
src/flask/sansio/app.py 310

310
311 #: Holds the path to the instance folder.
312 #:
313 #: .. versionadded:: 0.8
314 self.instance_path = instance_path
315
316 #: The configuration dictionary as :class:`Config`. This behaves
317 #: exactly like a regular dictionary but supports additional methods
318 #: to load a config from files.
319 self.config = self.make_config(instance_relative_config)
320
321 #: An instance of :attr:`aborter_class` created by
322 #: :meth:`make_aborter`. This is called by :func:`flask.abort`
323 #: to raise HTTP errors, and can be called directly as well.
324 #:
325 #: .. versionadded:: 2.2
326 #: Moved from ``flask.abort``, which calls this object.
327 self.aborter = self.make_aborter()
328
329 self.json: JSONProvider = self.json_provider_class(self)
330 """Provides access to JSON methods. Functions in ``flask.json``
331 will call methods on this provider when the application context
332 is active. Used for handling JSON requests and responses.
333
334 An instance of :attr:`json_provider_class`. Can be customized by
335 changing that attribute on a subclass, or by assigning to this
336 attribute afterwards.
337
338 The default, :class:`~flask.json.provider.DefaultJSONProvider`,
339 uses Python's built-in :mod:`json` library. A different provider
340 can use a different JSON library.
341
342 .. versionadded:: 2.2
343 """
344
345 #: A list of functions that are called by
346 #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
347 #: :exc:`~werkzeug.routing.BuildError`. Each function is called
348 #: with ``error``, ``endpoint`` and ``values``. If a function
349 #: returns ``None`` or raises a ``BuildError``, it is skipped.
350 #: Otherwise, its return value is returned by ``url_for``.
351 #:
352 #: .. versionadded:: 0.9
353 self.url_build_error_handlers: list[
354 t.Callable[[Exception, str, dict[str, t.Any]], str]
355 ] = []
356
357 #: A list of functions that are called when the application context
358 #: is destroyed. Since the application context is also torn down
359 #: if the request ends this is the place to store code that disconnects
360 #: from databases.
361 #:
362 #: .. versionadded:: 0.9
363 self.teardown_appcontext_funcs: list[ft.TeardownCallable] = []
364
365 #: A list of shell context processor functions that should be run
366 #: when a shell context is created.
367 #:
368 #: .. versionadded:: 0.11
369 self.shell_context_processors: list[ft.ShellContextProcessorCallable] = []
370
371 #: Maps registered blueprint names to blueprint objects. The
372 #: dict retains the order the blueprints were registered in.
373 #: Blueprints can be registered multiple times, this dict does
374 #: not track how often they were attached.
375 #:
376 #: .. versionadded:: 0.7
377 self.blueprints: dict[str, Blueprint] = {}
378
379 #: a place where extensions can store application specific state. For
380 #: example this is where an extension could store database engines and
src/flask/sansio/app.py 311

381 #: similar things.


382 #:
383 #: The key must match the name of the extension module. For example in
384 #: case of a "Flask-Foo" extension in `flask_foo`, the key would be
385 #: ``'foo'``.
386 #:
387 #: .. versionadded:: 0.7
388 self.extensions: dict[str, t.Any] = {}
389
390 #: The :class:`~werkzeug.routing.Map` for this instance. You can use
391 #: this to change the routing converters after the class was created
392 #: but before any routes are connected. Example::
393 #:
394 #: from werkzeug.routing import BaseConverter
395 #:
396 #: class ListConverter(BaseConverter):
397 #: def to_python(self, value):
398 #: return value.split(',')
399 #: def to_url(self, values):
400 #: return ','.join(super(ListConverter, self).to_url(value)
401 #: for value in values)
402 #:
403 #: app = Flask(__name__)
404 #: app.url_map.converters['list'] = ListConverter
405 self.url_map = self.url_map_class(host_matching=host_matching)
406
407 self.subdomain_matching = subdomain_matching
408
409 # tracks internally if the application already handled at least one
410 # request.
411 self._got_first_request = False
412
413 def _check_setup_finished(self, f_name: str) -> None:
414 if self._got_first_request:
415 raise AssertionError(
416 f"The setup method '{f_name}' can no longer be called"
417 " on the application. It has already handled its first"
418 " request, any changes will not be applied"
419 " consistently.\n"
420 "Make sure all imports, decorators, functions, etc."
421 " needed to set up the application are done before"
422 " running it."
423 )
424
425 @cached_property
426 def name(self) -> str: # type: ignore
427 """The name of the application. This is usually the import name
428 with the difference that it's guessed from the run file if the
429 import name is main. This name is used as a display name when
430 Flask needs the name of the application. It can be set and overridden
431 to change the value.
432
433 .. versionadded:: 0.8
434 """
435 if self.import_name == "__main__":
436 fn: str | None = getattr(sys.modules["__main__"], "__file__", None)
437 if fn is None:
438 return "__main__"
439 return os.path.splitext(os.path.basename(fn))[0]
440 return self.import_name
441
442 @cached_property
443 def logger(self) -> logging.Logger:
444 """A standard Python :class:`~logging.Logger` for the app, with
445 the same name as :attr:`name`.
446
447 In debug mode, the logger's :attr:`~logging.Logger.level` will
448 be set to :data:`~logging.DEBUG`.
449
450 If there are no handlers configured, a default handler will be
451 added. See :doc:`/logging` for more information.
src/flask/sansio/app.py 312

452
453 .. versionchanged:: 1.1.0
454 The logger takes the same name as :attr:`name` rather than
455 hard-coding ``"flask.app"``.
456
457 .. versionchanged:: 1.0.0
458 Behavior was simplified. The logger is always named
459 ``"flask.app"``. The level is only set during configuration,
460 it doesn't check ``app.debug`` each time. Only one format is
461 used, not different ones depending on ``app.debug``. No
462 handlers are removed, and a handler is only added if no
463 handlers are already configured.
464
465 .. versionadded:: 0.3
466 """
467 return create_logger(self)
468
469 @cached_property
470 def jinja_env(self) -> Environment:
471 """The Jinja environment used to load templates.
472
473 The environment is created the first time this property is
474 accessed. Changing :attr:`jinja_options` after that will have no
475 effect.
476 """
477 return self.create_jinja_environment()
478
479 def create_jinja_environment(self) -> Environment:
480 raise NotImplementedError()
481
482 def make_config(self, instance_relative: bool = False) -> Config:
483 """Used to create the config attribute by the Flask constructor.
484 The `instance_relative` parameter is passed in from the constructor
485 of Flask (there named `instance_relative_config`) and indicates if
486 the config should be relative to the instance path or the root path
487 of the application.
488
489 .. versionadded:: 0.8
490 """
491 root_path = self.root_path
492 if instance_relative:
493 root_path = self.instance_path
494 defaults = dict(self.default_config)
495 defaults["DEBUG"] = get_debug_flag()
496 return self.config_class(root_path, defaults)
497
498 def make_aborter(self) -> Aborter:
499 """Create the object to assign to :attr:`aborter`. That object
500 is called by :func:`flask.abort` to raise HTTP errors, and can
501 be called directly as well.
502
503 By default, this creates an instance of :attr:`aborter_class`,
504 which defaults to :class:`werkzeug.exceptions.Aborter`.
505
506 .. versionadded:: 2.2
507 """
508 return self.aborter_class()
509
510 def auto_find_instance_path(self) -> str:
511 """Tries to locate the instance path if it was not provided to the
512 constructor of the application class. It will basically calculate
513 the path to a folder named ``instance`` next to your main file or
514 the package.
515
516 .. versionadded:: 0.8
517 """
518 prefix, package_path = find_package(self.import_name)
519 if prefix is None:
520 return os.path.join(package_path, "instance")
521 return os.path.join(prefix, "var", f"{self.name}-instance")
522
src/flask/sansio/app.py 313

523 def create_global_jinja_loader(self) -> DispatchingJinjaLoader:


524 """Creates the loader for the Jinja2 environment. Can be used to
525 override just the loader and keeping the rest unchanged. It's
526 discouraged to override this function. Instead one should override
527 the :meth:`jinja_loader` function instead.
528
529 The global loader dispatches between the loaders of the application
530 and the individual blueprints.
531
532 .. versionadded:: 0.7
533 """
534 return DispatchingJinjaLoader(self)
535
536 def select_jinja_autoescape(self, filename: str) -> bool:
537 """Returns ``True`` if autoescaping should be active for the given
538 template name. If no template name is given, returns `True`.
539
540 .. versionchanged:: 2.2
541 Autoescaping is now enabled by default for ``.svg`` files.
542
543 .. versionadded:: 0.5
544 """
545 if filename is None:
546 return True
547 return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
548
549 @property
550 def debug(self) -> bool:
551 """Whether debug mode is enabled. When using ``flask run`` to start the
552 development server, an interactive debugger will be shown for unhandled
553 exceptions, and the server will be reloaded when code changes. This maps to the
554 :data:`DEBUG` config key. It may not behave as expected if set late.
555
556 **Do not enable debug mode when deploying in production.**
557
558 Default: ``False``
559 """
560 return self.config["DEBUG"] # type: ignore[no-any-return]
561
562 @debug.setter
563 def debug(self, value: bool) -> None:
564 self.config["DEBUG"] = value
565
566 if self.config["TEMPLATES_AUTO_RELOAD"] is None:
567 self.jinja_env.auto_reload = value
568
569 @setupmethod
570 def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
571 """Register a :class:`~flask.Blueprint` on the application. Keyword
572 arguments passed to this method will override the defaults set on the
573 blueprint.
574
575 Calls the blueprint's :meth:`~flask.Blueprint.register` method after
576 recording the blueprint in the application's :attr:`blueprints`.
577
578 :param blueprint: The blueprint to register.
579 :param url_prefix: Blueprint routes will be prefixed with this.
580 :param subdomain: Blueprint routes will match on this subdomain.
581 :param url_defaults: Blueprint routes will use these default values for
582 view arguments.
583 :param options: Additional keyword arguments are passed to
584 :class:`~flask.blueprints.BlueprintSetupState`. They can be
585 accessed in :meth:`~flask.Blueprint.record` callbacks.
586
587 .. versionchanged:: 2.0.1
588 The ``name`` option can be used to change the (pre-dotted)
589 name the blueprint is registered with. This allows the same
590 blueprint to be registered multiple times with unique names
591 for ``url_for``.
592
593 .. versionadded:: 0.7
src/flask/sansio/app.py 314

594 """
595 blueprint.register(self, options)
596
597 def iter_blueprints(self) -> t.ValuesView[Blueprint]:
598 """Iterates over all blueprints by the order they were registered.
599
600 .. versionadded:: 0.11
601 """
602 return self.blueprints.values()
603
604 @setupmethod
605 def add_url_rule(
606 self,
607 rule: str,
608 endpoint: str | None = None,
609 view_func: ft.RouteCallable | None = None,
610 provide_automatic_options: bool | None = None,
611 **options: t.Any,
612 ) -> None:
613 if endpoint is None:
614 endpoint = _endpoint_from_view_func(view_func) # type: ignore
615 options["endpoint"] = endpoint
616 methods = options.pop("methods", None)
617
618 # if the methods are not given and the view_func object knows its
619 # methods we can use that instead. If neither exists, we go with
620 # a tuple of only ``GET`` as default.
621 if methods is None:
622 methods = getattr(view_func, "methods", None) or ("GET",)
623 if isinstance(methods, str):
624 raise TypeError(
625 "Allowed methods must be a list of strings, for"
626 ' example: @app.route(..., methods=["POST"])'
627 )
628 methods = {item.upper() for item in methods}
629
630 # Methods that should always be added
631 required_methods: set[str] = set(getattr(view_func, "required_methods", ()))
632
633 # starting with Flask 0.8 the view_func object can disable and
634 # force-enable the automatic options handling.
635 if provide_automatic_options is None:
636 provide_automatic_options = getattr(
637 view_func, "provide_automatic_options", None
638 )
639
640 if provide_automatic_options is None:
641 if "OPTIONS" not in methods and self.config["PROVIDE_AUTOMATIC_OPTIONS"]:
642 provide_automatic_options = True
643 required_methods.add("OPTIONS")
644 else:
645 provide_automatic_options = False
646
647 # Add the required methods now.
648 methods |= required_methods
649
650 rule_obj = self.url_rule_class(rule, methods=methods, **options)
651 rule_obj.provide_automatic_options = provide_automatic_options # type: ignore[attr-defined]
652
653 self.url_map.add(rule_obj)
654 if view_func is not None:
655 old_func = self.view_functions.get(endpoint)
656 if old_func is not None and old_func != view_func:
657 raise AssertionError(
658 "View function mapping is overwriting an existing"
659 f" endpoint function: {endpoint}"
660 )
661 self.view_functions[endpoint] = view_func
662
663 @setupmethod
664 def template_filter(
src/flask/sansio/app.py 315

665 self, name: str | None = None


666 ) -> t.Callable[[T_template_filter], T_template_filter]:
667 """A decorator that is used to register custom template filter.
668 You can specify a name for the filter, otherwise the function
669 name will be used. Example::
670
671 @app.template_filter()
672 def reverse(s):
673 return s[::-1]
674
675 :param name: the optional name of the filter, otherwise the
676 function name will be used.
677 """
678
679 def decorator(f: T_template_filter) -> T_template_filter:
680 self.add_template_filter(f, name=name)
681 return f
682
683 return decorator
684
685 @setupmethod
686 def add_template_filter(
687 self, f: ft.TemplateFilterCallable, name: str | None = None
688 ) -> None:
689 """Register a custom template filter. Works exactly like the
690 :meth:`template_filter` decorator.
691
692 :param name: the optional name of the filter, otherwise the
693 function name will be used.
694 """
695 self.jinja_env.filters[name or f.__name__] = f
696
697 @setupmethod
698 def template_test(
699 self, name: str | None = None
700 ) -> t.Callable[[T_template_test], T_template_test]:
701 """A decorator that is used to register custom template test.
702 You can specify a name for the test, otherwise the function
703 name will be used. Example::
704
705 @app.template_test()
706 def is_prime(n):
707 if n == 2:
708 return True
709 for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
710 if n % i == 0:
711 return False
712 return True
713
714 .. versionadded:: 0.10
715
716 :param name: the optional name of the test, otherwise the
717 function name will be used.
718 """
719
720 def decorator(f: T_template_test) -> T_template_test:
721 self.add_template_test(f, name=name)
722 return f
723
724 return decorator
725
726 @setupmethod
727 def add_template_test(
728 self, f: ft.TemplateTestCallable, name: str | None = None
729 ) -> None:
730 """Register a custom template test. Works exactly like the
731 :meth:`template_test` decorator.
732
733 .. versionadded:: 0.10
734
735 :param name: the optional name of the test, otherwise the
src/flask/sansio/app.py 316

736 function name will be used.


737 """
738 self.jinja_env.tests[name or f.__name__] = f
739
740 @setupmethod
741 def template_global(
742 self, name: str | None = None
743 ) -> t.Callable[[T_template_global], T_template_global]:
744 """A decorator that is used to register a custom template global function.
745 You can specify a name for the global function, otherwise the function
746 name will be used. Example::
747
748 @app.template_global()
749 def double(n):
750 return 2 * n
751
752 .. versionadded:: 0.10
753
754 :param name: the optional name of the global function, otherwise the
755 function name will be used.
756 """
757
758 def decorator(f: T_template_global) -> T_template_global:
759 self.add_template_global(f, name=name)
760 return f
761
762 return decorator
763
764 @setupmethod
765 def add_template_global(
766 self, f: ft.TemplateGlobalCallable, name: str | None = None
767 ) -> None:
768 """Register a custom template global function. Works exactly like the
769 :meth:`template_global` decorator.
770
771 .. versionadded:: 0.10
772
773 :param name: the optional name of the global function, otherwise the
774 function name will be used.
775 """
776 self.jinja_env.globals[name or f.__name__] = f
777
778 @setupmethod
779 def teardown_appcontext(self, f: T_teardown) -> T_teardown:
780 """Registers a function to be called when the application
781 context is popped. The application context is typically popped
782 after the request context for each request, at the end of CLI
783 commands, or after a manually pushed context ends.
784
785 .. code-block:: python
786
787 with app.app_context():
788 ...
789
790 When the ``with`` block exits (or ``ctx.pop()`` is called), the
791 teardown functions are called just before the app context is
792 made inactive. Since a request context typically also manages an
793 application context it would also be called when you pop a
794 request context.
795
796 When a teardown function was called because of an unhandled
797 exception it will be passed an error object. If an
798 :meth:`errorhandler` is registered, it will handle the exception
799 and the teardown will not receive it.
800
801 Teardown functions must avoid raising exceptions. If they
802 execute code that might fail they must surround that code with a
803 ``try``/``except`` block and log any errors.
804
805 The return values of teardown functions are ignored.
806
src/flask/sansio/app.py 317

807 .. versionadded:: 0.9


808 """
809 self.teardown_appcontext_funcs.append(f)
810 return f
811
812 @setupmethod
813 def shell_context_processor(
814 self, f: T_shell_context_processor
815 ) -> T_shell_context_processor:
816 """Registers a shell context processor function.
817
818 .. versionadded:: 0.11
819 """
820 self.shell_context_processors.append(f)
821 return f
822
823 def _find_error_handler(
824 self, e: Exception, blueprints: list[str]
825 ) -> ft.ErrorHandlerCallable | None:
826 """Return a registered error handler for an exception in this order:
827 blueprint handler for a specific code, app handler for a specific code,
828 blueprint handler for an exception class, app handler for an exception
829 class, or ``None`` if a suitable handler is not found.
830 """
831 exc_class, code = self._get_exc_class_and_code(type(e))
832 names = (*blueprints, None)
833
834 for c in (code, None) if code is not None else (None,):
835 for name in names:
836 handler_map = self.error_handler_spec[name][c]
837
838 if not handler_map:
839 continue
840
841 for cls in exc_class.__mro__:
842 handler = handler_map.get(cls)
843
844 if handler is not None:
845 return handler
846 return None
847
848 def trap_http_exception(self, e: Exception) -> bool:
849 """Checks if an HTTP exception should be trapped or not. By default
850 this will return ``False`` for all exceptions except for a bad request
851 key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It
852 also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``.
853
854 This is called for all HTTP exceptions raised by a view function.
855 If it returns ``True`` for any exception the error handler for this
856 exception is not called and it shows up as regular exception in the
857 traceback. This is helpful for debugging implicitly raised HTTP
858 exceptions.
859
860 .. versionchanged:: 1.0
861 Bad request errors are not trapped by default in debug mode.
862
863 .. versionadded:: 0.8
864 """
865 if self.config["TRAP_HTTP_EXCEPTIONS"]:
866 return True
867
868 trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"]
869
870 # if unset, trap key errors in debug mode
871 if (
872 trap_bad_request is None
873 and self.debug
874 and isinstance(e, BadRequestKeyError)
875 ):
876 return True
877
src/flask/sansio/app.py 318

878 if trap_bad_request:
879 return isinstance(e, BadRequest)
880
881 return False
882
883 def should_ignore_error(self, error: BaseException | None) -> bool:
884 """This is called to figure out if an error should be ignored
885 or not as far as the teardown system is concerned. If this
886 function returns ``True`` then the teardown handlers will not be
887 passed the error.
888
889 .. versionadded:: 0.10
890 """
891 return False
892
893 def redirect(self, location: str, code: int = 302) -> BaseResponse:
894 """Create a redirect response object.
895
896 This is called by :func:`flask.redirect`, and can be called
897 directly as well.
898
899 :param location: The URL to redirect to.
900 :param code: The status code for the redirect.
901
902 .. versionadded:: 2.2
903 Moved from ``flask.redirect``, which calls this method.
904 """
905 return _wz_redirect(
906 location,
907 code=code,
908 Response=self.response_class, # type: ignore[arg-type]
909 )
910
911 def inject_url_defaults(self, endpoint: str, values: dict[str, t.Any]) -> None:
912 """Injects the URL defaults for the given endpoint directly into
913 the values dictionary passed. This is used internally and
914 automatically called on URL building.
915
916 .. versionadded:: 0.7
917 """
918 names: t.Iterable[str | None] = (None,)
919
920 # url_for may be called outside a request context, parse the
921 # passed endpoint instead of using request.blueprints.
922 if "." in endpoint:
923 names = chain(
924 names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
925 )
926
927 for name in names:
928 if name in self.url_default_functions:
929 for func in self.url_default_functions[name]:
930 func(endpoint, values)
931
932 def handle_url_build_error(
933 self, error: BuildError, endpoint: str, values: dict[str, t.Any]
934 ) -> str:
935 """Called by :meth:`.url_for` if a
936 :exc:`~werkzeug.routing.BuildError` was raised. If this returns
937 a value, it will be returned by ``url_for``, otherwise the error
938 will be re-raised.
939
940 Each function in :attr:`url_build_error_handlers` is called with
941 ``error``, ``endpoint`` and ``values``. If a function returns
942 ``None`` or raises a ``BuildError``, it is skipped. Otherwise,
943 its return value is returned by ``url_for``.
944
945 :param error: The active ``BuildError`` being handled.
946 :param endpoint: The endpoint being built.
947 :param values: The keyword arguments passed to ``url_for``.
948 """
src/flask/sansio/app.py 319

949 for handler in self.url_build_error_handlers:


950 try:
951 rv = handler(error, endpoint, values)
952 except BuildError as e:
953 # make error available outside except block
954 error = e
955 else:
956 if rv is not None:
957 return rv
958
959 # Re-raise if called with an active exception, otherwise raise
960 # the passed in exception.
961 if error is sys.exc_info()[1]:
962 raise
963
964 raise error

You might also like