@ -0,0 +1,153 @@
|
||||
# Flask
|
||||
from flask import Flask, render_template_string, request, redirect, url_for, session, jsonify, flash, g
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'demo_secret_key_for_testing'
|
||||
|
||||
# 模拟数据库
|
||||
users = {}
|
||||
messages = []
|
||||
|
||||
# 首页模板
|
||||
HOME_TEMPLATE = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Flask 示例应用</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
||||
.flash { padding: 10px; margin: 10px 0; background: #d4edda; border-radius: 5px; }
|
||||
.message { padding: 10px; margin: 5px 0; background: #f8f9fa; border-radius: 5px; }
|
||||
form { margin: 20px 0; }
|
||||
input, button { padding: 10px; margin: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Flask 示例应用</h1>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if session.get('username') %}
|
||||
<p>欢迎, {{ session['username'] }}! <a href="{{ url_for('logout') }}">退出</a></p>
|
||||
{% else %}
|
||||
<p><a href="{{ url_for('login') }}">登录</a> | <a href="{{ url_for('register') }}">注册</a></p>
|
||||
{% endif %}
|
||||
|
||||
<h2>留言板</h2>
|
||||
<form method="POST" action="{{ url_for('add_message') }}">
|
||||
<input type="text" name="content" placeholder="输入留言..." required>
|
||||
<button type="submit">发送</button>
|
||||
</form>
|
||||
|
||||
<h3>所有留言:</h3>
|
||||
{% for msg in messages %}
|
||||
<div class="message"><strong>{{ msg.user }}:</strong> {{ msg.content }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<hr>
|
||||
<p><a href="{{ url_for('api_info') }}">API 信息 (JSON)</a></p>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
LOGIN_TEMPLATE = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>登录</title></head>
|
||||
<body>
|
||||
<h1>登录</h1>
|
||||
<form method="POST">
|
||||
<input type="text" name="username" placeholder="用户名" required><br><br>
|
||||
<input type="password" name="password" placeholder="密码" required><br><br>
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
<p><a href="{{ url_for('index') }}">返回首页</a></p>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
REGISTER_TEMPLATE = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>注册</title></head>
|
||||
<body>
|
||||
<h1>注册</h1>
|
||||
<form method="POST">
|
||||
<input type="text" name="username" placeholder="用户名" required><br><br>
|
||||
<input type="password" name="password" placeholder="密码" required><br><br>
|
||||
<button type="submit">注册</button>
|
||||
</form>
|
||||
<p><a href="{{ url_for('index') }}">返回首页</a></p>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
"""请求前钩子 - 记录请求信息"""
|
||||
g.request_start = 'Request started'
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""首页路由"""
|
||||
return render_template_string(HOME_TEMPLATE, messages=messages)
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
"""登录路由"""
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
if username in users and users[username] == password:
|
||||
session['username'] = username
|
||||
flash('登录成功!')
|
||||
return redirect(url_for('index'))
|
||||
flash('用户名或密码错误!')
|
||||
return render_template_string(LOGIN_TEMPLATE)
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
"""注册路由"""
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
if username in users:
|
||||
flash('用户名已存在!')
|
||||
else:
|
||||
users[username] = password
|
||||
flash('注册成功,请登录!')
|
||||
return redirect(url_for('login'))
|
||||
return render_template_string(REGISTER_TEMPLATE)
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
"""退出登录"""
|
||||
session.pop('username', None)
|
||||
flash('已退出登录')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/message', methods=['POST'])
|
||||
def add_message():
|
||||
"""添加留言"""
|
||||
content = request.form.get('content', '')
|
||||
username = session.get('username', '匿名用户')
|
||||
if content:
|
||||
messages.append({'user': username, 'content': content})
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/api/info')
|
||||
def api_info():
|
||||
"""API 接口 - 返回 JSON 数据"""
|
||||
return jsonify({
|
||||
'app_name': 'Flask Demo',
|
||||
'version': '1.0',
|
||||
'total_users': len(users),
|
||||
'total_messages': len(messages)
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Flask 示例应用")
|
||||
print("运行命令: flask run 或 python demo_app.py")
|
||||
print("访问地址: http://127.0.0.1:5000")
|
||||
app.run(debug=True)
|
||||
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "pallets/flask",
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/.venv",
|
||||
"python.terminal.activateEnvInCurrentTerminal": true,
|
||||
"python.terminal.launchArgs": [
|
||||
"-X",
|
||||
"dev"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"onCreateCommand": ".devcontainer/on-create-command.sh"
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
python3 -m venv --upgrade-deps .venv
|
||||
. .venv/bin/activate
|
||||
pip install -r requirements/dev.txt
|
||||
pip install -e .
|
||||
pre-commit install --install-hooks
|
||||
@ -0,0 +1,13 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
max_line_length = 88
|
||||
|
||||
[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}]
|
||||
indent_size = 2
|
||||
@ -0,0 +1,8 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
__pycache__/
|
||||
dist/
|
||||
.coverage*
|
||||
htmlcov/
|
||||
.tox/
|
||||
docs/_build/
|
||||
@ -0,0 +1,18 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 488940d9de1b658fac229e34c521d75a6ea476f2 # frozen: v0.14.5
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: b6675a113e27a9b18f3d60c05794d62ca80c7ab5 # frozen: 0.9.9
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: debug-statements
|
||||
- id: fix-byte-order-marker
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
build:
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: '3.13'
|
||||
commands:
|
||||
- asdf plugin add uv
|
||||
- asdf install uv latest
|
||||
- asdf global uv latest
|
||||
- uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html
|
||||
@ -0,0 +1,28 @@
|
||||
Copyright 2010 Pallets
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@ -0,0 +1,53 @@
|
||||
<div align="center"><img src="https://raw.githubusercontent.com/pallets/flask/refs/heads/stable/docs/_static/flask-name.svg" alt="" height="150"></div>
|
||||
|
||||
# Flask
|
||||
|
||||
Flask is a lightweight [WSGI] web application framework. It is designed
|
||||
to make getting started quick and easy, with the ability to scale up to
|
||||
complex applications. It began as a simple wrapper around [Werkzeug]
|
||||
and [Jinja], and has become one of the most popular Python web
|
||||
application frameworks.
|
||||
|
||||
Flask offers suggestions, but doesn't enforce any dependencies or
|
||||
project layout. It is up to the developer to choose the tools and
|
||||
libraries they want to use. There are many extensions provided by the
|
||||
community that make adding new functionality easy.
|
||||
|
||||
[WSGI]: https://wsgi.readthedocs.io/
|
||||
[Werkzeug]: https://werkzeug.palletsprojects.com/
|
||||
[Jinja]: https://jinja.palletsprojects.com/
|
||||
|
||||
## A Simple Example
|
||||
|
||||
```python
|
||||
# save this as app.py
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def hello():
|
||||
return "Hello, World!"
|
||||
```
|
||||
|
||||
```
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
## Donate
|
||||
|
||||
The Pallets organization develops and supports Flask and the libraries
|
||||
it uses. In order to grow the community of contributors and users, and
|
||||
allow the maintainers to devote more time to the projects, [please
|
||||
donate today].
|
||||
|
||||
[please donate today]: https://palletsprojects.com/donate
|
||||
|
||||
## Contributing
|
||||
|
||||
See our [detailed contributing documentation][contrib] for many ways to
|
||||
contribute, including reporting issues, requesting features, asking or answering
|
||||
questions, and making PRs.
|
||||
|
||||
[contrib]: https://palletsprojects.com/contributing/
|
||||
@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 97 KiB |
@ -0,0 +1,708 @@
|
||||
API
|
||||
===
|
||||
|
||||
.. module:: flask
|
||||
|
||||
This part of the documentation covers all the interfaces of Flask. For
|
||||
parts where Flask depends on external libraries, we document the most
|
||||
important right here and provide links to the canonical documentation.
|
||||
|
||||
|
||||
Application Object
|
||||
------------------
|
||||
|
||||
.. autoclass:: Flask
|
||||
:members:
|
||||
:inherited-members:
|
||||
|
||||
|
||||
Blueprint Objects
|
||||
-----------------
|
||||
|
||||
.. autoclass:: Blueprint
|
||||
:members:
|
||||
:inherited-members:
|
||||
|
||||
Incoming Request Data
|
||||
---------------------
|
||||
|
||||
.. autoclass:: Request
|
||||
:members:
|
||||
:inherited-members:
|
||||
:exclude-members: json_module
|
||||
|
||||
.. data:: request
|
||||
|
||||
A proxy to the request data for the current request, an instance of
|
||||
:class:`.Request`.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
|
||||
|
||||
Response Objects
|
||||
----------------
|
||||
|
||||
.. autoclass:: flask.Response
|
||||
:members:
|
||||
:inherited-members:
|
||||
:exclude-members: json_module
|
||||
|
||||
Sessions
|
||||
--------
|
||||
|
||||
If you have set :attr:`Flask.secret_key` (or configured it from
|
||||
:data:`SECRET_KEY`) you can use sessions in Flask applications. A session makes
|
||||
it possible to remember information from one request to another. The way Flask
|
||||
does this is by using a signed cookie. The user can look at the session
|
||||
contents, but can't modify it unless they know the secret key, so make sure to
|
||||
set that to something complex and unguessable.
|
||||
|
||||
To access the current session you can use the :data:`.session` proxy.
|
||||
|
||||
.. data:: session
|
||||
|
||||
A proxy to the session data for the current request, an instance of
|
||||
:class:`.SessionMixin`.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
|
||||
The session object works like a dict but tracks assignment and access to its
|
||||
keys. It cannot track modifications to mutable values, you need to set
|
||||
:attr:`~.SessionMixin.modified` manually when modifying a list, dict, etc.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# appending to a list is not detected
|
||||
session["numbers"].append(42)
|
||||
# so mark it as modified yourself
|
||||
session.modified = True
|
||||
|
||||
The session is persisted across requests using a cookie. By default the
|
||||
users's browser will clear the cookie when it is closed. Set
|
||||
:attr:`~.SessionMixin.permanent` to ``True`` to persist the cookie for
|
||||
:data:`PERMANENT_SESSION_LIFETIME`.
|
||||
|
||||
|
||||
Session Interface
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
The session interface provides a simple way to replace the session
|
||||
implementation that Flask is using.
|
||||
|
||||
.. currentmodule:: flask.sessions
|
||||
|
||||
.. autoclass:: SessionInterface
|
||||
:members:
|
||||
|
||||
.. autoclass:: SecureCookieSessionInterface
|
||||
:members:
|
||||
|
||||
.. autoclass:: SecureCookieSession
|
||||
:members:
|
||||
|
||||
.. autoclass:: NullSession
|
||||
:members:
|
||||
|
||||
.. autoclass:: SessionMixin
|
||||
:members:
|
||||
|
||||
.. admonition:: Notice
|
||||
|
||||
The :data:`PERMANENT_SESSION_LIFETIME` config can be an integer or ``timedelta``.
|
||||
The :attr:`~flask.Flask.permanent_session_lifetime` attribute is always a
|
||||
``timedelta``.
|
||||
|
||||
|
||||
Test Client
|
||||
-----------
|
||||
|
||||
.. currentmodule:: flask.testing
|
||||
|
||||
.. autoclass:: FlaskClient
|
||||
:members:
|
||||
|
||||
|
||||
Test CLI Runner
|
||||
---------------
|
||||
|
||||
.. currentmodule:: flask.testing
|
||||
|
||||
.. autoclass:: FlaskCliRunner
|
||||
:members:
|
||||
|
||||
|
||||
Application Globals
|
||||
-------------------
|
||||
|
||||
.. currentmodule:: flask
|
||||
|
||||
To share data that is valid for one request only from one function to
|
||||
another, a global variable is not good enough because it would break in
|
||||
threaded environments. Flask provides you with a special object that
|
||||
ensures it is only valid for the active request and that will return
|
||||
different values for each request. In a nutshell: it does the right
|
||||
thing, like it does for :data:`.request` and :data:`.session`.
|
||||
|
||||
.. data:: g
|
||||
|
||||
A proxy to a namespace object used to store data during a single request or
|
||||
app context. An instance of :attr:`.Flask.app_ctx_globals_class`, which
|
||||
defaults to :class:`._AppCtxGlobals`.
|
||||
|
||||
This is a good place to store resources during a request. For example, a
|
||||
:meth:`~.Flask.before_request` function could load a user object from a
|
||||
session id, then set ``g.user`` to be used in the view function.
|
||||
|
||||
This is only available when an :doc:`app context </appcontext>` is active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
|
||||
.. versionchanged:: 0.10
|
||||
Bound to the application context instead of the request context.
|
||||
|
||||
.. autoclass:: flask.ctx._AppCtxGlobals
|
||||
:members:
|
||||
|
||||
|
||||
Useful Functions and Classes
|
||||
----------------------------
|
||||
|
||||
.. data:: current_app
|
||||
|
||||
A proxy to the :class:`.Flask` application handling the current request or
|
||||
other activity.
|
||||
|
||||
This is useful to access the application without needing to import it, or if
|
||||
it can't be imported, such as when using the application factory pattern or
|
||||
in blueprints and extensions.
|
||||
|
||||
This is only available when an :doc:`app context </appcontext>` is active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
|
||||
.. autofunction:: has_request_context
|
||||
|
||||
.. autofunction:: copy_current_request_context
|
||||
|
||||
.. autofunction:: has_app_context
|
||||
|
||||
.. autofunction:: url_for
|
||||
|
||||
.. autofunction:: abort
|
||||
|
||||
.. autofunction:: redirect
|
||||
|
||||
.. autofunction:: make_response
|
||||
|
||||
.. autofunction:: after_this_request
|
||||
|
||||
.. autofunction:: send_file
|
||||
|
||||
.. autofunction:: send_from_directory
|
||||
|
||||
|
||||
Message Flashing
|
||||
----------------
|
||||
|
||||
.. autofunction:: flash
|
||||
|
||||
.. autofunction:: get_flashed_messages
|
||||
|
||||
|
||||
JSON Support
|
||||
------------
|
||||
|
||||
.. module:: flask.json
|
||||
|
||||
Flask uses Python's built-in :mod:`json` module for handling JSON by
|
||||
default. The JSON implementation can be changed by assigning a different
|
||||
provider to :attr:`flask.Flask.json_provider_class` or
|
||||
:attr:`flask.Flask.json`. The functions provided by ``flask.json`` will
|
||||
use methods on ``app.json`` if an app context is active.
|
||||
|
||||
Jinja's ``|tojson`` filter is configured to use the app's JSON provider.
|
||||
The filter marks the output with ``|safe``. Use it to render data inside
|
||||
HTML ``<script>`` tags.
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<script>
|
||||
const names = {{ names|tojson }};
|
||||
renderChart(names, {{ axis_data|tojson }});
|
||||
</script>
|
||||
|
||||
.. autofunction:: jsonify
|
||||
|
||||
.. autofunction:: dumps
|
||||
|
||||
.. autofunction:: dump
|
||||
|
||||
.. autofunction:: loads
|
||||
|
||||
.. autofunction:: load
|
||||
|
||||
.. autoclass:: flask.json.provider.JSONProvider
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
.. autoclass:: flask.json.provider.DefaultJSONProvider
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
.. automodule:: flask.json.tag
|
||||
|
||||
|
||||
Template Rendering
|
||||
------------------
|
||||
|
||||
.. currentmodule:: flask
|
||||
|
||||
.. autofunction:: render_template
|
||||
|
||||
.. autofunction:: render_template_string
|
||||
|
||||
.. autofunction:: stream_template
|
||||
|
||||
.. autofunction:: stream_template_string
|
||||
|
||||
.. autofunction:: get_template_attribute
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. autoclass:: Config
|
||||
:members:
|
||||
|
||||
|
||||
Stream Helpers
|
||||
--------------
|
||||
|
||||
.. autofunction:: stream_with_context
|
||||
|
||||
Useful Internals
|
||||
----------------
|
||||
|
||||
.. autoclass:: flask.ctx.AppContext
|
||||
:members:
|
||||
|
||||
.. data:: flask.globals.app_ctx
|
||||
|
||||
A proxy to the active :class:`.AppContext`.
|
||||
|
||||
This is an internal object that is essential to how Flask handles requests.
|
||||
Accessing this should not be needed in most cases. Most likely you want
|
||||
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` instead.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
|
||||
.. class:: flask.ctx.RequestContext
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Merged with :class:`AppContext`. This alias will be removed in Flask 4.0.
|
||||
|
||||
.. data:: flask.globals.request_ctx
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Merged with :data:`.app_ctx`. This alias will be removed in Flask 4.0.
|
||||
|
||||
.. autoclass:: flask.blueprints.BlueprintSetupState
|
||||
:members:
|
||||
|
||||
.. _core-signals-list:
|
||||
|
||||
Signals
|
||||
-------
|
||||
|
||||
Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introduction.
|
||||
|
||||
.. _blinker: https://blinker.readthedocs.io/
|
||||
|
||||
.. data:: template_rendered
|
||||
|
||||
This signal is sent when a template was successfully rendered. The
|
||||
signal is invoked with the instance of the template as `template`
|
||||
and the context as dictionary (named `context`).
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_template_renders(sender, template, context, **extra):
|
||||
sender.logger.debug('Rendering template "%s" with context %s',
|
||||
template.name or 'string template',
|
||||
context)
|
||||
|
||||
from flask import template_rendered
|
||||
template_rendered.connect(log_template_renders, app)
|
||||
|
||||
.. data:: flask.before_render_template
|
||||
:noindex:
|
||||
|
||||
This signal is sent before template rendering process. The
|
||||
signal is invoked with the instance of the template as `template`
|
||||
and the context as dictionary (named `context`).
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_template_renders(sender, template, context, **extra):
|
||||
sender.logger.debug('Rendering template "%s" with context %s',
|
||||
template.name or 'string template',
|
||||
context)
|
||||
|
||||
from flask import before_render_template
|
||||
before_render_template.connect(log_template_renders, app)
|
||||
|
||||
.. data:: request_started
|
||||
|
||||
This signal is sent when the request context is set up, before
|
||||
any request processing happens. Because the request context is already
|
||||
bound, the subscriber can access the request with the standard global
|
||||
proxies such as :class:`~flask.request`.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_request(sender, **extra):
|
||||
sender.logger.debug('Request context is set up')
|
||||
|
||||
from flask import request_started
|
||||
request_started.connect(log_request, app)
|
||||
|
||||
.. data:: request_finished
|
||||
|
||||
This signal is sent right before the response is sent to the client.
|
||||
It is passed the response to be sent named `response`.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_response(sender, response, **extra):
|
||||
sender.logger.debug('Request context is about to close down. '
|
||||
'Response: %s', response)
|
||||
|
||||
from flask import request_finished
|
||||
request_finished.connect(log_response, app)
|
||||
|
||||
.. data:: got_request_exception
|
||||
|
||||
This signal is sent when an unhandled exception happens during
|
||||
request processing, including when debugging. The exception is
|
||||
passed to the subscriber as ``exception``.
|
||||
|
||||
This signal is not sent for
|
||||
:exc:`~werkzeug.exceptions.HTTPException`, or other exceptions that
|
||||
have error handlers registered, unless the exception was raised from
|
||||
an error handler.
|
||||
|
||||
This example shows how to do some extra logging if a theoretical
|
||||
``SecurityException`` was raised:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import got_request_exception
|
||||
|
||||
def log_security_exception(sender, exception, **extra):
|
||||
if not isinstance(exception, SecurityException):
|
||||
return
|
||||
|
||||
security_logger.exception(
|
||||
f"SecurityException at {request.url!r}",
|
||||
exc_info=exception,
|
||||
)
|
||||
|
||||
got_request_exception.connect(log_security_exception, app)
|
||||
|
||||
.. data:: request_tearing_down
|
||||
|
||||
This signal is sent when the request is tearing down. This is always
|
||||
called, even if an exception is caused. Currently functions listening
|
||||
to this signal are called after the regular teardown handlers, but this
|
||||
is not something you can rely on.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def close_db_connection(sender, **extra):
|
||||
session.close()
|
||||
|
||||
from flask import request_tearing_down
|
||||
request_tearing_down.connect(close_db_connection, app)
|
||||
|
||||
As of Flask 0.9, this will also be passed an `exc` keyword argument
|
||||
that has a reference to the exception that caused the teardown if
|
||||
there was one.
|
||||
|
||||
.. data:: appcontext_tearing_down
|
||||
|
||||
This signal is sent when the app context is tearing down. This is always
|
||||
called, even if an exception is caused. Currently functions listening
|
||||
to this signal are called after the regular teardown handlers, but this
|
||||
is not something you can rely on.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def close_db_connection(sender, **extra):
|
||||
session.close()
|
||||
|
||||
from flask import appcontext_tearing_down
|
||||
appcontext_tearing_down.connect(close_db_connection, app)
|
||||
|
||||
This will also be passed an `exc` keyword argument that has a reference
|
||||
to the exception that caused the teardown if there was one.
|
||||
|
||||
.. data:: appcontext_pushed
|
||||
|
||||
This signal is sent when an application context is pushed. The sender
|
||||
is the application. This is usually useful for unittests in order to
|
||||
temporarily hook in information. For instance it can be used to
|
||||
set a resource early onto the `g` object.
|
||||
|
||||
Example usage::
|
||||
|
||||
from contextlib import contextmanager
|
||||
from flask import appcontext_pushed
|
||||
|
||||
@contextmanager
|
||||
def user_set(app, user):
|
||||
def handler(sender, **kwargs):
|
||||
g.user = user
|
||||
with appcontext_pushed.connected_to(handler, app):
|
||||
yield
|
||||
|
||||
And in the testcode::
|
||||
|
||||
def test_user_me(self):
|
||||
with user_set(app, 'john'):
|
||||
c = app.test_client()
|
||||
resp = c.get('/users/me')
|
||||
assert resp.data == 'username=john'
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
.. data:: appcontext_popped
|
||||
|
||||
This signal is sent when an application context is popped. The sender
|
||||
is the application. This usually falls in line with the
|
||||
:data:`appcontext_tearing_down` signal.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
.. data:: message_flashed
|
||||
|
||||
This signal is sent when the application is flashing a message. The
|
||||
messages is sent as `message` keyword argument and the category as
|
||||
`category`.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
recorded = []
|
||||
def record(sender, message, category, **extra):
|
||||
recorded.append((message, category))
|
||||
|
||||
from flask import message_flashed
|
||||
message_flashed.connect(record, app)
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
|
||||
Class-Based Views
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
.. currentmodule:: None
|
||||
|
||||
.. autoclass:: flask.views.View
|
||||
:members:
|
||||
|
||||
.. autoclass:: flask.views.MethodView
|
||||
:members:
|
||||
|
||||
.. _url-route-registrations:
|
||||
|
||||
URL Route Registrations
|
||||
-----------------------
|
||||
|
||||
Generally there are three ways to define rules for the routing system:
|
||||
|
||||
1. You can use the :meth:`flask.Flask.route` decorator.
|
||||
2. You can use the :meth:`flask.Flask.add_url_rule` function.
|
||||
3. You can directly access the underlying Werkzeug routing system
|
||||
which is exposed as :attr:`flask.Flask.url_map`.
|
||||
|
||||
Variable parts in the route can be specified with angular brackets
|
||||
(``/user/<username>``). By default a variable part in the URL accepts any
|
||||
string without a slash however a different converter can be specified as
|
||||
well by using ``<converter:name>``.
|
||||
|
||||
Variable parts are passed to the view function as keyword arguments.
|
||||
|
||||
The following converters are available:
|
||||
|
||||
=========== ===============================================
|
||||
`string` accepts any text without a slash (the default)
|
||||
`int` accepts integers
|
||||
`float` like `int` but for floating point values
|
||||
`path` like the default but also accepts slashes
|
||||
`any` matches one of the items provided
|
||||
`uuid` accepts UUID strings
|
||||
=========== ===============================================
|
||||
|
||||
Custom converters can be defined using :attr:`flask.Flask.url_map`.
|
||||
|
||||
Here are some examples::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
pass
|
||||
|
||||
@app.route('/<username>')
|
||||
def show_user(username):
|
||||
pass
|
||||
|
||||
@app.route('/post/<int:post_id>')
|
||||
def show_post(post_id):
|
||||
pass
|
||||
|
||||
An important detail to keep in mind is how Flask deals with trailing
|
||||
slashes. The idea is to keep each URL unique so the following rules
|
||||
apply:
|
||||
|
||||
1. If a rule ends with a slash and is requested without a slash by the
|
||||
user, the user is automatically redirected to the same page with a
|
||||
trailing slash attached.
|
||||
2. If a rule does not end with a trailing slash and the user requests the
|
||||
page with a trailing slash, a 404 not found is raised.
|
||||
|
||||
This is consistent with how web servers deal with static files. This
|
||||
also makes it possible to use relative link targets safely.
|
||||
|
||||
You can also define multiple rules for the same function. They have to be
|
||||
unique however. Defaults can also be specified. Here for example is a
|
||||
definition for a URL that accepts an optional page::
|
||||
|
||||
@app.route('/users/', defaults={'page': 1})
|
||||
@app.route('/users/page/<int:page>')
|
||||
def show_users(page):
|
||||
pass
|
||||
|
||||
This specifies that ``/users/`` will be the URL for page one and
|
||||
``/users/page/N`` will be the URL for page ``N``.
|
||||
|
||||
If a URL contains a default value, it will be redirected to its simpler
|
||||
form with a 301 redirect. In the above example, ``/users/page/1`` will
|
||||
be redirected to ``/users/``. If your route handles ``GET`` and ``POST``
|
||||
requests, make sure the default route only handles ``GET``, as redirects
|
||||
can't preserve form data. ::
|
||||
|
||||
@app.route('/region/', defaults={'id': 1})
|
||||
@app.route('/region/<int:id>', methods=['GET', 'POST'])
|
||||
def region(id):
|
||||
pass
|
||||
|
||||
Here are the parameters that :meth:`~flask.Flask.route` and
|
||||
:meth:`~flask.Flask.add_url_rule` accept. The only difference is that
|
||||
with the route parameter the view function is defined with the decorator
|
||||
instead of the `view_func` parameter.
|
||||
|
||||
=============== ==========================================================
|
||||
`rule` the URL rule as string
|
||||
`endpoint` the endpoint for the registered URL rule. Flask itself
|
||||
assumes that the name of the view function is the name
|
||||
of the endpoint if not explicitly stated.
|
||||
`view_func` the function to call when serving a request to the
|
||||
provided endpoint. If this is not provided one can
|
||||
specify the function later by storing it in the
|
||||
:attr:`~flask.Flask.view_functions` dictionary with the
|
||||
endpoint as key.
|
||||
`defaults` A dictionary with defaults for this rule. See the
|
||||
example above for how defaults work.
|
||||
`subdomain` specifies the rule for the subdomain in case subdomain
|
||||
matching is in use. If not specified the default
|
||||
subdomain is assumed.
|
||||
`**options` the options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object. A change to
|
||||
Werkzeug is handling of method options. methods is a list
|
||||
of methods this rule should be limited to (``GET``, ``POST``
|
||||
etc.). By default a rule just listens for ``GET`` (and
|
||||
implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is
|
||||
implicitly added and handled by the standard request
|
||||
handling. They have to be specified as keyword arguments.
|
||||
=============== ==========================================================
|
||||
|
||||
|
||||
View Function Options
|
||||
---------------------
|
||||
|
||||
For internal usage the view functions can have some attributes attached to
|
||||
customize behavior the view function would normally not have control over.
|
||||
The following attributes can be provided optionally to either override
|
||||
some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior:
|
||||
|
||||
- `__name__`: The name of a function is by default used as endpoint. If
|
||||
endpoint is provided explicitly this value is used. Additionally this
|
||||
will be prefixed with the name of the blueprint by default which
|
||||
cannot be customized from the function itself.
|
||||
|
||||
- `methods`: If methods are not provided when the URL rule is added,
|
||||
Flask will look on the view function object itself if a `methods`
|
||||
attribute exists. If it does, it will pull the information for the
|
||||
methods from there.
|
||||
|
||||
- `provide_automatic_options`: if this attribute is set Flask will
|
||||
either force enable or disable the automatic implementation of the
|
||||
HTTP ``OPTIONS`` response. This can be useful when working with
|
||||
decorators that want to customize the ``OPTIONS`` response on a per-view
|
||||
basis.
|
||||
|
||||
- `required_methods`: if this attribute is set, Flask will always add
|
||||
these methods when registering a URL rule even if the methods were
|
||||
explicitly overridden in the ``route()`` call.
|
||||
|
||||
Full example::
|
||||
|
||||
def index():
|
||||
if request.method == 'OPTIONS':
|
||||
# custom options handling here
|
||||
...
|
||||
return 'Hello World!'
|
||||
index.provide_automatic_options = False
|
||||
index.methods = ['GET', 'OPTIONS']
|
||||
|
||||
app.add_url_rule('/', index)
|
||||
|
||||
.. versionadded:: 0.8
|
||||
The `provide_automatic_options` functionality was added.
|
||||
|
||||
Command Line Interface
|
||||
----------------------
|
||||
|
||||
.. currentmodule:: flask.cli
|
||||
|
||||
.. autoclass:: FlaskGroup
|
||||
:members:
|
||||
|
||||
.. autoclass:: AppGroup
|
||||
:members:
|
||||
|
||||
.. autoclass:: ScriptInfo
|
||||
:members:
|
||||
|
||||
.. autofunction:: load_dotenv
|
||||
|
||||
.. autofunction:: with_appcontext
|
||||
|
||||
.. autofunction:: pass_script_info
|
||||
|
||||
Marks a function so that an instance of :class:`ScriptInfo` is passed
|
||||
as first argument to the click callback.
|
||||
|
||||
.. autodata:: run_command
|
||||
|
||||
.. autodata:: shell_command
|
||||
@ -0,0 +1,186 @@
|
||||
The App and Request Context
|
||||
===========================
|
||||
|
||||
The context keeps track of data and objects during a request, CLI command, or
|
||||
other activity. Rather than passing this data around to every function, the
|
||||
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` proxies
|
||||
are accessed instead.
|
||||
|
||||
When handling a request, the context is referred to as the "request context"
|
||||
because it contains request data in addition to application data. Otherwise,
|
||||
such as during a CLI command, it is referred to as the "app context". During an
|
||||
app context, :data:`.current_app` and :data:`.g` are available, while during a
|
||||
request context :data:`.request` and :data:`.session` are also available.
|
||||
|
||||
|
||||
Purpose of the Context
|
||||
----------------------
|
||||
|
||||
The context and proxies help solve two development issues: circular imports, and
|
||||
passing around global data during a request.
|
||||
|
||||
The :class:`.Flask` application object has attributes, such as
|
||||
:attr:`~.Flask.config`, that are useful to access within views and other
|
||||
functions. However, importing the ``app`` instance within the modules in your
|
||||
project is prone to circular import issues. When using the
|
||||
:doc:`app factory pattern </patterns/appfactories>` or writing reusable
|
||||
:doc:`blueprints </blueprints>` or :doc:`extensions </extensions>` there won't
|
||||
be an ``app`` instance to import at all.
|
||||
|
||||
When the application handles a request, it creates a :class:`.Request` object.
|
||||
Because a *worker* handles only one request at a time, the request data can be
|
||||
considered global to that worker during that request. Passing it as an argument
|
||||
through every function during the request becomes verbose and redundant.
|
||||
|
||||
Flask solves these issues with the *active context* pattern. Rather than
|
||||
importing an ``app`` directly, or having to pass it and the request through to
|
||||
every single function, you import and access the proxies, which point to the
|
||||
currently active application and request data. This is sometimes referred to
|
||||
as "context local" data.
|
||||
|
||||
|
||||
Context During Setup
|
||||
--------------------
|
||||
|
||||
If you try to access :data:`.current_app`, :data:`.g`, or anything that uses it,
|
||||
outside an app context, you'll get this error message:
|
||||
|
||||
.. code-block:: pytb
|
||||
|
||||
RuntimeError: Working outside of application context.
|
||||
|
||||
Attempted to use functionality that expected a current application to be
|
||||
set. To solve this, set up an app context using 'with app.app_context()'.
|
||||
See the documentation on app context for more information.
|
||||
|
||||
If you see that error while configuring your application, such as when
|
||||
initializing an extension, you can push a context manually since you have direct
|
||||
access to the ``app``. Use :meth:`.Flask.app_context` in a ``with`` block.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
|
||||
with app.app_context():
|
||||
init_db()
|
||||
|
||||
return app
|
||||
|
||||
If you see that error somewhere else in your code not related to setting up the
|
||||
application, it most likely indicates that you should move that code into a view
|
||||
function or CLI command.
|
||||
|
||||
|
||||
Context During Testing
|
||||
----------------------
|
||||
|
||||
See :doc:`/testing` for detailed information about managing the context during
|
||||
tests.
|
||||
|
||||
If you try to access :data:`.request`, :data:`.session`, or anything that uses
|
||||
it, outside a request context, you'll get this error message:
|
||||
|
||||
.. code-block:: pytb
|
||||
|
||||
RuntimeError: Working outside of request context.
|
||||
|
||||
Attempted to use functionality that expected an active HTTP request. See the
|
||||
documentation on request context for more information.
|
||||
|
||||
This will probably only happen during tests. If you see that error somewhere
|
||||
else in your code not related to testing, it most likely indicates that you
|
||||
should move that code into a view function.
|
||||
|
||||
The primary way to solve this is to use :meth:`.Flask.test_client` to simulate
|
||||
a full request.
|
||||
|
||||
If you only want to unit test one function, rather than a full request, use
|
||||
:meth:`.Flask.test_request_context` in a ``with`` block.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def generate_report(year):
|
||||
format = request.args.get("format")
|
||||
...
|
||||
|
||||
with app.test_request_context(
|
||||
"/make_report/2017", query_string={"format": "short"}
|
||||
):
|
||||
generate_report()
|
||||
|
||||
|
||||
.. _context-visibility:
|
||||
|
||||
Visibility of the Context
|
||||
-------------------------
|
||||
|
||||
The context will have the same lifetime as an activity, such as a request, CLI
|
||||
command, or ``with`` block. Various callbacks and signals registered with the
|
||||
app will be run during the context.
|
||||
|
||||
When a Flask application handles a request, it pushes a requet context
|
||||
to set the active application and request data. When it handles a CLI command,
|
||||
it pushes an app context to set the active application. When the activity ends,
|
||||
it pops that context. Proxy objects like :data:`.request`, :data:`.session`,
|
||||
:data:`.g`, and :data:`.current_app`, are accessible while the context is pushed
|
||||
and active, and are not accessible after the context is popped.
|
||||
|
||||
The context is unique to each thread (or other worker type). The proxies cannot
|
||||
be passed to another worker, which has a different context space and will not
|
||||
know about the active context in the parent's space.
|
||||
|
||||
Besides being scoped to each worker, the proxy object has a separate type and
|
||||
identity than the proxied real object. In some cases you'll need access to the
|
||||
real object, rather than the proxy. Use the
|
||||
:meth:`~.LocalProxy._get_current_object` method in those cases.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = current_app._get_current_object()
|
||||
my_signal.send(app)
|
||||
|
||||
|
||||
Lifecycle of the Context
|
||||
------------------------
|
||||
|
||||
Flask dispatches a request in multiple stages which can affect the request,
|
||||
response, and how errors are handled. See :doc:`/lifecycle` for a list of all
|
||||
the steps, callbacks, and signals during each request. The following are the
|
||||
steps directly related to the context.
|
||||
|
||||
- The app context is pushed, the proxies are available.
|
||||
- The :data:`.appcontext_pushed` signal is sent.
|
||||
- The request is dispatched.
|
||||
- Any :meth:`.Flask.teardown_request` decorated functions are called.
|
||||
- The :data:`.request_tearing_down` signal is sent.
|
||||
- Any :meth:`.Flask.teardown_appcontext` decorated functions are called.
|
||||
- The :data:`.appcontext_tearing_down` signal is sent.
|
||||
- The app context is popped, the proxies are no longer available.
|
||||
- The :data:`.appcontext_popped` signal is sent.
|
||||
|
||||
The teardown callbacks are called by the context when it is popped. They are
|
||||
called even if there is an unhandled exception during dispatch. They may be
|
||||
called multiple times in some test scenarios. This means there is no guarantee
|
||||
that any other parts of the request dispatch have run. Be sure to write these
|
||||
functions in a way that does not depend on other callbacks and will not fail.
|
||||
|
||||
|
||||
How the Context Works
|
||||
---------------------
|
||||
|
||||
Context locals are implemented using Python's :mod:`contextvars` and Werkzeug's
|
||||
:class:`~werkzeug.local.LocalProxy`. Python's contextvars are a low level
|
||||
structure to manage data local to a thread or coroutine. ``LocalProxy`` wraps
|
||||
the contextvar so that access to any attributes and methods is forwarded to the
|
||||
object stored in the contextvar.
|
||||
|
||||
The context is tracked like a stack, with the active context at the top of the
|
||||
stack. Flask manages pushing and popping contexts during requests, CLI commands,
|
||||
testing, ``with`` blocks, etc. The proxies access attributes on the active
|
||||
context.
|
||||
|
||||
Because it is a stack, other contexts may be pushed to change the proxies during
|
||||
an already active context. This is not a common pattern, but can be used in
|
||||
advanced use cases. For example, a Flask application can be used as WSGI
|
||||
middleware, calling another wrapped Flask app from a view.
|
||||
@ -0,0 +1,125 @@
|
||||
.. _async_await:
|
||||
|
||||
Using ``async`` and ``await``
|
||||
=============================
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Routes, error handlers, before request, after request, and teardown
|
||||
functions can all be coroutine functions if Flask is installed with the
|
||||
``async`` extra (``pip install flask[async]``). This allows views to be
|
||||
defined with ``async def`` and use ``await``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route("/get-data")
|
||||
async def get_data():
|
||||
data = await async_db_query(...)
|
||||
return jsonify(data)
|
||||
|
||||
Pluggable class-based views also support handlers that are implemented as
|
||||
coroutines. This applies to the :meth:`~flask.views.View.dispatch_request`
|
||||
method in views that inherit from the :class:`flask.views.View` class, as
|
||||
well as all the HTTP method handlers in views that inherit from the
|
||||
:class:`flask.views.MethodView` class.
|
||||
|
||||
.. admonition:: Using ``async`` with greenlet
|
||||
|
||||
When using gevent or eventlet to serve an application or patch the
|
||||
runtime, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is
|
||||
required.
|
||||
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Async functions require an event loop to run. Flask, as a WSGI
|
||||
application, uses one worker to handle one request/response cycle.
|
||||
When a request comes in to an async view, Flask will start an event loop
|
||||
in a thread, run the view function there, then return the result.
|
||||
|
||||
Each request still ties up one worker, even for async views. The upside
|
||||
is that you can run async code within a view, for example to make
|
||||
multiple concurrent database queries, HTTP requests to an external API,
|
||||
etc. However, the number of requests your application can handle at one
|
||||
time will remain the same.
|
||||
|
||||
**Async is not inherently faster than sync code.** Async is beneficial
|
||||
when performing concurrent IO-bound tasks, but will probably not improve
|
||||
CPU-bound tasks. Traditional Flask views will still be appropriate for
|
||||
most use cases, but Flask's async support enables writing and using
|
||||
code that wasn't possible natively before.
|
||||
|
||||
|
||||
Background tasks
|
||||
----------------
|
||||
|
||||
Async functions will run in an event loop until they complete, at
|
||||
which stage the event loop will stop. This means any additional
|
||||
spawned tasks that haven't completed when the async function completes
|
||||
will be cancelled. Therefore you cannot spawn background tasks, for
|
||||
example via ``asyncio.create_task``.
|
||||
|
||||
If you wish to use background tasks it is best to use a task queue to
|
||||
trigger background work, rather than spawn tasks in a view
|
||||
function. With that in mind you can spawn asyncio tasks by serving
|
||||
Flask with an ASGI server and utilising the asgiref WsgiToAsgi adapter
|
||||
as described in :doc:`deploying/asgi`. This works as the adapter creates
|
||||
an event loop that runs continually.
|
||||
|
||||
|
||||
When to use Quart instead
|
||||
-------------------------
|
||||
|
||||
Flask's async support is less performant than async-first frameworks due
|
||||
to the way it is implemented. If you have a mainly async codebase it
|
||||
would make sense to consider `Quart`_. Quart is a reimplementation of
|
||||
Flask based on the `ASGI`_ standard instead of WSGI. This allows it to
|
||||
handle many concurrent requests, long running requests, and websockets
|
||||
without requiring multiple worker processes or threads.
|
||||
|
||||
It has also already been possible to run Flask with Gevent or Eventlet
|
||||
to get many of the benefits of async request handling. These libraries
|
||||
patch low-level Python functions to accomplish this, whereas ``async``/
|
||||
``await`` and ASGI use standard, modern Python capabilities. Deciding
|
||||
whether you should use Flask, Quart, or something else is ultimately up
|
||||
to understanding the specific needs of your project.
|
||||
|
||||
.. _Quart: https://github.com/pallets/quart
|
||||
.. _ASGI: https://asgi.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
Extensions
|
||||
----------
|
||||
|
||||
Flask extensions predating Flask's async support do not expect async views.
|
||||
If they provide decorators to add functionality to views, those will probably
|
||||
not work with async views because they will not await the function or be
|
||||
awaitable. Other functions they provide will not be awaitable either and
|
||||
will probably be blocking if called within an async view.
|
||||
|
||||
Extension authors can support async functions by utilising the
|
||||
:meth:`flask.Flask.ensure_sync` method. For example, if the extension
|
||||
provides a view function decorator add ``ensure_sync`` before calling
|
||||
the decorated function,
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def extension(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
... # Extension logic
|
||||
return current_app.ensure_sync(func)(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
Check the changelog of the extension you want to use to see if they've
|
||||
implemented async support, or make a feature request or PR to them.
|
||||
|
||||
|
||||
Other event loops
|
||||
-----------------
|
||||
|
||||
At the moment Flask only supports :mod:`asyncio`. It's possible to
|
||||
override :meth:`flask.Flask.ensure_sync` to change how async functions
|
||||
are wrapped to use a different library.
|
||||
@ -0,0 +1,315 @@
|
||||
Modular Applications with Blueprints
|
||||
====================================
|
||||
|
||||
.. currentmodule:: flask
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
Flask uses a concept of *blueprints* for making application components and
|
||||
supporting common patterns within an application or across applications.
|
||||
Blueprints can greatly simplify how large applications work and provide a
|
||||
central means for Flask extensions to register operations on applications.
|
||||
A :class:`Blueprint` object works similarly to a :class:`Flask`
|
||||
application object, but it is not actually an application. Rather it is a
|
||||
*blueprint* of how to construct or extend an application.
|
||||
|
||||
Why Blueprints?
|
||||
---------------
|
||||
|
||||
Blueprints in Flask are intended for these cases:
|
||||
|
||||
* Factor an application into a set of blueprints. This is ideal for
|
||||
larger applications; a project could instantiate an application object,
|
||||
initialize several extensions, and register a collection of blueprints.
|
||||
* Register a blueprint on an application at a URL prefix and/or subdomain.
|
||||
Parameters in the URL prefix/subdomain become common view arguments
|
||||
(with defaults) across all view functions in the blueprint.
|
||||
* Register a blueprint multiple times on an application with different URL
|
||||
rules.
|
||||
* Provide template filters, static files, templates, and other utilities
|
||||
through blueprints. A blueprint does not have to implement applications
|
||||
or view functions.
|
||||
* Register a blueprint on an application for any of these cases when
|
||||
initializing a Flask extension.
|
||||
|
||||
A blueprint in Flask is not a pluggable app because it is not actually an
|
||||
application -- it's a set of operations which can be registered on an
|
||||
application, even multiple times. Why not have multiple application
|
||||
objects? You can do that (see :doc:`/patterns/appdispatch`), but your
|
||||
applications will have separate configs and will be managed at the WSGI
|
||||
layer.
|
||||
|
||||
Blueprints instead provide separation at the Flask level, share
|
||||
application config, and can change an application object as necessary with
|
||||
being registered. The downside is that you cannot unregister a blueprint
|
||||
once an application was created without having to destroy the whole
|
||||
application object.
|
||||
|
||||
The Concept of Blueprints
|
||||
-------------------------
|
||||
|
||||
The basic concept of blueprints is that they record operations to execute
|
||||
when registered on an application. Flask associates view functions with
|
||||
blueprints when dispatching requests and generating URLs from one endpoint
|
||||
to another.
|
||||
|
||||
My First Blueprint
|
||||
------------------
|
||||
|
||||
This is what a very basic blueprint looks like. In this case we want to
|
||||
implement a blueprint that does simple rendering of static templates::
|
||||
|
||||
from flask import Blueprint, render_template, abort
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
simple_page = Blueprint('simple_page', __name__,
|
||||
template_folder='templates')
|
||||
|
||||
@simple_page.route('/', defaults={'page': 'index'})
|
||||
@simple_page.route('/<page>')
|
||||
def show(page):
|
||||
try:
|
||||
return render_template(f'pages/{page}.html')
|
||||
except TemplateNotFound:
|
||||
abort(404)
|
||||
|
||||
When you bind a function with the help of the ``@simple_page.route``
|
||||
decorator, the blueprint will record the intention of registering the
|
||||
function ``show`` on the application when it's later registered.
|
||||
Additionally it will prefix the endpoint of the function with the
|
||||
name of the blueprint which was given to the :class:`Blueprint`
|
||||
constructor (in this case also ``simple_page``). The blueprint's name
|
||||
does not modify the URL, only the endpoint.
|
||||
|
||||
Registering Blueprints
|
||||
----------------------
|
||||
|
||||
So how do you register that blueprint? Like this::
|
||||
|
||||
from flask import Flask
|
||||
from yourapplication.simple_page import simple_page
|
||||
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(simple_page)
|
||||
|
||||
If you check the rules registered on the application, you will find
|
||||
these::
|
||||
|
||||
>>> app.url_map
|
||||
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||
<Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
|
||||
<Rule '/' (HEAD, OPTIONS, GET) -> simple_page.show>])
|
||||
|
||||
The first one is obviously from the application itself for the static
|
||||
files. The other two are for the `show` function of the ``simple_page``
|
||||
blueprint. As you can see, they are also prefixed with the name of the
|
||||
blueprint and separated by a dot (``.``).
|
||||
|
||||
Blueprints however can also be mounted at different locations::
|
||||
|
||||
app.register_blueprint(simple_page, url_prefix='/pages')
|
||||
|
||||
And sure enough, these are the generated rules::
|
||||
|
||||
>>> app.url_map
|
||||
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||
<Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
|
||||
<Rule '/pages/' (HEAD, OPTIONS, GET) -> simple_page.show>])
|
||||
|
||||
On top of that you can register blueprints multiple times though not every
|
||||
blueprint might respond properly to that. In fact it depends on how the
|
||||
blueprint is implemented if it can be mounted more than once.
|
||||
|
||||
Nesting Blueprints
|
||||
------------------
|
||||
|
||||
It is possible to register a blueprint on another blueprint.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
parent = Blueprint('parent', __name__, url_prefix='/parent')
|
||||
child = Blueprint('child', __name__, url_prefix='/child')
|
||||
parent.register_blueprint(child)
|
||||
app.register_blueprint(parent)
|
||||
|
||||
The child blueprint will gain the parent's name as a prefix to its
|
||||
name, and child URLs will be prefixed with the parent's URL prefix.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url_for('parent.child.create')
|
||||
/parent/child/create
|
||||
|
||||
In addition a child blueprint's will gain their parent's subdomain,
|
||||
with their subdomain as prefix if present i.e.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
parent = Blueprint('parent', __name__, subdomain='parent')
|
||||
child = Blueprint('child', __name__, subdomain='child')
|
||||
parent.register_blueprint(child)
|
||||
app.register_blueprint(parent)
|
||||
|
||||
url_for('parent.child.create', _external=True)
|
||||
"child.parent.domain.tld"
|
||||
|
||||
Blueprint-specific before request functions, etc. registered with the
|
||||
parent will trigger for the child. If a child does not have an error
|
||||
handler that can handle a given exception, the parent's will be tried.
|
||||
|
||||
|
||||
Blueprint Resources
|
||||
-------------------
|
||||
|
||||
Blueprints can provide resources as well. Sometimes you might want to
|
||||
introduce a blueprint only for the resources it provides.
|
||||
|
||||
Blueprint Resource Folder
|
||||
`````````````````````````
|
||||
|
||||
Like for regular applications, blueprints are considered to be contained
|
||||
in a folder. While multiple blueprints can originate from the same folder,
|
||||
it does not have to be the case and it's usually not recommended.
|
||||
|
||||
The folder is inferred from the second argument to :class:`Blueprint` which
|
||||
is usually `__name__`. This argument specifies what logical Python
|
||||
module or package corresponds to the blueprint. If it points to an actual
|
||||
Python package that package (which is a folder on the filesystem) is the
|
||||
resource folder. If it's a module, the package the module is contained in
|
||||
will be the resource folder. You can access the
|
||||
:attr:`Blueprint.root_path` property to see what the resource folder is::
|
||||
|
||||
>>> simple_page.root_path
|
||||
'/Users/username/TestProject/yourapplication'
|
||||
|
||||
To quickly open sources from this folder you can use the
|
||||
:meth:`~Blueprint.open_resource` function::
|
||||
|
||||
with simple_page.open_resource('static/style.css') as f:
|
||||
code = f.read()
|
||||
|
||||
Static Files
|
||||
````````````
|
||||
|
||||
A blueprint can expose a folder with static files by providing the path
|
||||
to the folder on the filesystem with the ``static_folder`` argument.
|
||||
It is either an absolute path or relative to the blueprint's location::
|
||||
|
||||
admin = Blueprint('admin', __name__, static_folder='static')
|
||||
|
||||
By default the rightmost part of the path is where it is exposed on the
|
||||
web. This can be changed with the ``static_url_path`` argument. Because the
|
||||
folder is called ``static`` here it will be available at the
|
||||
``url_prefix`` of the blueprint + ``/static``. If the blueprint
|
||||
has the prefix ``/admin``, the static URL will be ``/admin/static``.
|
||||
|
||||
The endpoint is named ``blueprint_name.static``. You can generate URLs
|
||||
to it with :func:`url_for` like you would with the static folder of the
|
||||
application::
|
||||
|
||||
url_for('admin.static', filename='style.css')
|
||||
|
||||
However, if the blueprint does not have a ``url_prefix``, it is not
|
||||
possible to access the blueprint's static folder. This is because the
|
||||
URL would be ``/static`` in this case, and the application's ``/static``
|
||||
route takes precedence. Unlike template folders, blueprint static
|
||||
folders are not searched if the file does not exist in the application
|
||||
static folder.
|
||||
|
||||
Templates
|
||||
`````````
|
||||
|
||||
If you want the blueprint to expose templates you can do that by providing
|
||||
the `template_folder` parameter to the :class:`Blueprint` constructor::
|
||||
|
||||
admin = Blueprint('admin', __name__, template_folder='templates')
|
||||
|
||||
For static files, the path can be absolute or relative to the blueprint
|
||||
resource folder.
|
||||
|
||||
The template folder is added to the search path of templates but with a lower
|
||||
priority than the actual application's template folder. That way you can
|
||||
easily override templates that a blueprint provides in the actual application.
|
||||
This also means that if you don't want a blueprint template to be accidentally
|
||||
overridden, make sure that no other blueprint or actual application template
|
||||
has the same relative path. When multiple blueprints provide the same relative
|
||||
template path the first blueprint registered takes precedence over the others.
|
||||
|
||||
|
||||
So if you have a blueprint in the folder ``yourapplication/admin`` and you
|
||||
want to render the template ``'admin/index.html'`` and you have provided
|
||||
``templates`` as a `template_folder` you will have to create a file like
|
||||
this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
|
||||
for the extra ``admin`` folder is to avoid getting our template overridden
|
||||
by a template named ``index.html`` in the actual application template
|
||||
folder.
|
||||
|
||||
To further reiterate this: if you have a blueprint named ``admin`` and you
|
||||
want to render a template called :file:`index.html` which is specific to this
|
||||
blueprint, the best idea is to lay out your templates like this::
|
||||
|
||||
yourpackage/
|
||||
blueprints/
|
||||
admin/
|
||||
templates/
|
||||
admin/
|
||||
index.html
|
||||
__init__.py
|
||||
|
||||
And then when you want to render the template, use :file:`admin/index.html` as
|
||||
the name to look up the template by. If you encounter problems loading
|
||||
the correct templates enable the ``EXPLAIN_TEMPLATE_LOADING`` config
|
||||
variable which will instruct Flask to print out the steps it goes through
|
||||
to locate templates on every ``render_template`` call.
|
||||
|
||||
Building URLs
|
||||
-------------
|
||||
|
||||
If you want to link from one page to another you can use the
|
||||
:func:`url_for` function just like you normally would do just that you
|
||||
prefix the URL endpoint with the name of the blueprint and a dot (``.``)::
|
||||
|
||||
url_for('admin.index')
|
||||
|
||||
Additionally if you are in a view function of a blueprint or a rendered
|
||||
template and you want to link to another endpoint of the same blueprint,
|
||||
you can use relative redirects by prefixing the endpoint with a dot only::
|
||||
|
||||
url_for('.index')
|
||||
|
||||
This will link to ``admin.index`` for instance in case the current request
|
||||
was dispatched to any other admin blueprint endpoint.
|
||||
|
||||
|
||||
Blueprint Error Handlers
|
||||
------------------------
|
||||
|
||||
Blueprints support the ``errorhandler`` decorator just like the :class:`Flask`
|
||||
application object, so it is easy to make Blueprint-specific custom error
|
||||
pages.
|
||||
|
||||
Here is an example for a "404 Page Not Found" exception::
|
||||
|
||||
@simple_page.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template('pages/404.html')
|
||||
|
||||
Most errorhandlers will simply work as expected; however, there is a caveat
|
||||
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
|
||||
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
|
||||
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
|
||||
access. This is because the blueprint does not "own" a certain URL space, so
|
||||
the application instance has no way of knowing which blueprint error handler it
|
||||
should run if given an invalid URL. If you would like to execute different
|
||||
handling strategies for these errors based on URL prefixes, they may be defined
|
||||
at the application level using the ``request`` proxy object::
|
||||
|
||||
@app.errorhandler(404)
|
||||
@app.errorhandler(405)
|
||||
def _handle_api_error(ex):
|
||||
if request.path.startswith('/api/'):
|
||||
return jsonify(error=str(ex)), ex.code
|
||||
else:
|
||||
return ex
|
||||
|
||||
See :doc:`/errorhandling`.
|
||||
@ -0,0 +1,4 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
.. include:: ../CHANGES.rst
|
||||
@ -0,0 +1,556 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Command Line Interface
|
||||
======================
|
||||
|
||||
Installing Flask installs the ``flask`` script, a `Click`_ command line
|
||||
interface, in your virtualenv. Executed from the terminal, this script gives
|
||||
access to built-in, extension, and application-defined commands. The ``--help``
|
||||
option will give more information about any commands and options.
|
||||
|
||||
.. _Click: https://click.palletsprojects.com/
|
||||
|
||||
|
||||
Application Discovery
|
||||
---------------------
|
||||
|
||||
The ``flask`` command is installed by Flask, not your application; it must be
|
||||
told where to find your application in order to use it. The ``--app``
|
||||
option is used to specify how to load the application.
|
||||
|
||||
While ``--app`` supports a variety of options for specifying your
|
||||
application, most use cases should be simple. Here are the typical values:
|
||||
|
||||
(nothing)
|
||||
The name "app" or "wsgi" is imported (as a ".py" file, or package),
|
||||
automatically detecting an app (``app`` or ``application``) or
|
||||
factory (``create_app`` or ``make_app``).
|
||||
|
||||
``--app hello``
|
||||
The given name is imported, automatically detecting an app (``app``
|
||||
or ``application``) or factory (``create_app`` or ``make_app``).
|
||||
|
||||
----
|
||||
|
||||
``--app`` has three parts: an optional path that sets the current working
|
||||
directory, a Python file or dotted import path, and an optional variable
|
||||
name of the instance or factory. If the name is a factory, it can optionally
|
||||
be followed by arguments in parentheses. The following values demonstrate these
|
||||
parts:
|
||||
|
||||
``--app src/hello``
|
||||
Sets the current working directory to ``src`` then imports ``hello``.
|
||||
|
||||
``--app hello.web``
|
||||
Imports the path ``hello.web``.
|
||||
|
||||
``--app hello:app2``
|
||||
Uses the ``app2`` Flask instance in ``hello``.
|
||||
|
||||
``--app 'hello:create_app("dev")'``
|
||||
The ``create_app`` factory in ``hello`` is called with the string ``'dev'``
|
||||
as the argument.
|
||||
|
||||
If ``--app`` is not set, the command will try to import "app" or
|
||||
"wsgi" (as a ".py" file, or package) and try to detect an application
|
||||
instance or factory.
|
||||
|
||||
Within the given import, the command looks for an application instance named
|
||||
``app`` or ``application``, then any application instance. If no instance is
|
||||
found, the command looks for a factory function named ``create_app`` or
|
||||
``make_app`` that returns an instance.
|
||||
|
||||
If parentheses follow the factory name, their contents are parsed as
|
||||
Python literals and passed as arguments and keyword arguments to the
|
||||
function. This means that strings must still be in quotes.
|
||||
|
||||
|
||||
Run the Development Server
|
||||
--------------------------
|
||||
|
||||
The :func:`run <cli.run_command>` command will start the development server. It
|
||||
replaces the :meth:`Flask.run` method in most cases. ::
|
||||
|
||||
$ flask --app hello run
|
||||
* Serving Flask app "hello"
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
|
||||
.. warning:: Do not use this command to run your application in production.
|
||||
Only use the development server during development. The development server
|
||||
is provided for convenience, but is not designed to be particularly secure,
|
||||
stable, or efficient. See :doc:`/deploying/index` for how to run in production.
|
||||
|
||||
If another program is already using port 5000, you'll see
|
||||
``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the
|
||||
server tries to start. See :ref:`address-already-in-use` for how to
|
||||
handle that.
|
||||
|
||||
|
||||
Debug Mode
|
||||
~~~~~~~~~~
|
||||
|
||||
In debug mode, the ``flask run`` command will enable the interactive debugger and the
|
||||
reloader by default, and make errors easier to see and debug. To enable debug mode, use
|
||||
the ``--debug`` option.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ flask --app hello run --debug
|
||||
* Serving Flask app "hello"
|
||||
* Debug mode: on
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
* Restarting with inotify reloader
|
||||
* Debugger is active!
|
||||
* Debugger PIN: 223-456-919
|
||||
|
||||
The ``--debug`` option can also be passed to the top level ``flask`` command to enable
|
||||
debug mode for any command. The following two ``run`` calls are equivalent.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ flask --app hello --debug run
|
||||
$ flask --app hello run --debug
|
||||
|
||||
|
||||
Watch and Ignore Files with the Reloader
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When using debug mode, the reloader will trigger whenever your Python code or imported
|
||||
modules change. The reloader can watch additional files with the ``--extra-files``
|
||||
option. Multiple paths are separated with ``:``, or ``;`` on Windows.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask run --extra-files file1:dirA/file2:dirB/
|
||||
* Running on http://127.0.0.1:8000/
|
||||
* Detected change in '/path/to/file1', reloading
|
||||
|
||||
The reloader can also ignore files using :mod:`fnmatch` patterns with the
|
||||
``--exclude-patterns`` option. Multiple patterns are separated with ``:``, or ``;`` on
|
||||
Windows.
|
||||
|
||||
|
||||
Open a Shell
|
||||
------------
|
||||
|
||||
To explore the data in your application, you can start an interactive Python
|
||||
shell with the :func:`shell <cli.shell_command>` command. An application
|
||||
context will be active, and the app instance will be imported. ::
|
||||
|
||||
$ flask shell
|
||||
Python 3.10.0 (default, Oct 27 2021, 06:59:51) [GCC 11.1.0] on linux
|
||||
App: example [production]
|
||||
Instance: /home/david/Projects/pallets/flask/instance
|
||||
>>>
|
||||
|
||||
Use :meth:`~Flask.shell_context_processor` to add other automatic imports.
|
||||
|
||||
|
||||
.. _dotenv:
|
||||
|
||||
Environment Variables From dotenv
|
||||
---------------------------------
|
||||
|
||||
The ``flask`` command supports setting any option for any command with
|
||||
environment variables. The variables are named like ``FLASK_OPTION`` or
|
||||
``FLASK_COMMAND_OPTION``, for example ``FLASK_APP`` or
|
||||
``FLASK_RUN_PORT``.
|
||||
|
||||
Rather than passing options every time you run a command, or environment
|
||||
variables every time you open a new terminal, you can use Flask's dotenv
|
||||
support to set environment variables automatically.
|
||||
|
||||
If `python-dotenv`_ is installed, running the ``flask`` command will set
|
||||
environment variables defined in the files ``.env`` and ``.flaskenv``.
|
||||
You can also specify an extra file to load with the ``--env-file``
|
||||
option. Dotenv files can be used to avoid having to set ``--app`` or
|
||||
``FLASK_APP`` manually, and to set configuration using environment
|
||||
variables similar to how some deployment services work.
|
||||
|
||||
Variables set on the command line are used over those set in :file:`.env`,
|
||||
which are used over those set in :file:`.flaskenv`. :file:`.flaskenv` should be
|
||||
used for public variables, such as ``FLASK_APP``, while :file:`.env` should not
|
||||
be committed to your repository so that it can set private variables.
|
||||
|
||||
Directories are scanned upwards from the directory you call ``flask``
|
||||
from to locate the files.
|
||||
|
||||
The files are only loaded by the ``flask`` command or calling
|
||||
:meth:`~Flask.run`. If you would like to load these files when running in
|
||||
production, you should call :func:`~cli.load_dotenv` manually.
|
||||
|
||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||
|
||||
|
||||
Setting Command Options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Click is configured to load default values for command options from
|
||||
environment variables. The variables use the pattern
|
||||
``FLASK_COMMAND_OPTION``. For example, to set the port for the run
|
||||
command, instead of ``flask run --port 8000``:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Bash
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export FLASK_RUN_PORT=8000
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:8000/
|
||||
|
||||
.. group-tab:: Fish
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ set -x FLASK_RUN_PORT 8000
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:8000/
|
||||
|
||||
.. group-tab:: CMD
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> set FLASK_RUN_PORT=8000
|
||||
> flask run
|
||||
* Running on http://127.0.0.1:8000/
|
||||
|
||||
.. group-tab:: Powershell
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> $env:FLASK_RUN_PORT = 8000
|
||||
> flask run
|
||||
* Running on http://127.0.0.1:8000/
|
||||
|
||||
These can be added to the ``.flaskenv`` file just like ``FLASK_APP`` to
|
||||
control default command options.
|
||||
|
||||
|
||||
Disable dotenv
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The ``flask`` command will show a message if it detects dotenv files but
|
||||
python-dotenv is not installed.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ flask run
|
||||
* Tip: There are .env files present. Do "pip install python-dotenv" to use them.
|
||||
|
||||
You can tell Flask not to load dotenv files even when python-dotenv is
|
||||
installed by setting the ``FLASK_SKIP_DOTENV`` environment variable.
|
||||
This can be useful if you want to load them manually, or if you're using
|
||||
a project runner that loads them already. Keep in mind that the
|
||||
environment variables must be set before the app loads or it won't
|
||||
configure as expected.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Bash
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export FLASK_SKIP_DOTENV=1
|
||||
$ flask run
|
||||
|
||||
.. group-tab:: Fish
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ set -x FLASK_SKIP_DOTENV 1
|
||||
$ flask run
|
||||
|
||||
.. group-tab:: CMD
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> set FLASK_SKIP_DOTENV=1
|
||||
> flask run
|
||||
|
||||
.. group-tab:: Powershell
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> $env:FLASK_SKIP_DOTENV = 1
|
||||
> flask run
|
||||
|
||||
|
||||
Environment Variables From virtualenv
|
||||
-------------------------------------
|
||||
|
||||
If you do not want to install dotenv support, you can still set environment
|
||||
variables by adding them to the end of the virtualenv's :file:`activate`
|
||||
script. Activating the virtualenv will set the variables.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Bash
|
||||
|
||||
Unix Bash, :file:`.venv/bin/activate`::
|
||||
|
||||
$ export FLASK_APP=hello
|
||||
|
||||
.. group-tab:: Fish
|
||||
|
||||
Fish, :file:`.venv/bin/activate.fish`::
|
||||
|
||||
$ set -x FLASK_APP hello
|
||||
|
||||
.. group-tab:: CMD
|
||||
|
||||
Windows CMD, :file:`.venv\\Scripts\\activate.bat`::
|
||||
|
||||
> set FLASK_APP=hello
|
||||
|
||||
.. group-tab:: Powershell
|
||||
|
||||
Windows Powershell, :file:`.venv\\Scripts\\activate.ps1`::
|
||||
|
||||
> $env:FLASK_APP = "hello"
|
||||
|
||||
It is preferred to use dotenv support over this, since :file:`.flaskenv` can be
|
||||
committed to the repository so that it works automatically wherever the project
|
||||
is checked out.
|
||||
|
||||
|
||||
Custom Commands
|
||||
---------------
|
||||
|
||||
The ``flask`` command is implemented using `Click`_. See that project's
|
||||
documentation for full information about writing commands.
|
||||
|
||||
This example adds the command ``create-user`` that takes the argument
|
||||
``name``. ::
|
||||
|
||||
import click
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.cli.command("create-user")
|
||||
@click.argument("name")
|
||||
def create_user(name):
|
||||
...
|
||||
|
||||
::
|
||||
|
||||
$ flask create-user admin
|
||||
|
||||
This example adds the same command, but as ``user create``, a command in a
|
||||
group. This is useful if you want to organize multiple related commands. ::
|
||||
|
||||
import click
|
||||
from flask import Flask
|
||||
from flask.cli import AppGroup
|
||||
|
||||
app = Flask(__name__)
|
||||
user_cli = AppGroup('user')
|
||||
|
||||
@user_cli.command('create')
|
||||
@click.argument('name')
|
||||
def create_user(name):
|
||||
...
|
||||
|
||||
app.cli.add_command(user_cli)
|
||||
|
||||
::
|
||||
|
||||
$ flask user create demo
|
||||
|
||||
See :ref:`testing-cli` for an overview of how to test your custom
|
||||
commands.
|
||||
|
||||
|
||||
Registering Commands with Blueprints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If your application uses blueprints, you can optionally register CLI
|
||||
commands directly onto them. When your blueprint is registered onto your
|
||||
application, the associated commands will be available to the ``flask``
|
||||
command. By default, those commands will be nested in a group matching
|
||||
the name of the blueprint.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('students', __name__)
|
||||
|
||||
@bp.cli.command('create')
|
||||
@click.argument('name')
|
||||
def create(name):
|
||||
...
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask students create alice
|
||||
|
||||
You can alter the group name by specifying the ``cli_group`` parameter
|
||||
when creating the :class:`Blueprint` object, or later with
|
||||
:meth:`app.register_blueprint(bp, cli_group='...') <Flask.register_blueprint>`.
|
||||
The following are equivalent:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp = Blueprint('students', __name__, cli_group='other')
|
||||
# or
|
||||
app.register_blueprint(bp, cli_group='other')
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask other create alice
|
||||
|
||||
Specifying ``cli_group=None`` will remove the nesting and merge the
|
||||
commands directly to the application's level:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp = Blueprint('students', __name__, cli_group=None)
|
||||
# or
|
||||
app.register_blueprint(bp, cli_group=None)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask create alice
|
||||
|
||||
|
||||
Application Context
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Commands added using the Flask app's :attr:`~Flask.cli` or
|
||||
:class:`~flask.cli.FlaskGroup` :meth:`~cli.AppGroup.command` decorator
|
||||
will be executed with an application context pushed, so your custom
|
||||
commands and parameters have access to the app and its configuration. The
|
||||
:func:`~cli.with_appcontext` decorator can be used to get the same
|
||||
behavior, but is not needed in most cases.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
from flask.cli import with_appcontext
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def do_work():
|
||||
...
|
||||
|
||||
app.cli.add_command(do_work)
|
||||
|
||||
|
||||
Plugins
|
||||
-------
|
||||
|
||||
Flask will automatically load commands specified in the ``flask.commands``
|
||||
`entry point`_. This is useful for extensions that want to add commands when
|
||||
they are installed. Entry points are specified in :file:`pyproject.toml`:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[project.entry-points."flask.commands"]
|
||||
my-command = "my_extension.commands:cli"
|
||||
|
||||
.. _entry point: https://packaging.python.org/tutorials/packaging-projects/#entry-points
|
||||
|
||||
Inside :file:`my_extension/commands.py` you can then export a Click
|
||||
object::
|
||||
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
...
|
||||
|
||||
Once that package is installed in the same virtualenv as your Flask project,
|
||||
you can run ``flask my-command`` to invoke the command.
|
||||
|
||||
|
||||
.. _custom-scripts:
|
||||
|
||||
Custom Scripts
|
||||
--------------
|
||||
|
||||
When you are using the app factory pattern, it may be more convenient to define
|
||||
your own Click script. Instead of using ``--app`` and letting Flask load
|
||||
your application, you can create your own Click object and export it as a
|
||||
`console script`_ entry point.
|
||||
|
||||
Create an instance of :class:`~cli.FlaskGroup` and pass it the factory::
|
||||
|
||||
import click
|
||||
from flask import Flask
|
||||
from flask.cli import FlaskGroup
|
||||
|
||||
def create_app():
|
||||
app = Flask('wiki')
|
||||
# other setup
|
||||
return app
|
||||
|
||||
@click.group(cls=FlaskGroup, create_app=create_app)
|
||||
def cli():
|
||||
"""Management script for the Wiki application."""
|
||||
|
||||
Define the entry point in :file:`pyproject.toml`:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[project.scripts]
|
||||
wiki = "wiki:cli"
|
||||
|
||||
Install the application in the virtualenv in editable mode and the custom
|
||||
script is available. Note that you don't need to set ``--app``. ::
|
||||
|
||||
$ pip install -e .
|
||||
$ wiki run
|
||||
|
||||
.. admonition:: Errors in Custom Scripts
|
||||
|
||||
When using a custom script, if you introduce an error in your
|
||||
module-level code, the reloader will fail because it can no longer
|
||||
load the entry point.
|
||||
|
||||
The ``flask`` command, being separate from your code, does not have
|
||||
this issue and is recommended in most cases.
|
||||
|
||||
.. _console script: https://packaging.python.org/tutorials/packaging-projects/#console-scripts
|
||||
|
||||
|
||||
PyCharm Integration
|
||||
-------------------
|
||||
|
||||
PyCharm Professional provides a special Flask run configuration to run the development
|
||||
server. For the Community Edition, and for other commands besides ``run``, you need to
|
||||
create a custom run configuration. These instructions should be similar for any other
|
||||
IDE you use.
|
||||
|
||||
In PyCharm, with your project open, click on *Run* from the menu bar and go to *Edit
|
||||
Configurations*. You'll see a screen similar to this:
|
||||
|
||||
.. image:: _static/pycharm-run-config.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: Screenshot of PyCharm run configuration.
|
||||
|
||||
Once you create a configuration for the ``flask run``, you can copy and change it to
|
||||
call any other command.
|
||||
|
||||
Click the *+ (Add New Configuration)* button and select *Python*. Give the configuration
|
||||
a name such as "flask run".
|
||||
|
||||
Click the *Script path* dropdown and change it to *Module name*, then input ``flask``.
|
||||
|
||||
The *Parameters* field is set to the CLI command to execute along with any arguments.
|
||||
This example uses ``--app hello run --debug``, which will run the development server in
|
||||
debug mode. ``--app hello`` should be the import or file with your Flask app.
|
||||
|
||||
If you installed your project as a package in your virtualenv, you may uncheck the
|
||||
*PYTHONPATH* options. This will more accurately match how you deploy later.
|
||||
|
||||
Click *OK* to save and close the configuration. Select the configuration in the main
|
||||
PyCharm window and click the play button next to it to run the server.
|
||||
|
||||
Now that you have a configuration for ``flask run``, you can copy that configuration and
|
||||
change the *Parameters* argument to run a different CLI command.
|
||||
@ -0,0 +1,101 @@
|
||||
import packaging.version
|
||||
from pallets_sphinx_themes import get_version
|
||||
from pallets_sphinx_themes import ProjectLink
|
||||
|
||||
# Project --------------------------------------------------------------
|
||||
|
||||
project = "Flask"
|
||||
copyright = "2010 Pallets"
|
||||
author = "Pallets"
|
||||
release, version = get_version("Flask")
|
||||
|
||||
# General --------------------------------------------------------------
|
||||
|
||||
default_role = "code"
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.extlinks",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinxcontrib.log_cabinet",
|
||||
"sphinx_tabs.tabs",
|
||||
"pallets_sphinx_themes",
|
||||
]
|
||||
autodoc_member_order = "bysource"
|
||||
autodoc_typehints = "description"
|
||||
autodoc_preserve_defaults = True
|
||||
extlinks = {
|
||||
"issue": ("https://github.com/pallets/flask/issues/%s", "#%s"),
|
||||
"pr": ("https://github.com/pallets/flask/pull/%s", "#%s"),
|
||||
"ghsa": ("https://github.com/pallets/flask/security/advisories/GHSA-%s", "GHSA-%s"),
|
||||
}
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3/", None),
|
||||
"werkzeug": ("https://werkzeug.palletsprojects.com/", None),
|
||||
"click": ("https://click.palletsprojects.com/", None),
|
||||
"jinja": ("https://jinja.palletsprojects.com/", None),
|
||||
"itsdangerous": ("https://itsdangerous.palletsprojects.com/", None),
|
||||
"sqlalchemy": ("https://docs.sqlalchemy.org/", None),
|
||||
"wtforms": ("https://wtforms.readthedocs.io/", None),
|
||||
"blinker": ("https://blinker.readthedocs.io/", None),
|
||||
}
|
||||
|
||||
# HTML -----------------------------------------------------------------
|
||||
|
||||
html_theme = "flask"
|
||||
html_theme_options = {"index_sidebar_logo": False}
|
||||
html_context = {
|
||||
"project_links": [
|
||||
ProjectLink("Donate", "https://palletsprojects.com/donate"),
|
||||
ProjectLink("PyPI Releases", "https://pypi.org/project/Flask/"),
|
||||
ProjectLink("Source Code", "https://github.com/pallets/flask/"),
|
||||
ProjectLink("Issue Tracker", "https://github.com/pallets/flask/issues/"),
|
||||
ProjectLink("Chat", "https://discord.gg/pallets"),
|
||||
]
|
||||
}
|
||||
html_sidebars = {
|
||||
"index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"],
|
||||
"**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"],
|
||||
}
|
||||
singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]}
|
||||
html_static_path = ["_static"]
|
||||
html_favicon = "_static/flask-icon.svg"
|
||||
html_logo = "_static/flask-logo.svg"
|
||||
html_title = f"Flask Documentation ({version})"
|
||||
html_show_sourcelink = False
|
||||
|
||||
gettext_uuid = True
|
||||
gettext_compact = False
|
||||
|
||||
# Local Extensions -----------------------------------------------------
|
||||
|
||||
|
||||
def github_link(name, rawtext, text, lineno, inliner, options=None, content=None):
|
||||
app = inliner.document.settings.env.app
|
||||
release = app.config.release
|
||||
base_url = "https://github.com/pallets/flask/tree/"
|
||||
|
||||
if text.endswith(">"):
|
||||
words, text = text[:-1].rsplit("<", 1)
|
||||
words = words.strip()
|
||||
else:
|
||||
words = None
|
||||
|
||||
if packaging.version.parse(release).is_devrelease:
|
||||
url = f"{base_url}main/{text}"
|
||||
else:
|
||||
url = f"{base_url}{release}/{text}"
|
||||
|
||||
if words is None:
|
||||
words = url
|
||||
|
||||
from docutils.nodes import reference
|
||||
from docutils.parsers.rst.roles import set_classes
|
||||
|
||||
options = options or {}
|
||||
set_classes(options)
|
||||
node = reference(rawtext, words, refuri=url, **options)
|
||||
return [node], []
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_role("gh", github_link)
|
||||
@ -0,0 +1,839 @@
|
||||
Configuration Handling
|
||||
======================
|
||||
|
||||
Applications need some kind of configuration. There are different settings
|
||||
you might want to change depending on the application environment like
|
||||
toggling the debug mode, setting the secret key, and other such
|
||||
environment-specific things.
|
||||
|
||||
The way Flask is designed usually requires the configuration to be
|
||||
available when the application starts up. You can hard code the
|
||||
configuration in the code, which for many small applications is not
|
||||
actually that bad, but there are better ways.
|
||||
|
||||
Independent of how you load your config, there is a config object
|
||||
available which holds the loaded configuration values:
|
||||
The :attr:`~flask.Flask.config` attribute of the :class:`~flask.Flask`
|
||||
object. This is the place where Flask itself puts certain configuration
|
||||
values and also where extensions can put their configuration values. But
|
||||
this is also where you can have your own configuration.
|
||||
|
||||
|
||||
Configuration Basics
|
||||
--------------------
|
||||
|
||||
The :attr:`~flask.Flask.config` is actually a subclass of a dictionary and
|
||||
can be modified just like any dictionary::
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['TESTING'] = True
|
||||
|
||||
Certain configuration values are also forwarded to the
|
||||
:attr:`~flask.Flask` object so you can read and write them from there::
|
||||
|
||||
app.testing = True
|
||||
|
||||
To update multiple keys at once you can use the :meth:`dict.update`
|
||||
method::
|
||||
|
||||
app.config.update(
|
||||
TESTING=True,
|
||||
SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
)
|
||||
|
||||
|
||||
Debug Mode
|
||||
----------
|
||||
|
||||
The :data:`DEBUG` config value is special because it may behave inconsistently if
|
||||
changed after the app has begun setting up. In order to set debug mode reliably, use the
|
||||
``--debug`` option on the ``flask`` or ``flask run`` command. ``flask run`` will use the
|
||||
interactive debugger and reloader by default in debug mode.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app hello run --debug
|
||||
|
||||
Using the option is recommended. While it is possible to set :data:`DEBUG` in your
|
||||
config or code, this is strongly discouraged. It can't be read early by the
|
||||
``flask run`` command, and some systems or extensions may have already configured
|
||||
themselves based on a previous value.
|
||||
|
||||
|
||||
Builtin Configuration Values
|
||||
----------------------------
|
||||
|
||||
The following configuration values are used internally by Flask:
|
||||
|
||||
.. py:data:: DEBUG
|
||||
|
||||
Whether debug mode is enabled. When using ``flask run`` to start the development
|
||||
server, an interactive debugger will be shown for unhandled exceptions, and the
|
||||
server will be reloaded when code changes. The :attr:`~flask.Flask.debug` attribute
|
||||
maps to this config key. This is set with the ``FLASK_DEBUG`` environment variable.
|
||||
It may not behave as expected if set in code.
|
||||
|
||||
**Do not enable debug mode when deploying in production.**
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. py:data:: TESTING
|
||||
|
||||
Enable testing mode. Exceptions are propagated rather than handled by the
|
||||
the app's error handlers. Extensions may also change their behavior to
|
||||
facilitate easier testing. You should enable this in your own tests.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. py:data:: PROPAGATE_EXCEPTIONS
|
||||
|
||||
Exceptions are re-raised rather than being handled by the app's error
|
||||
handlers. If not set, this is implicitly true if ``TESTING`` or ``DEBUG``
|
||||
is enabled.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: TRAP_HTTP_EXCEPTIONS
|
||||
|
||||
If there is no handler for an ``HTTPException``-type exception, re-raise it
|
||||
to be handled by the interactive debugger instead of returning it as a
|
||||
simple error response.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. py:data:: TRAP_BAD_REQUEST_ERRORS
|
||||
|
||||
Trying to access a key that doesn't exist from request dicts like ``args``
|
||||
and ``form`` will return a 400 Bad Request error page. Enable this to treat
|
||||
the error as an unhandled exception instead so that you get the interactive
|
||||
debugger. This is a more specific version of ``TRAP_HTTP_EXCEPTIONS``. If
|
||||
unset, it is enabled in debug mode.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: SECRET_KEY
|
||||
|
||||
A secret key that will be used for securely signing the session cookie
|
||||
and can be used for any other security related needs by extensions or your
|
||||
application. It should be a long random ``bytes`` or ``str``. For
|
||||
example, copy the output of this to your config::
|
||||
|
||||
$ python -c 'import secrets; print(secrets.token_hex())'
|
||||
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
|
||||
**Do not reveal the secret key when posting questions or committing code.**
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: SECRET_KEY_FALLBACKS
|
||||
|
||||
A list of old secret keys that can still be used for unsigning. This allows
|
||||
a project to implement key rotation without invalidating active sessions or
|
||||
other recently-signed secrets.
|
||||
|
||||
Keys should be removed after an appropriate period of time, as checking each
|
||||
additional key adds some overhead.
|
||||
|
||||
Order should not matter, but the default implementation will test the last
|
||||
key in the list first, so it might make sense to order oldest to newest.
|
||||
|
||||
Flask's built-in secure cookie session supports this. Extensions that use
|
||||
:data:`SECRET_KEY` may not support this yet.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
.. py:data:: SESSION_COOKIE_NAME
|
||||
|
||||
The name of the session cookie. Can be changed in case you already have a
|
||||
cookie with the same name.
|
||||
|
||||
Default: ``'session'``
|
||||
|
||||
.. py:data:: SESSION_COOKIE_DOMAIN
|
||||
|
||||
The value of the ``Domain`` parameter on the session cookie. If not set, browsers
|
||||
will only send the cookie to the exact domain it was set from. Otherwise, they
|
||||
will send it to any subdomain of the given value as well.
|
||||
|
||||
Not setting this value is more restricted and secure than setting it.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. warning::
|
||||
If this is changed after the browser created a cookie is created with
|
||||
one setting, it may result in another being created. Browsers may send
|
||||
send both in an undefined order. In that case, you may want to change
|
||||
:data:`SESSION_COOKIE_NAME` as well or otherwise invalidate old sessions.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Not set by default, does not fall back to ``SERVER_NAME``.
|
||||
|
||||
.. py:data:: SESSION_COOKIE_PATH
|
||||
|
||||
The path that the session cookie will be valid for. If not set, the cookie
|
||||
will be valid underneath ``APPLICATION_ROOT`` or ``/`` if that is not set.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: SESSION_COOKIE_HTTPONLY
|
||||
|
||||
Browsers will not allow JavaScript access to cookies marked as "HTTP only"
|
||||
for security.
|
||||
|
||||
Default: ``True``
|
||||
|
||||
.. py:data:: SESSION_COOKIE_SECURE
|
||||
|
||||
Browsers will only send cookies with requests over HTTPS if the cookie is
|
||||
marked "secure". The application must be served over HTTPS for this to make
|
||||
sense.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. py:data:: SESSION_COOKIE_PARTITIONED
|
||||
|
||||
Browsers will send cookies based on the top-level document's domain, rather
|
||||
than only the domain of the document setting the cookie. This prevents third
|
||||
party cookies set in iframes from "leaking" between separate sites.
|
||||
|
||||
Browsers are beginning to disallow non-partitioned third party cookies, so
|
||||
you need to mark your cookies partitioned if you expect them to work in such
|
||||
embedded situations.
|
||||
|
||||
Enabling this implicitly enables :data:`SESSION_COOKIE_SECURE` as well, as
|
||||
it is only valid when served over HTTPS.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
.. py:data:: SESSION_COOKIE_SAMESITE
|
||||
|
||||
Restrict how cookies are sent with requests from external sites. Can
|
||||
be set to ``'Lax'`` (recommended) or ``'Strict'``.
|
||||
See :ref:`security-cookie`.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
.. py:data:: PERMANENT_SESSION_LIFETIME
|
||||
|
||||
If ``session.permanent`` is true, the cookie's expiration will be set this
|
||||
number of seconds in the future. Can either be a
|
||||
:class:`datetime.timedelta` or an ``int``.
|
||||
|
||||
Flask's default cookie implementation validates that the cryptographic
|
||||
signature is not older than this value.
|
||||
|
||||
Default: ``timedelta(days=31)`` (``2678400`` seconds)
|
||||
|
||||
.. py:data:: SESSION_REFRESH_EACH_REQUEST
|
||||
|
||||
Control whether the cookie is sent with every response when
|
||||
``session.permanent`` is true. Sending the cookie every time (the default)
|
||||
can more reliably keep the session from expiring, but uses more bandwidth.
|
||||
Non-permanent sessions are not affected.
|
||||
|
||||
Default: ``True``
|
||||
|
||||
.. py:data:: USE_X_SENDFILE
|
||||
|
||||
When serving files, set the ``X-Sendfile`` header instead of serving the
|
||||
data with Flask. Some web servers, such as Apache, recognize this and serve
|
||||
the data more efficiently. This only makes sense when using such a server.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. py:data:: SEND_FILE_MAX_AGE_DEFAULT
|
||||
|
||||
When serving files, set the cache control max age to this number of
|
||||
seconds. Can be a :class:`datetime.timedelta` or an ``int``.
|
||||
Override this value on a per-file basis using
|
||||
:meth:`~flask.Flask.get_send_file_max_age` on the application or
|
||||
blueprint.
|
||||
|
||||
If ``None``, ``send_file`` tells the browser to use conditional
|
||||
requests will be used instead of a timed cache, which is usually
|
||||
preferable.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: TRUSTED_HOSTS
|
||||
|
||||
Validate :attr:`.Request.host` and other attributes that use it against
|
||||
these trusted values. Raise a :exc:`~werkzeug.exceptions.SecurityError` if
|
||||
the host is invalid, which results in a 400 error. If it is ``None``, all
|
||||
hosts are valid. Each value is either an exact match, or can start with
|
||||
a dot ``.`` to match any subdomain.
|
||||
|
||||
Validation is done during routing against this value. ``before_request`` and
|
||||
``after_request`` callbacks will still be called.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
.. py:data:: SERVER_NAME
|
||||
|
||||
Inform the application what host and port it is bound to.
|
||||
|
||||
Must be set if ``subdomain_matching`` is enabled, to be able to extract the
|
||||
subdomain from the request.
|
||||
|
||||
Must be set for ``url_for`` to generate external URLs outside of a
|
||||
request context.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. versionchanged:: 3.1
|
||||
Does not restrict requests to only this domain, for both
|
||||
``subdomain_matching`` and ``host_matching``.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Does not implicitly enable ``subdomain_matching``.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Does not affect ``SESSION_COOKIE_DOMAIN``.
|
||||
|
||||
.. py:data:: APPLICATION_ROOT
|
||||
|
||||
Inform the application what path it is mounted under by the application /
|
||||
web server. This is used for generating URLs outside the context of a
|
||||
request (inside a request, the dispatcher is responsible for setting
|
||||
``SCRIPT_NAME`` instead; see :doc:`/patterns/appdispatch`
|
||||
for examples of dispatch configuration).
|
||||
|
||||
Will be used for the session cookie path if ``SESSION_COOKIE_PATH`` is not
|
||||
set.
|
||||
|
||||
Default: ``'/'``
|
||||
|
||||
.. py:data:: PREFERRED_URL_SCHEME
|
||||
|
||||
Use this scheme for generating external URLs when not in a request context.
|
||||
|
||||
Default: ``'http'``
|
||||
|
||||
.. py:data:: MAX_CONTENT_LENGTH
|
||||
|
||||
The maximum number of bytes that will be read during this request. If
|
||||
this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge`
|
||||
error is raised. If it is set to ``None``, no limit is enforced at the
|
||||
Flask application level. However, if it is ``None`` and the request has no
|
||||
``Content-Length`` header and the WSGI server does not indicate that it
|
||||
terminates the stream, then no data is read to avoid an infinite stream.
|
||||
|
||||
Each request defaults to this config. It can be set on a specific
|
||||
:attr:`.Request.max_content_length` to apply the limit to that specific
|
||||
view. This should be set appropriately based on an application's or view's
|
||||
specific needs.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
.. py:data:: MAX_FORM_MEMORY_SIZE
|
||||
|
||||
The maximum size in bytes any non-file form field may be in a
|
||||
``multipart/form-data`` body. If this limit is exceeded, a 413
|
||||
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it is
|
||||
set to ``None``, no limit is enforced at the Flask application level.
|
||||
|
||||
Each request defaults to this config. It can be set on a specific
|
||||
:attr:`.Request.max_form_memory_parts` to apply the limit to that specific
|
||||
view. This should be set appropriately based on an application's or view's
|
||||
specific needs.
|
||||
|
||||
Default: ``500_000``
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
.. py:data:: MAX_FORM_PARTS
|
||||
|
||||
The maximum number of fields that may be present in a
|
||||
``multipart/form-data`` body. If this limit is exceeded, a 413
|
||||
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it
|
||||
is set to ``None``, no limit is enforced at the Flask application level.
|
||||
|
||||
Each request defaults to this config. It can be set on a specific
|
||||
:attr:`.Request.max_form_parts` to apply the limit to that specific view.
|
||||
This should be set appropriately based on an application's or view's
|
||||
specific needs.
|
||||
|
||||
Default: ``1_000``
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
.. py:data:: TEMPLATES_AUTO_RELOAD
|
||||
|
||||
Reload templates when they are changed. If not set, it will be enabled in
|
||||
debug mode.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: EXPLAIN_TEMPLATE_LOADING
|
||||
|
||||
Log debugging information tracing how a template file was loaded. This can
|
||||
be useful to figure out why a template was not loaded or the wrong file
|
||||
appears to be loaded.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. py:data:: MAX_COOKIE_SIZE
|
||||
|
||||
Warn if cookie headers are larger than this many bytes. Defaults to
|
||||
``4093``. Larger cookies may be silently ignored by browsers. Set to
|
||||
``0`` to disable the warning.
|
||||
|
||||
.. py:data:: PROVIDE_AUTOMATIC_OPTIONS
|
||||
|
||||
Set to ``False`` to disable the automatic addition of OPTIONS
|
||||
responses. This can be overridden per route by altering the
|
||||
``provide_automatic_options`` attribute.
|
||||
|
||||
.. versionadded:: 0.4
|
||||
``LOGGER_NAME``
|
||||
|
||||
.. versionadded:: 0.5
|
||||
``SERVER_NAME``
|
||||
|
||||
.. versionadded:: 0.6
|
||||
``MAX_CONTENT_LENGTH``
|
||||
|
||||
.. versionadded:: 0.7
|
||||
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
|
||||
|
||||
.. versionadded:: 0.8
|
||||
``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``,
|
||||
``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``,
|
||||
``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``,
|
||||
``SESSION_COOKIE_SECURE``
|
||||
|
||||
.. versionadded:: 0.9
|
||||
``PREFERRED_URL_SCHEME``
|
||||
|
||||
.. versionadded:: 0.10
|
||||
``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR``
|
||||
|
||||
.. versionadded:: 0.11
|
||||
``SESSION_REFRESH_EACH_REQUEST``, ``TEMPLATES_AUTO_RELOAD``,
|
||||
``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING``
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See
|
||||
:doc:`/logging` for information about configuration.
|
||||
|
||||
Added :data:`ENV` to reflect the :envvar:`FLASK_ENV` environment
|
||||
variable.
|
||||
|
||||
Added :data:`SESSION_COOKIE_SAMESITE` to control the session
|
||||
cookie's ``SameSite`` option.
|
||||
|
||||
Added :data:`MAX_COOKIE_SIZE` to control a warning from Werkzeug.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
Removed ``PRESERVE_CONTEXT_ON_EXCEPTION``.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and
|
||||
``JSONIFY_PRETTYPRINT_REGULAR`` were removed. The default ``app.json`` provider has
|
||||
equivalent attributes instead.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
``ENV`` was removed.
|
||||
|
||||
.. versionadded:: 3.10
|
||||
Added :data:`PROVIDE_AUTOMATIC_OPTIONS` to control the default
|
||||
addition of autogenerated OPTIONS responses.
|
||||
|
||||
|
||||
Configuring from Python Files
|
||||
-----------------------------
|
||||
|
||||
Configuration becomes more useful if you can store it in a separate file, ideally
|
||||
located outside the actual application package. You can deploy your application, then
|
||||
separately configure it for the specific deployment.
|
||||
|
||||
A common pattern is this::
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('yourapplication.default_settings')
|
||||
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
||||
|
||||
This first loads the configuration from the
|
||||
`yourapplication.default_settings` module and then overrides the values
|
||||
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||
environment variable points to. This environment variable can be set
|
||||
in the shell before starting the server:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Bash
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
.. group-tab:: Fish
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ set -x YOURAPPLICATION_SETTINGS /path/to/settings.cfg
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
.. group-tab:: CMD
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg
|
||||
> flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
.. group-tab:: Powershell
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> $env:YOURAPPLICATION_SETTINGS = "\path\to\settings.cfg"
|
||||
> flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
The configuration files themselves are actual Python files. Only values
|
||||
in uppercase are actually stored in the config object later on. So make
|
||||
sure to use uppercase letters for your config keys.
|
||||
|
||||
Here is an example of a configuration file::
|
||||
|
||||
# Example configuration
|
||||
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
|
||||
Make sure to load the configuration very early on, so that extensions have
|
||||
the ability to access the configuration when starting up. There are other
|
||||
methods on the config object as well to load from individual files. For a
|
||||
complete reference, read the :class:`~flask.Config` object's
|
||||
documentation.
|
||||
|
||||
|
||||
Configuring from Data Files
|
||||
---------------------------
|
||||
|
||||
It is also possible to load configuration from a file in a format of
|
||||
your choice using :meth:`~flask.Config.from_file`. For example to load
|
||||
from a TOML file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import tomllib
|
||||
app.config.from_file("config.toml", load=tomllib.load, text=False)
|
||||
|
||||
Or from a JSON file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import json
|
||||
app.config.from_file("config.json", load=json.load)
|
||||
|
||||
|
||||
Configuring from Environment Variables
|
||||
--------------------------------------
|
||||
|
||||
In addition to pointing to configuration files using environment
|
||||
variables, you may find it useful (or necessary) to control your
|
||||
configuration values directly from the environment. Flask can be
|
||||
instructed to load all environment variables starting with a specific
|
||||
prefix into the config using :meth:`~flask.Config.from_prefixed_env`.
|
||||
|
||||
Environment variables can be set in the shell before starting the
|
||||
server:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Bash
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f"
|
||||
$ export FLASK_MAIL_ENABLED=false
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
.. group-tab:: Fish
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ set -x FLASK_SECRET_KEY "5f352379324c22463451387a0aec5d2f"
|
||||
$ set -x FLASK_MAIL_ENABLED false
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
.. group-tab:: CMD
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> set FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f"
|
||||
> set FLASK_MAIL_ENABLED=false
|
||||
> flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
.. group-tab:: Powershell
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> $env:FLASK_SECRET_KEY = "5f352379324c22463451387a0aec5d2f"
|
||||
> $env:FLASK_MAIL_ENABLED = "false"
|
||||
> flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
The variables can then be loaded and accessed via the config with a key
|
||||
equal to the environment variable name without the prefix i.e.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.config.from_prefixed_env()
|
||||
app.config["SECRET_KEY"] # Is "5f352379324c22463451387a0aec5d2f"
|
||||
|
||||
The prefix is ``FLASK_`` by default. This is configurable via the
|
||||
``prefix`` argument of :meth:`~flask.Config.from_prefixed_env`.
|
||||
|
||||
Values will be parsed to attempt to convert them to a more specific type
|
||||
than strings. By default :func:`json.loads` is used, so any valid JSON
|
||||
value is possible, including lists and dicts. This is configurable via
|
||||
the ``loads`` argument of :meth:`~flask.Config.from_prefixed_env`.
|
||||
|
||||
When adding a boolean value with the default JSON parsing, only "true"
|
||||
and "false", lowercase, are valid values. Keep in mind that any
|
||||
non-empty string is considered ``True`` by Python.
|
||||
|
||||
It is possible to set keys in nested dictionaries by separating the
|
||||
keys with double underscore (``__``). Any intermediate keys that don't
|
||||
exist on the parent dict will be initialized to an empty dict.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export FLASK_MYAPI__credentials__username=user123
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.config["MYAPI"]["credentials"]["username"] # Is "user123"
|
||||
|
||||
On Windows, environment variable keys are always uppercase, therefore
|
||||
the above example would end up as ``MYAPI__CREDENTIALS__USERNAME``.
|
||||
|
||||
For even more config loading features, including merging and
|
||||
case-insensitive Windows support, try a dedicated library such as
|
||||
Dynaconf_, which includes integration with Flask.
|
||||
|
||||
.. _Dynaconf: https://www.dynaconf.com/
|
||||
|
||||
|
||||
Configuration Best Practices
|
||||
----------------------------
|
||||
|
||||
The downside with the approach mentioned earlier is that it makes testing
|
||||
a little harder. There is no single 100% solution for this problem in
|
||||
general, but there are a couple of things you can keep in mind to improve
|
||||
that experience:
|
||||
|
||||
1. Create your application in a function and register blueprints on it.
|
||||
That way you can create multiple instances of your application with
|
||||
different configurations attached which makes unit testing a lot
|
||||
easier. You can use this to pass in configuration as needed.
|
||||
|
||||
2. Do not write code that needs the configuration at import time. If you
|
||||
limit yourself to request-only accesses to the configuration you can
|
||||
reconfigure the object later on as needed.
|
||||
|
||||
3. Make sure to load the configuration very early on, so that
|
||||
extensions can access the configuration when calling ``init_app``.
|
||||
|
||||
|
||||
.. _config-dev-prod:
|
||||
|
||||
Development / Production
|
||||
------------------------
|
||||
|
||||
Most applications need more than one configuration. There should be at
|
||||
least separate configurations for the production server and the one used
|
||||
during development. The easiest way to handle this is to use a default
|
||||
configuration that is always loaded and part of the version control, and a
|
||||
separate configuration that overrides the values as necessary as mentioned
|
||||
in the example above::
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('yourapplication.default_settings')
|
||||
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
||||
|
||||
Then you just have to add a separate :file:`config.py` file and export
|
||||
``YOURAPPLICATION_SETTINGS=/path/to/config.py`` and you are done. However
|
||||
there are alternative ways as well. For example you could use imports or
|
||||
subclassing.
|
||||
|
||||
What is very popular in the Django world is to make the import explicit in
|
||||
the config file by adding ``from yourapplication.default_settings
|
||||
import *`` to the top of the file and then overriding the changes by hand.
|
||||
You could also inspect an environment variable like
|
||||
``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc
|
||||
and import different hard-coded files based on that.
|
||||
|
||||
An interesting pattern is also to use classes and inheritance for
|
||||
configuration::
|
||||
|
||||
class Config(object):
|
||||
TESTING = False
|
||||
|
||||
class ProductionConfig(Config):
|
||||
DATABASE_URI = 'mysql://user@localhost/foo'
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DATABASE_URI = "sqlite:////tmp/foo.db"
|
||||
|
||||
class TestingConfig(Config):
|
||||
DATABASE_URI = 'sqlite:///:memory:'
|
||||
TESTING = True
|
||||
|
||||
To enable such a config you just have to call into
|
||||
:meth:`~flask.Config.from_object`::
|
||||
|
||||
app.config.from_object('configmodule.ProductionConfig')
|
||||
|
||||
Note that :meth:`~flask.Config.from_object` does not instantiate the class
|
||||
object. If you need to instantiate the class, such as to access a property,
|
||||
then you must do so before calling :meth:`~flask.Config.from_object`::
|
||||
|
||||
from configmodule import ProductionConfig
|
||||
app.config.from_object(ProductionConfig())
|
||||
|
||||
# Alternatively, import via string:
|
||||
from werkzeug.utils import import_string
|
||||
cfg = import_string('configmodule.ProductionConfig')()
|
||||
app.config.from_object(cfg)
|
||||
|
||||
Instantiating the configuration object allows you to use ``@property`` in
|
||||
your configuration classes::
|
||||
|
||||
class Config(object):
|
||||
"""Base config, uses staging database server."""
|
||||
TESTING = False
|
||||
DB_SERVER = '192.168.1.56'
|
||||
|
||||
@property
|
||||
def DATABASE_URI(self): # Note: all caps
|
||||
return f"mysql://user@{self.DB_SERVER}/foo"
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""Uses production database server."""
|
||||
DB_SERVER = '192.168.19.32'
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DB_SERVER = 'localhost'
|
||||
|
||||
class TestingConfig(Config):
|
||||
DB_SERVER = 'localhost'
|
||||
DATABASE_URI = 'sqlite:///:memory:'
|
||||
|
||||
There are many different ways and it's up to you how you want to manage
|
||||
your configuration files. However here a list of good recommendations:
|
||||
|
||||
- Keep a default configuration in version control. Either populate the
|
||||
config with this default configuration or import it in your own
|
||||
configuration files before overriding values.
|
||||
- Use an environment variable to switch between the configurations.
|
||||
This can be done from outside the Python interpreter and makes
|
||||
development and deployment much easier because you can quickly and
|
||||
easily switch between different configs without having to touch the
|
||||
code at all. If you are working often on different projects you can
|
||||
even create your own script for sourcing that activates a virtualenv
|
||||
and exports the development configuration for you.
|
||||
- Use a tool like `fabric`_ to push code and configuration separately
|
||||
to the production server(s).
|
||||
|
||||
.. _fabric: https://www.fabfile.org/
|
||||
|
||||
|
||||
.. _instance-folders:
|
||||
|
||||
Instance Folders
|
||||
----------------
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
Flask 0.8 introduces instance folders. Flask for a long time made it
|
||||
possible to refer to paths relative to the application's folder directly
|
||||
(via :attr:`Flask.root_path`). This was also how many developers loaded
|
||||
configurations stored next to the application. Unfortunately however this
|
||||
only works well if applications are not packages in which case the root
|
||||
path refers to the contents of the package.
|
||||
|
||||
With Flask 0.8 a new attribute was introduced:
|
||||
:attr:`Flask.instance_path`. It refers to a new concept called the
|
||||
“instance folder”. The instance folder is designed to not be under
|
||||
version control and be deployment specific. It's the perfect place to
|
||||
drop things that either change at runtime or configuration files.
|
||||
|
||||
You can either explicitly provide the path of the instance folder when
|
||||
creating the Flask application or you can let Flask autodetect the
|
||||
instance folder. For explicit configuration use the `instance_path`
|
||||
parameter::
|
||||
|
||||
app = Flask(__name__, instance_path='/path/to/instance/folder')
|
||||
|
||||
Please keep in mind that this path *must* be absolute when provided.
|
||||
|
||||
If the `instance_path` parameter is not provided the following default
|
||||
locations are used:
|
||||
|
||||
- Uninstalled module::
|
||||
|
||||
/myapp.py
|
||||
/instance
|
||||
|
||||
- Uninstalled package::
|
||||
|
||||
/myapp
|
||||
/__init__.py
|
||||
/instance
|
||||
|
||||
- Installed module or package::
|
||||
|
||||
$PREFIX/lib/pythonX.Y/site-packages/myapp
|
||||
$PREFIX/var/myapp-instance
|
||||
|
||||
``$PREFIX`` is the prefix of your Python installation. This can be
|
||||
``/usr`` or the path to your virtualenv. You can print the value of
|
||||
``sys.prefix`` to see what the prefix is set to.
|
||||
|
||||
Since the config object provided loading of configuration files from
|
||||
relative filenames we made it possible to change the loading via filenames
|
||||
to be relative to the instance path if wanted. The behavior of relative
|
||||
paths in config files can be flipped between “relative to the application
|
||||
root” (the default) to “relative to instance folder” via the
|
||||
`instance_relative_config` switch to the application constructor::
|
||||
|
||||
app = Flask(__name__, instance_relative_config=True)
|
||||
|
||||
Here is a full example of how to configure Flask to preload the config
|
||||
from a module and then override the config from a file in the instance
|
||||
folder if it exists::
|
||||
|
||||
app = Flask(__name__, instance_relative_config=True)
|
||||
app.config.from_object('yourapplication.default_settings')
|
||||
app.config.from_pyfile('application.cfg', silent=True)
|
||||
|
||||
The path to the instance folder can be found via the
|
||||
:attr:`Flask.instance_path`. Flask also provides a shortcut to open a
|
||||
file from the instance folder with :meth:`Flask.open_instance_resource`.
|
||||
|
||||
Example usage for both::
|
||||
|
||||
filename = os.path.join(app.instance_path, 'application.cfg')
|
||||
with open(filename) as f:
|
||||
config = f.read()
|
||||
|
||||
# or via open_instance_resource:
|
||||
with app.open_instance_resource('application.cfg') as f:
|
||||
config = f.read()
|
||||
@ -0,0 +1,8 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
See the Pallets `detailed contributing documentation <contrib_>`_ for many ways
|
||||
to contribute, including reporting issues, requesting features, asking or
|
||||
answering questions, and making PRs.
|
||||
|
||||
.. _contrib: https://palletsprojects.com/contributing/
|
||||
@ -0,0 +1,99 @@
|
||||
Debugging Application Errors
|
||||
============================
|
||||
|
||||
|
||||
In Production
|
||||
-------------
|
||||
|
||||
**Do not run the development server, or enable the built-in debugger, in
|
||||
a production environment.** The debugger allows executing arbitrary
|
||||
Python code from the browser. It's protected by a pin, but that should
|
||||
not be relied on for security.
|
||||
|
||||
Use an error logging tool, such as Sentry, as described in
|
||||
:ref:`error-logging-tools`, or enable logging and notifications as
|
||||
described in :doc:`/logging`.
|
||||
|
||||
If you have access to the server, you could add some code to start an
|
||||
external debugger if ``request.remote_addr`` matches your IP. Some IDE
|
||||
debuggers also have a remote mode so breakpoints on the server can be
|
||||
interacted with locally. Only enable a debugger temporarily.
|
||||
|
||||
|
||||
The Built-In Debugger
|
||||
---------------------
|
||||
|
||||
The built-in Werkzeug development server provides a debugger which shows
|
||||
an interactive traceback in the browser when an unhandled error occurs
|
||||
during a request. This debugger should only be used during development.
|
||||
|
||||
.. image:: _static/debugger.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of debugger in action
|
||||
|
||||
.. warning::
|
||||
|
||||
The debugger allows executing arbitrary Python code from the
|
||||
browser. It is protected by a pin, but still represents a major
|
||||
security risk. Do not run the development server or debugger in a
|
||||
production environment.
|
||||
|
||||
The debugger is enabled by default when the development server is run in debug mode.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app hello run --debug
|
||||
|
||||
When running from Python code, passing ``debug=True`` enables debug mode, which is
|
||||
mostly equivalent.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(debug=True)
|
||||
|
||||
:doc:`/server` and :doc:`/cli` have more information about running the debugger and
|
||||
debug mode. More information about the debugger can be found in the `Werkzeug
|
||||
documentation <https://werkzeug.palletsprojects.com/debug/>`__.
|
||||
|
||||
|
||||
External Debuggers
|
||||
------------------
|
||||
|
||||
External debuggers, such as those provided by IDEs, can offer a more
|
||||
powerful debugging experience than the built-in debugger. They can also
|
||||
be used to step through code during a request before an error is raised,
|
||||
or if no error is raised. Some even have a remote mode so you can debug
|
||||
code running on another machine.
|
||||
|
||||
When using an external debugger, the app should still be in debug mode, otherwise Flask
|
||||
turns unhandled errors into generic 500 error pages. However, the built-in debugger and
|
||||
reloader should be disabled so they don't interfere with the external debugger.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app hello run --debug --no-debugger --no-reload
|
||||
|
||||
When running from Python:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(debug=True, use_debugger=False, use_reloader=False)
|
||||
|
||||
Disabling these isn't required, an external debugger will continue to work with the
|
||||
following caveats.
|
||||
|
||||
- If the built-in debugger is not disabled, it will catch unhandled exceptions before
|
||||
the external debugger can.
|
||||
- If the reloader is not disabled, it could cause an unexpected reload if code changes
|
||||
during a breakpoint.
|
||||
- The development server will still catch unhandled exceptions if the built-in
|
||||
debugger is disabled, otherwise it would crash on any error. If you want that (and
|
||||
usually you don't) pass ``passthrough_errors=True`` to ``app.run``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(
|
||||
debug=True, passthrough_errors=True,
|
||||
use_debugger=False, use_reloader=False
|
||||
)
|
||||
@ -0,0 +1,66 @@
|
||||
Apache httpd
|
||||
============
|
||||
|
||||
`Apache httpd`_ is a fast, production level HTTP server. When serving
|
||||
your application with one of the WSGI servers listed in :doc:`index`, it
|
||||
is often good or necessary to put a dedicated HTTP server in front of
|
||||
it. This "reverse proxy" can handle incoming requests, TLS, and other
|
||||
security and performance concerns better than the WSGI server.
|
||||
|
||||
httpd can be installed using your system package manager, or a pre-built
|
||||
executable for Windows. Installing and running httpd itself is outside
|
||||
the scope of this doc. This page outlines the basics of configuring
|
||||
httpd to proxy your application. Be sure to read its documentation to
|
||||
understand what features are available.
|
||||
|
||||
.. _Apache httpd: https://httpd.apache.org/
|
||||
|
||||
|
||||
Domain Name
|
||||
-----------
|
||||
|
||||
Acquiring and configuring a domain name is outside the scope of this
|
||||
doc. In general, you will buy a domain name from a registrar, pay for
|
||||
server space with a hosting provider, and then point your registrar
|
||||
at the hosting provider's name servers.
|
||||
|
||||
To simulate this, you can also edit your ``hosts`` file, located at
|
||||
``/etc/hosts`` on Linux. Add a line that associates a name with the
|
||||
local IP.
|
||||
|
||||
Modern Linux systems may be configured to treat any domain name that
|
||||
ends with ``.localhost`` like this without adding it to the ``hosts``
|
||||
file.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``/etc/hosts``
|
||||
|
||||
127.0.0.1 hello.localhost
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on
|
||||
Linux. It may be different depending on your operating system. Check the
|
||||
docs and look for ``httpd.conf``.
|
||||
|
||||
Remove or comment out any existing ``DocumentRoot`` directive. Add the
|
||||
config lines below. We'll assume the WSGI server is listening locally at
|
||||
``http://127.0.0.1:8000``.
|
||||
|
||||
.. code-block:: apache
|
||||
:caption: ``/etc/httpd/conf/httpd.conf``
|
||||
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
ProxyPass / http://127.0.0.1:8000/
|
||||
RequestHeader set X-Forwarded-Proto http
|
||||
RequestHeader set X-Forwarded-Prefix /
|
||||
|
||||
The ``LoadModule`` lines might already exist. If so, make sure they are
|
||||
uncommented instead of adding them manually.
|
||||
|
||||
Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded``
|
||||
headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically
|
||||
set by ``ProxyPass``.
|
||||
@ -0,0 +1,27 @@
|
||||
ASGI
|
||||
====
|
||||
|
||||
If you'd like to use an ASGI server you will need to utilise WSGI to
|
||||
ASGI middleware. The asgiref
|
||||
`WsgiToAsgi <https://github.com/django/asgiref#wsgi-to-asgi-adapter>`_
|
||||
adapter is recommended as it integrates with the event loop used for
|
||||
Flask's :ref:`async_await` support. You can use the adapter by
|
||||
wrapping the Flask app,
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from asgiref.wsgi import WsgiToAsgi
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
...
|
||||
|
||||
asgi_app = WsgiToAsgi(app)
|
||||
|
||||
and then serving the ``asgi_app`` with the ASGI server, e.g. using
|
||||
`Hypercorn <https://github.com/pgjones/hypercorn>`_,
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
$ hypercorn module:asgi_app
|
||||
@ -0,0 +1,80 @@
|
||||
eventlet
|
||||
========
|
||||
|
||||
Prefer using :doc:`gunicorn` with eventlet workers rather than using
|
||||
`eventlet`_ directly. Gunicorn provides a much more configurable and
|
||||
production-tested server.
|
||||
|
||||
`eventlet`_ allows writing asynchronous, coroutine-based code that looks
|
||||
like standard synchronous Python. It uses `greenlet`_ to enable task
|
||||
switching without writing ``async/await`` or using ``asyncio``.
|
||||
|
||||
:doc:`gevent` is another library that does the same thing. Certain
|
||||
dependencies you have, or other considerations, may affect which of the
|
||||
two you choose to use.
|
||||
|
||||
eventlet provides a WSGI server that can handle many connections at once
|
||||
instead of one per worker process. You must actually use eventlet in
|
||||
your own code to see any benefit to using the server.
|
||||
|
||||
.. _eventlet: https://eventlet.net/
|
||||
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
When using eventlet, greenlet>=1.0 is required, otherwise context locals
|
||||
such as ``request`` will not work as expected. When using PyPy,
|
||||
PyPy>=7.3.7 is required.
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``eventlet``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install eventlet
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
To use eventlet to serve your application, write a script that imports
|
||||
its ``wsgi.server``, as well as your app or app factory.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
import eventlet
|
||||
from eventlet import wsgi
|
||||
from hello import create_app
|
||||
|
||||
app = create_app()
|
||||
wsgi.server(eventlet.listen(("127.0.0.1", 8000)), app)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python wsgi.py
|
||||
(x) wsgi starting up on http://127.0.0.1:8000
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
eventlet should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of eventlet.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port by using
|
||||
``0.0.0.0`` in the server arguments shown in the previous section.
|
||||
Don't do this when using a reverse proxy setup, otherwise it will be
|
||||
possible to bypass the proxy.
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
@ -0,0 +1,80 @@
|
||||
gevent
|
||||
======
|
||||
|
||||
Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather
|
||||
than using `gevent`_ directly. Gunicorn and uWSGI provide much more
|
||||
configurable and production-tested servers.
|
||||
|
||||
`gevent`_ allows writing asynchronous, coroutine-based code that looks
|
||||
like standard synchronous Python. It uses `greenlet`_ to enable task
|
||||
switching without writing ``async/await`` or using ``asyncio``.
|
||||
|
||||
:doc:`eventlet` is another library that does the same thing. Certain
|
||||
dependencies you have, or other considerations, may affect which of the
|
||||
two you choose to use.
|
||||
|
||||
gevent provides a WSGI server that can handle many connections at once
|
||||
instead of one per worker process. You must actually use gevent in your
|
||||
own code to see any benefit to using the server.
|
||||
|
||||
.. _gevent: https://www.gevent.org/
|
||||
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
When using gevent, greenlet>=1.0 is required, otherwise context locals
|
||||
such as ``request`` will not work as expected. When using PyPy,
|
||||
PyPy>=7.3.7 is required.
|
||||
|
||||
Create a virtualenv, install your application, then install ``gevent``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install gevent
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
To use gevent to serve your application, write a script that imports its
|
||||
``WSGIServer``, as well as your app or app factory.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from hello import create_app
|
||||
|
||||
app = create_app()
|
||||
http_server = WSGIServer(("127.0.0.1", 8000), app)
|
||||
http_server.serve_forever()
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python wsgi.py
|
||||
|
||||
No output is shown when the server starts.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
gevent should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of gevent.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port by using
|
||||
``0.0.0.0`` in the server arguments shown in the previous section. Don't
|
||||
do this when using a reverse proxy setup, otherwise it will be possible
|
||||
to bypass the proxy.
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
@ -0,0 +1,130 @@
|
||||
Gunicorn
|
||||
========
|
||||
|
||||
`Gunicorn`_ is a pure Python WSGI server with simple configuration and
|
||||
multiple worker implementations for performance tuning.
|
||||
|
||||
* It tends to integrate easily with hosting platforms.
|
||||
* It does not support Windows (but does run on WSL).
|
||||
* It is easy to install as it does not require additional dependencies
|
||||
or compilation.
|
||||
* It has built-in async worker support using gevent or eventlet.
|
||||
|
||||
This page outlines the basics of running Gunicorn. Be sure to read its
|
||||
`documentation`_ and use ``gunicorn --help`` to understand what features
|
||||
are available.
|
||||
|
||||
.. _Gunicorn: https://gunicorn.org/
|
||||
.. _documentation: https://docs.gunicorn.org/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Gunicorn is easy to install, as it does not require external
|
||||
dependencies or compilation. It runs on Windows only under WSL.
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``gunicorn``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install gunicorn
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The only required argument to Gunicorn tells it how to load your Flask
|
||||
application. The syntax is ``{module_import}:{app_variable}``.
|
||||
``module_import`` is the dotted import name to the module with your
|
||||
application. ``app_variable`` is the variable with the application. It
|
||||
can also be a function call (with any arguments) if you're using the
|
||||
app factory pattern.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# equivalent to 'from hello import app'
|
||||
$ gunicorn -w 4 'hello:app'
|
||||
|
||||
# equivalent to 'from hello import create_app; create_app()'
|
||||
$ gunicorn -w 4 'hello:create_app()'
|
||||
|
||||
Starting gunicorn 20.1.0
|
||||
Listening at: http://127.0.0.1:8000 (x)
|
||||
Using worker: sync
|
||||
Booting worker with pid: x
|
||||
Booting worker with pid: x
|
||||
Booting worker with pid: x
|
||||
Booting worker with pid: x
|
||||
|
||||
The ``-w`` option specifies the number of processes to run; a starting
|
||||
value could be ``CPU * 2``. The default is only 1 worker, which is
|
||||
probably not what you want for the default worker type.
|
||||
|
||||
Logs for each request aren't shown by default, only worker info and
|
||||
errors are shown. To show access logs on stdout, use the
|
||||
``--access-logfile=-`` option.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
Gunicorn should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of Gunicorn.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port using the
|
||||
``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup,
|
||||
otherwise it will be possible to bypass the proxy.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()'
|
||||
Listening at: http://0.0.0.0:8000 (x)
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
|
||||
|
||||
Async with gevent or eventlet
|
||||
-----------------------------
|
||||
|
||||
The default sync worker is appropriate for many use cases. If you need
|
||||
asynchronous support, Gunicorn provides workers using either `gevent`_
|
||||
or `eventlet`_. This is not the same as Python's ``async/await``, or the
|
||||
ASGI server spec. You must actually use gevent/eventlet in your own code
|
||||
to see any benefit to using the workers.
|
||||
|
||||
When using either gevent or eventlet, greenlet>=1.0 is required,
|
||||
otherwise context locals such as ``request`` will not work as expected.
|
||||
When using PyPy, PyPy>=7.3.7 is required.
|
||||
|
||||
To use gevent:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -k gevent 'hello:create_app()'
|
||||
Starting gunicorn 20.1.0
|
||||
Listening at: http://127.0.0.1:8000 (x)
|
||||
Using worker: gevent
|
||||
Booting worker with pid: x
|
||||
|
||||
To use eventlet:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -k eventlet 'hello:create_app()'
|
||||
Starting gunicorn 20.1.0
|
||||
Listening at: http://127.0.0.1:8000 (x)
|
||||
Using worker: eventlet
|
||||
Booting worker with pid: x
|
||||
|
||||
.. _gevent: https://www.gevent.org/
|
||||
.. _eventlet: https://eventlet.net/
|
||||
@ -0,0 +1,79 @@
|
||||
Deploying to Production
|
||||
=======================
|
||||
|
||||
After developing your application, you'll want to make it available
|
||||
publicly to other users. When you're developing locally, you're probably
|
||||
using the built-in development server, debugger, and reloader. These
|
||||
should not be used in production. Instead, you should use a dedicated
|
||||
WSGI server or hosting platform, some of which will be described here.
|
||||
|
||||
"Production" means "not development", which applies whether you're
|
||||
serving your application publicly to millions of users or privately /
|
||||
locally to a single user. **Do not use the development server when
|
||||
deploying to production. It is intended for use only during local
|
||||
development. It is not designed to be particularly secure, stable, or
|
||||
efficient.**
|
||||
|
||||
Self-Hosted Options
|
||||
-------------------
|
||||
|
||||
Flask is a WSGI *application*. A WSGI *server* is used to run the
|
||||
application, converting incoming HTTP requests to the standard WSGI
|
||||
environ, and converting outgoing WSGI responses to HTTP responses.
|
||||
|
||||
The primary goal of these docs is to familiarize you with the concepts
|
||||
involved in running a WSGI application using a production WSGI server
|
||||
and HTTP server. There are many WSGI servers and HTTP servers, with many
|
||||
configuration possibilities. The pages below discuss the most common
|
||||
servers, and show the basics of running each one. The next section
|
||||
discusses platforms that can manage this for you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
gunicorn
|
||||
waitress
|
||||
mod_wsgi
|
||||
uwsgi
|
||||
gevent
|
||||
eventlet
|
||||
asgi
|
||||
|
||||
WSGI servers have HTTP servers built-in. However, a dedicated HTTP
|
||||
server may be safer, more efficient, or more capable. Putting an HTTP
|
||||
server in front of the WSGI server is called a "reverse proxy."
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
proxy_fix
|
||||
nginx
|
||||
apache-httpd
|
||||
|
||||
This list is not exhaustive, and you should evaluate these and other
|
||||
servers based on your application's needs. Different servers will have
|
||||
different capabilities, configuration, and support.
|
||||
|
||||
|
||||
Hosting Platforms
|
||||
-----------------
|
||||
|
||||
There are many services available for hosting web applications without
|
||||
needing to maintain your own server, networking, domain, etc. Some
|
||||
services may have a free tier up to a certain time or bandwidth. Many of
|
||||
these services use one of the WSGI servers described above, or a similar
|
||||
interface. The links below are for some of the most common platforms,
|
||||
which have instructions for Flask, WSGI, or Python.
|
||||
|
||||
- `PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||
- `Google App Engine <https://cloud.google.com/appengine/docs/standard/python3/building-app>`_
|
||||
- `Google Cloud Run <https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-python-service>`_
|
||||
- `AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||
- `Microsoft Azure <https://docs.microsoft.com/en-us/azure/app-service/quickstart-python>`_
|
||||
|
||||
This list is not exhaustive, and you should evaluate these and other
|
||||
services based on your application's needs. Different services will have
|
||||
different capabilities, configuration, pricing, and support.
|
||||
|
||||
You'll probably need to :doc:`proxy_fix` when using most hosting
|
||||
platforms.
|
||||
@ -0,0 +1,94 @@
|
||||
mod_wsgi
|
||||
========
|
||||
|
||||
`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server.
|
||||
The modern `mod_wsgi-express`_ command makes it easy to configure and
|
||||
start the server without needing to write Apache httpd configuration.
|
||||
|
||||
* Tightly integrated with Apache httpd.
|
||||
* Supports Windows directly.
|
||||
* Requires a compiler and the Apache development headers to install.
|
||||
* Does not require a reverse proxy setup.
|
||||
|
||||
This page outlines the basics of running mod_wsgi-express, not the more
|
||||
complex installation and configuration with httpd. Be sure to read the
|
||||
`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to
|
||||
understand what features are available.
|
||||
|
||||
.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/
|
||||
.. _mod_wsgi: https://modwsgi.readthedocs.io/
|
||||
.. _Apache httpd: https://httpd.apache.org/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Installing mod_wsgi requires a compiler and the Apache server and
|
||||
development headers installed. You will get an error if they are not.
|
||||
How to install them depends on the OS and package manager that you use.
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``mod_wsgi``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install mod_wsgi
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The only argument to ``mod_wsgi-express`` specifies a script containing
|
||||
your Flask application, which must be called ``application``. You can
|
||||
write a small script to import your app with this name, or to create it
|
||||
if using the app factory pattern.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from hello import app
|
||||
|
||||
application = app
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from hello import create_app
|
||||
|
||||
application = create_app()
|
||||
|
||||
Now run the ``mod_wsgi-express start-server`` command.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ mod_wsgi-express start-server wsgi.py --processes 4
|
||||
|
||||
The ``--processes`` option specifies the number of worker processes to
|
||||
run; a starting value could be ``CPU * 2``.
|
||||
|
||||
Logs for each request aren't show in the terminal. If an error occurs,
|
||||
its information is written to the error log file shown when starting the
|
||||
server.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
Unlike the other WSGI servers in these docs, mod_wsgi can be run as
|
||||
root to bind to privileged ports like 80 and 443. However, it must be
|
||||
configured to drop permissions to a different user and group for the
|
||||
worker processes.
|
||||
|
||||
For example, if you created a ``hello`` user and group, you should
|
||||
install your virtualenv and application as that user, then tell
|
||||
mod_wsgi to drop to that user after starting.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ sudo /home/hello/.venv/bin/mod_wsgi-express start-server \
|
||||
/home/hello/wsgi.py \
|
||||
--user hello --group hello --port 80 --processes 4
|
||||
@ -0,0 +1,69 @@
|
||||
nginx
|
||||
=====
|
||||
|
||||
`nginx`_ is a fast, production level HTTP server. When serving your
|
||||
application with one of the WSGI servers listed in :doc:`index`, it is
|
||||
often good or necessary to put a dedicated HTTP server in front of it.
|
||||
This "reverse proxy" can handle incoming requests, TLS, and other
|
||||
security and performance concerns better than the WSGI server.
|
||||
|
||||
Nginx can be installed using your system package manager, or a pre-built
|
||||
executable for Windows. Installing and running Nginx itself is outside
|
||||
the scope of this doc. This page outlines the basics of configuring
|
||||
Nginx to proxy your application. Be sure to read its documentation to
|
||||
understand what features are available.
|
||||
|
||||
.. _nginx: https://nginx.org/
|
||||
|
||||
|
||||
Domain Name
|
||||
-----------
|
||||
|
||||
Acquiring and configuring a domain name is outside the scope of this
|
||||
doc. In general, you will buy a domain name from a registrar, pay for
|
||||
server space with a hosting provider, and then point your registrar
|
||||
at the hosting provider's name servers.
|
||||
|
||||
To simulate this, you can also edit your ``hosts`` file, located at
|
||||
``/etc/hosts`` on Linux. Add a line that associates a name with the
|
||||
local IP.
|
||||
|
||||
Modern Linux systems may be configured to treat any domain name that
|
||||
ends with ``.localhost`` like this without adding it to the ``hosts``
|
||||
file.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``/etc/hosts``
|
||||
|
||||
127.0.0.1 hello.localhost
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The nginx configuration is located at ``/etc/nginx/nginx.conf`` on
|
||||
Linux. It may be different depending on your operating system. Check the
|
||||
docs and look for ``nginx.conf``.
|
||||
|
||||
Remove or comment out any existing ``server`` section. Add a ``server``
|
||||
section and use the ``proxy_pass`` directive to point to the address the
|
||||
WSGI server is listening on. We'll assume the WSGI server is listening
|
||||
locally at ``http://127.0.0.1:8000``.
|
||||
|
||||
.. code-block:: nginx
|
||||
:caption: ``/etc/nginx.conf``
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000/;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Prefix /;
|
||||
}
|
||||
}
|
||||
|
||||
Then :doc:`proxy_fix` so that your application uses these headers.
|
||||
@ -0,0 +1,33 @@
|
||||
Tell Flask it is Behind a Proxy
|
||||
===============================
|
||||
|
||||
When using a reverse proxy, or many Python hosting platforms, the proxy
|
||||
will intercept and forward all external requests to the local WSGI
|
||||
server.
|
||||
|
||||
From the WSGI server and Flask application's perspectives, requests are
|
||||
now coming from the HTTP server to the local address, rather than from
|
||||
the remote address to the external server address.
|
||||
|
||||
HTTP servers should set ``X-Forwarded-`` headers to pass on the real
|
||||
values to the application. The application can then be told to trust and
|
||||
use those values by wrapping it with the
|
||||
:doc:`werkzeug:middleware/proxy_fix` middleware provided by Werkzeug.
|
||||
|
||||
This middleware should only be used if the application is actually
|
||||
behind a proxy, and should be configured with the number of proxies that
|
||||
are chained in front of it. Not all proxies set all the headers. Since
|
||||
incoming headers can be faked, you must set how many proxies are setting
|
||||
each header so the middleware knows what to trust.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
app.wsgi_app = ProxyFix(
|
||||
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
|
||||
)
|
||||
|
||||
Remember, only apply this middleware if you are behind a proxy, and set
|
||||
the correct number of proxies that set each header. It can be a security
|
||||
issue if you get this configuration wrong.
|
||||
@ -0,0 +1,145 @@
|
||||
uWSGI
|
||||
=====
|
||||
|
||||
`uWSGI`_ is a fast, compiled server suite with extensive configuration
|
||||
and capabilities beyond a basic server.
|
||||
|
||||
* It can be very performant due to being a compiled program.
|
||||
* It is complex to configure beyond the basic application, and has so
|
||||
many options that it can be difficult for beginners to understand.
|
||||
* It does not support Windows (but does run on WSL).
|
||||
* It requires a compiler to install in some cases.
|
||||
|
||||
This page outlines the basics of running uWSGI. Be sure to read its
|
||||
documentation to understand what features are available.
|
||||
|
||||
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
uWSGI has multiple ways to install it. The most straightforward is to
|
||||
install the ``pyuwsgi`` package, which provides precompiled wheels for
|
||||
common platforms. However, it does not provide SSL support, which can be
|
||||
provided with a reverse proxy instead.
|
||||
|
||||
Create a virtualenv, install your application, then install ``pyuwsgi``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install pyuwsgi
|
||||
|
||||
If you have a compiler available, you can install the ``uwsgi`` package
|
||||
instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
|
||||
Either method will include SSL support.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install uwsgi
|
||||
|
||||
# or
|
||||
$ pip install --no-binary pyuwsgi pyuwsgi
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The most basic way to run uWSGI is to tell it to start an HTTP server
|
||||
and import your application.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app
|
||||
|
||||
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
|
||||
*** Operational MODE: preforking ***
|
||||
mounting hello:app on /
|
||||
spawned uWSGI master process (pid: x)
|
||||
spawned uWSGI worker 1 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 2 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 3 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 4 (pid: x, cores: 1)
|
||||
spawned uWSGI http 1 (pid: x)
|
||||
|
||||
If you're using the app factory pattern, you'll need to create a small
|
||||
Python file to create the app, then point uWSGI at that.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from hello import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app
|
||||
|
||||
The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The
|
||||
``--master`` option specifies the standard worker manager. The ``-p``
|
||||
option starts 4 worker processes; a starting value could be ``CPU * 2``.
|
||||
The ``-w`` option tells uWSGI how to import your application
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
uWSGI should not be run as root with the configuration shown in this doc
|
||||
because it would cause your application code to run as root, which is
|
||||
not secure. However, this means it will not be possible to bind to port
|
||||
80 or 443. Instead, a reverse proxy such as :doc:`nginx` or
|
||||
:doc:`apache-httpd` should be used in front of uWSGI. It is possible to
|
||||
run uWSGI as root securely, but that is beyond the scope of this doc.
|
||||
|
||||
uWSGI has optimized integration with `Nginx uWSGI`_ and
|
||||
`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using
|
||||
a standard HTTP proxy. That configuration is beyond the scope of this
|
||||
doc, see the links for more information.
|
||||
|
||||
.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
|
||||
.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi
|
||||
|
||||
You can bind to all external IPs on a non-privileged port using the
|
||||
``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy
|
||||
setup, otherwise it will be possible to bypass the proxy.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
|
||||
|
||||
Async with gevent
|
||||
-----------------
|
||||
|
||||
The default sync worker is appropriate for many use cases. If you need
|
||||
asynchronous support, uWSGI provides a `gevent`_ worker. This is not the
|
||||
same as Python's ``async/await``, or the ASGI server spec. You must
|
||||
actually use gevent in your own code to see any benefit to using the
|
||||
worker.
|
||||
|
||||
When using gevent, greenlet>=1.0 is required, otherwise context locals
|
||||
such as ``request`` will not work as expected. When using PyPy,
|
||||
PyPy>=7.3.7 is required.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app
|
||||
|
||||
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
|
||||
*** Operational MODE: async ***
|
||||
mounting hello:app on /
|
||||
spawned uWSGI master process (pid: x)
|
||||
spawned uWSGI worker 1 (pid: x, cores: 100)
|
||||
spawned uWSGI http 1 (pid: x)
|
||||
*** running gevent loop engine [addr:x] ***
|
||||
|
||||
|
||||
.. _gevent: https://www.gevent.org/
|
||||
@ -0,0 +1,75 @@
|
||||
Waitress
|
||||
========
|
||||
|
||||
`Waitress`_ is a pure Python WSGI server.
|
||||
|
||||
* It is easy to configure.
|
||||
* It supports Windows directly.
|
||||
* It is easy to install as it does not require additional dependencies
|
||||
or compilation.
|
||||
* It does not support streaming requests, full request data is always
|
||||
buffered.
|
||||
* It uses a single process with multiple thread workers.
|
||||
|
||||
This page outlines the basics of running Waitress. Be sure to read its
|
||||
documentation and ``waitress-serve --help`` to understand what features
|
||||
are available.
|
||||
|
||||
.. _Waitress: https://docs.pylonsproject.org/projects/waitress/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``waitress``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install waitress
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The only required argument to ``waitress-serve`` tells it how to load
|
||||
your Flask application. The syntax is ``{module}:{app}``. ``module`` is
|
||||
the dotted import name to the module with your application. ``app`` is
|
||||
the variable with the application. If you're using the app factory
|
||||
pattern, use ``--call {module}:{factory}`` instead.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# equivalent to 'from hello import app'
|
||||
$ waitress-serve --host 127.0.0.1 hello:app
|
||||
|
||||
# equivalent to 'from hello import create_app; create_app()'
|
||||
$ waitress-serve --host 127.0.0.1 --call hello:create_app
|
||||
|
||||
Serving on http://127.0.0.1:8080
|
||||
|
||||
The ``--host`` option binds the server to local ``127.0.0.1`` only.
|
||||
|
||||
Logs for each request aren't shown, only errors are shown. Logging can
|
||||
be configured through the Python interface instead of the command line.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
Waitress should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of Waitress.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port by not
|
||||
specifying the ``--host`` option. Don't do this when using a reverse
|
||||
proxy setup, otherwise it will be possible to bypass the proxy.
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
@ -0,0 +1,229 @@
|
||||
Design Decisions in Flask
|
||||
=========================
|
||||
|
||||
If you are curious why Flask does certain things the way it does and not
|
||||
differently, this section is for you. This should give you an idea about
|
||||
some of the design decisions that may appear arbitrary and surprising at
|
||||
first, especially in direct comparison with other frameworks.
|
||||
|
||||
|
||||
The Explicit Application Object
|
||||
-------------------------------
|
||||
|
||||
A Python web application based on WSGI has to have one central callable
|
||||
object that implements the actual application. In Flask this is an
|
||||
instance of the :class:`~flask.Flask` class. Each Flask application has
|
||||
to create an instance of this class itself and pass it the name of the
|
||||
module, but why can't Flask do that itself?
|
||||
|
||||
Without such an explicit application object the following code::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Hello World!'
|
||||
|
||||
Would look like this instead::
|
||||
|
||||
from hypothetical_flask import route
|
||||
|
||||
@route('/')
|
||||
def index():
|
||||
return 'Hello World!'
|
||||
|
||||
There are three major reasons for this. The most important one is that
|
||||
implicit application objects require that there may only be one instance at
|
||||
the time. There are ways to fake multiple applications with a single
|
||||
application object, like maintaining a stack of applications, but this
|
||||
causes some problems I won't outline here in detail. Now the question is:
|
||||
when does a microframework need more than one application at the same
|
||||
time? A good example for this is unit testing. When you want to test
|
||||
something it can be very helpful to create a minimal application to test
|
||||
specific behavior. When the application object is deleted everything it
|
||||
allocated will be freed again.
|
||||
|
||||
Another thing that becomes possible when you have an explicit object lying
|
||||
around in your code is that you can subclass the base class
|
||||
(:class:`~flask.Flask`) to alter specific behavior. This would not be
|
||||
possible without hacks if the object were created ahead of time for you
|
||||
based on a class that is not exposed to you.
|
||||
|
||||
But there is another very important reason why Flask depends on an
|
||||
explicit instantiation of that class: the package name. Whenever you
|
||||
create a Flask instance you usually pass it `__name__` as package name.
|
||||
Flask depends on that information to properly load resources relative
|
||||
to your module. With Python's outstanding support for reflection it can
|
||||
then access the package to figure out where the templates and static files
|
||||
are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there
|
||||
are frameworks around that do not need any configuration and will still be
|
||||
able to load templates relative to your application module. But they have
|
||||
to use the current working directory for that, which is a very unreliable
|
||||
way to determine where the application is. The current working directory
|
||||
is process-wide and if you are running multiple applications in one
|
||||
process (which could happen in a webserver without you knowing) the paths
|
||||
will be off. Worse: many webservers do not set the working directory to
|
||||
the directory of your application but to the document root which does not
|
||||
have to be the same folder.
|
||||
|
||||
The third reason is "explicit is better than implicit". That object is
|
||||
your WSGI application, you don't have to remember anything else. If you
|
||||
want to apply a WSGI middleware, just wrap it and you're done (though
|
||||
there are better ways to do that so that you do not lose the reference
|
||||
to the application object :meth:`~flask.Flask.wsgi_app`).
|
||||
|
||||
Furthermore this design makes it possible to use a factory function to
|
||||
create the application which is very helpful for unit testing and similar
|
||||
things (:doc:`/patterns/appfactories`).
|
||||
|
||||
The Routing System
|
||||
------------------
|
||||
|
||||
Flask uses the Werkzeug routing system which was designed to
|
||||
automatically order routes by complexity. This means that you can declare
|
||||
routes in arbitrary order and they will still work as expected. This is a
|
||||
requirement if you want to properly implement decorator based routing
|
||||
since decorators could be fired in undefined order when the application is
|
||||
split into multiple modules.
|
||||
|
||||
Another design decision with the Werkzeug routing system is that routes
|
||||
in Werkzeug try to ensure that URLs are unique. Werkzeug will go quite far
|
||||
with that in that it will automatically redirect to a canonical URL if a route
|
||||
is ambiguous.
|
||||
|
||||
|
||||
One Template Engine
|
||||
-------------------
|
||||
|
||||
Flask decides on one template engine: Jinja. Why doesn't Flask have a
|
||||
pluggable template engine interface? You can obviously use a different
|
||||
template engine, but Flask will still configure Jinja for you. While
|
||||
that limitation that Jinja is *always* configured will probably go away,
|
||||
the decision to bundle one template engine and use that will not.
|
||||
|
||||
Template engines are like programming languages and each of those engines
|
||||
has a certain understanding about how things work. On the surface they
|
||||
all work the same: you tell the engine to evaluate a template with a set
|
||||
of variables and take the return value as string.
|
||||
|
||||
But that's about where similarities end. Jinja for example has an
|
||||
extensive filter system, a certain way to do template inheritance,
|
||||
support for reusable blocks (macros) that can be used from inside
|
||||
templates and also from Python code, supports iterative template
|
||||
rendering, configurable syntax and more. On the other hand an engine
|
||||
like Genshi is based on XML stream evaluation, template inheritance by
|
||||
taking the availability of XPath into account and more. Mako on the
|
||||
other hand treats templates similar to Python modules.
|
||||
|
||||
When it comes to connecting a template engine with an application or
|
||||
framework there is more than just rendering templates. For instance,
|
||||
Flask uses Jinja's extensive autoescaping support. Also it provides
|
||||
ways to access macros from Jinja templates.
|
||||
|
||||
A template abstraction layer that would not take the unique features of
|
||||
the template engines away is a science on its own and a too large
|
||||
undertaking for a microframework like Flask.
|
||||
|
||||
Furthermore extensions can then easily depend on one template language
|
||||
being present. You can easily use your own templating language, but an
|
||||
extension could still depend on Jinja itself.
|
||||
|
||||
|
||||
What does "micro" mean?
|
||||
-----------------------
|
||||
|
||||
“Micro” does not mean that your whole web application has to fit into a single
|
||||
Python file (although it certainly can), nor does it mean that Flask is lacking
|
||||
in functionality. The "micro" in microframework means Flask aims to keep the
|
||||
core simple but extensible. Flask won't make many decisions for you, such as
|
||||
what database to use. Those decisions that it does make, such as what
|
||||
templating engine to use, are easy to change. Everything else is up to you, so
|
||||
that Flask can be everything you need and nothing you don't.
|
||||
|
||||
By default, Flask does not include a database abstraction layer, form
|
||||
validation or anything else where different libraries already exist that can
|
||||
handle that. Instead, Flask supports extensions to add such functionality to
|
||||
your application as if it was implemented in Flask itself. Numerous extensions
|
||||
provide database integration, form validation, upload handling, various open
|
||||
authentication technologies, and more. Flask may be "micro", but it's ready for
|
||||
production use on a variety of needs.
|
||||
|
||||
Why does Flask call itself a microframework and yet it depends on two
|
||||
libraries (namely Werkzeug and Jinja). Why shouldn't it? If we look
|
||||
over to the Ruby side of web development there we have a protocol very
|
||||
similar to WSGI. Just that it's called Rack there, but besides that it
|
||||
looks very much like a WSGI rendition for Ruby. But nearly all
|
||||
applications in Ruby land do not work with Rack directly, but on top of a
|
||||
library with the same name. This Rack library has two equivalents in
|
||||
Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but
|
||||
from my understanding it's sort of deprecated in favour of WebOb. The
|
||||
development of WebOb and Werkzeug started side by side with similar ideas
|
||||
in mind: be a good implementation of WSGI for other applications to take
|
||||
advantage.
|
||||
|
||||
Flask is a framework that takes advantage of the work already done by
|
||||
Werkzeug to properly interface WSGI (which can be a complex task at
|
||||
times). Thanks to recent developments in the Python package
|
||||
infrastructure, packages with dependencies are no longer an issue and
|
||||
there are very few reasons against having libraries that depend on others.
|
||||
|
||||
|
||||
Context Locals
|
||||
--------------
|
||||
|
||||
Flask uses special context locals and proxies to provide access to the
|
||||
current app and request data to any code running during a request, CLI command,
|
||||
etc. Context locals are specific to the worker handling the activity, such as a
|
||||
thread, process, coroutine, or greenlet.
|
||||
|
||||
The context and proxies help solve two development issues: circular imports, and
|
||||
passing around global data. :data:`.current_app` can be used to access the
|
||||
application object without needing to import the app object directly, avoiding
|
||||
circular import issues. :data:`.request`, :data:`.session`, and :data:`.g` can
|
||||
be imported to access the current data for the request, rather than needing to
|
||||
pass them as arguments through every single function in your project.
|
||||
|
||||
|
||||
Async/await and ASGI support
|
||||
----------------------------
|
||||
|
||||
Flask supports ``async`` coroutines for view functions by executing the
|
||||
coroutine on a separate thread instead of using an event loop on the
|
||||
main thread as an async-first (ASGI) framework would. This is necessary
|
||||
for Flask to remain backwards compatible with extensions and code built
|
||||
before ``async`` was introduced into Python. This compromise introduces
|
||||
a performance cost compared with the ASGI frameworks, due to the
|
||||
overhead of the threads.
|
||||
|
||||
Due to how tied to WSGI Flask's code is, it's not clear if it's possible
|
||||
to make the ``Flask`` class support ASGI and WSGI at the same time. Work
|
||||
is currently being done in Werkzeug to work with ASGI, which may
|
||||
eventually enable support in Flask as well.
|
||||
|
||||
See :doc:`/async-await` for more discussion.
|
||||
|
||||
|
||||
What Flask is, What Flask is Not
|
||||
--------------------------------
|
||||
|
||||
Flask will never have a database layer. It will not have a form library
|
||||
or anything else in that direction. Flask itself just bridges to Werkzeug
|
||||
to implement a proper WSGI application and to Jinja to handle templating.
|
||||
It also binds to a few common standard library packages such as logging.
|
||||
Everything else is up for extensions.
|
||||
|
||||
Why is this the case? Because people have different preferences and
|
||||
requirements and Flask could not meet those if it would force any of this
|
||||
into the core. The majority of web applications will need a template
|
||||
engine in some sort. However not every application needs a SQL database.
|
||||
|
||||
As your codebase grows, you are free to make the design decisions appropriate
|
||||
for your project. Flask will continue to provide a very simple glue layer to
|
||||
the best that Python has to offer. You can implement advanced patterns in
|
||||
SQLAlchemy or another database tool, introduce non-relational data persistence
|
||||
as appropriate, and take advantage of framework-agnostic tools built for WSGI,
|
||||
the Python web interface.
|
||||
|
||||
The idea of Flask is to build a good foundation for all applications.
|
||||
Everything else is up to you or extensions.
|
||||
@ -0,0 +1,523 @@
|
||||
Handling Application Errors
|
||||
===========================
|
||||
|
||||
Applications fail, servers fail. Sooner or later you will see an exception
|
||||
in production. Even if your code is 100% correct, you will still see
|
||||
exceptions from time to time. Why? Because everything else involved will
|
||||
fail. Here are some situations where perfectly fine code can lead to server
|
||||
errors:
|
||||
|
||||
- the client terminated the request early and the application was still
|
||||
reading from the incoming data
|
||||
- the database server was overloaded and could not handle the query
|
||||
- a filesystem is full
|
||||
- a harddrive crashed
|
||||
- a backend server overloaded
|
||||
- a programming error in a library you are using
|
||||
- network connection of the server to another system failed
|
||||
|
||||
And that's just a small sample of issues you could be facing. So how do we
|
||||
deal with that sort of problem? By default if your application runs in
|
||||
production mode, and an exception is raised Flask will display a very simple
|
||||
page for you and log the exception to the :attr:`~flask.Flask.logger`.
|
||||
|
||||
But there is more you can do, and we will cover some better setups to deal
|
||||
with errors including custom exceptions and 3rd party tools.
|
||||
|
||||
|
||||
.. _error-logging-tools:
|
||||
|
||||
Error Logging Tools
|
||||
-------------------
|
||||
|
||||
Sending error mails, even if just for critical ones, can become
|
||||
overwhelming if enough users are hitting the error and log files are
|
||||
typically never looked at. This is why we recommend using `Sentry
|
||||
<https://sentry.io/>`_ for dealing with application errors. It's
|
||||
available as a source-available project `on GitHub
|
||||
<https://github.com/getsentry/sentry>`_ and is also available as a `hosted version
|
||||
<https://sentry.io/signup/>`_ which you can try for free. Sentry
|
||||
aggregates duplicate errors, captures the full stack trace and local
|
||||
variables for debugging, and sends you mails based on new errors or
|
||||
frequency thresholds.
|
||||
|
||||
To use Sentry you need to install the ``sentry-sdk`` client with extra
|
||||
``flask`` dependencies.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install sentry-sdk[flask]
|
||||
|
||||
And then add this to your Flask app:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
|
||||
sentry_sdk.init('YOUR_DSN_HERE', integrations=[FlaskIntegration()])
|
||||
|
||||
The ``YOUR_DSN_HERE`` value needs to be replaced with the DSN value you
|
||||
get from your Sentry installation.
|
||||
|
||||
After installation, failures leading to an Internal Server Error
|
||||
are automatically reported to Sentry and from there you can
|
||||
receive error notifications.
|
||||
|
||||
See also:
|
||||
|
||||
- Sentry also supports catching errors from a worker queue
|
||||
(RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs
|
||||
<https://docs.sentry.io/platforms/python/>`__ for more information.
|
||||
- `Flask-specific documentation <https://docs.sentry.io/platforms/python/guides/flask/>`__
|
||||
|
||||
|
||||
Error Handlers
|
||||
--------------
|
||||
|
||||
When an error occurs in Flask, an appropriate `HTTP status code
|
||||
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Status>`__ will be
|
||||
returned. 400-499 indicate errors with the client's request data, or
|
||||
about the data requested. 500-599 indicate errors with the server or
|
||||
application itself.
|
||||
|
||||
You might want to show custom error pages to the user when an error occurs.
|
||||
This can be done by registering error handlers.
|
||||
|
||||
An error handler is a function that returns a response when a type of error is
|
||||
raised, similar to how a view is a function that returns a response when a
|
||||
request URL is matched. It is passed the instance of the error being handled,
|
||||
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`.
|
||||
|
||||
The status code of the response will not be set to the handler's code. Make
|
||||
sure to provide the appropriate HTTP status code when returning a response from
|
||||
a handler.
|
||||
|
||||
|
||||
Registering
|
||||
```````````
|
||||
|
||||
Register handlers by decorating a function with
|
||||
:meth:`~flask.Flask.errorhandler`. Or use
|
||||
:meth:`~flask.Flask.register_error_handler` to register the function later.
|
||||
Remember to set the error code when returning the response.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
||||
def handle_bad_request(e):
|
||||
return 'bad request!', 400
|
||||
|
||||
# or, without the decorator
|
||||
app.register_error_handler(400, handle_bad_request)
|
||||
|
||||
:exc:`werkzeug.exceptions.HTTPException` subclasses like
|
||||
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
|
||||
when registering handlers. (``BadRequest.code == 400``)
|
||||
|
||||
Non-standard HTTP codes cannot be registered by code because they are not known
|
||||
by Werkzeug. Instead, define a subclass of
|
||||
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
|
||||
register and raise that exception class.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class InsufficientStorage(werkzeug.exceptions.HTTPException):
|
||||
code = 507
|
||||
description = 'Not enough storage space.'
|
||||
|
||||
app.register_error_handler(InsufficientStorage, handle_507)
|
||||
|
||||
raise InsufficientStorage()
|
||||
|
||||
Handlers can be registered for any exception class, not just
|
||||
:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status
|
||||
codes. Handlers can be registered for a specific class, or for all subclasses
|
||||
of a parent class.
|
||||
|
||||
|
||||
Handling
|
||||
````````
|
||||
|
||||
When building a Flask application you *will* run into exceptions. If some part
|
||||
of your code breaks while handling a request (and you have no error handlers
|
||||
registered), a "500 Internal Server Error"
|
||||
(:exc:`~werkzeug.exceptions.InternalServerError`) will be returned by default.
|
||||
Similarly, "404 Not Found"
|
||||
(:exc:`~werkzeug.exceptions.NotFound`) error will occur if a request is sent to an unregistered route.
|
||||
If a route receives an unallowed request method, a "405 Method Not Allowed"
|
||||
(:exc:`~werkzeug.exceptions.MethodNotAllowed`) will be raised. These are all
|
||||
subclasses of :class:`~werkzeug.exceptions.HTTPException` and are provided by
|
||||
default in Flask.
|
||||
|
||||
Flask gives you the ability to raise any HTTP exception registered by
|
||||
Werkzeug. However, the default HTTP exceptions return simple exception
|
||||
pages. You might want to show custom error pages to the user when an error occurs.
|
||||
This can be done by registering error handlers.
|
||||
|
||||
When Flask catches an exception while handling a request, it is first looked up by code.
|
||||
If no handler is registered for the code, Flask looks up the error by its class hierarchy; the most specific handler is chosen.
|
||||
If no handler is registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
||||
generic message about their code, while other exceptions are converted to a
|
||||
generic "500 Internal Server Error".
|
||||
|
||||
For example, if an instance of :exc:`ConnectionRefusedError` is raised,
|
||||
and a handler is registered for :exc:`ConnectionError` and
|
||||
:exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError`
|
||||
handler is called with the exception instance to generate the response.
|
||||
|
||||
Handlers registered on the blueprint take precedence over those registered
|
||||
globally on the application, assuming a blueprint is handling the request that
|
||||
raises the exception. However, the blueprint cannot handle 404 routing errors
|
||||
because the 404 occurs at the routing level before the blueprint can be
|
||||
determined.
|
||||
|
||||
|
||||
Generic Exception Handlers
|
||||
``````````````````````````
|
||||
|
||||
It is possible to register error handlers for very generic base classes
|
||||
such as ``HTTPException`` or even ``Exception``. However, be aware that
|
||||
these will catch more than you might expect.
|
||||
|
||||
For example, an error handler for ``HTTPException`` might be useful for turning
|
||||
the default HTML errors pages into JSON. However, this
|
||||
handler will trigger for things you don't cause directly, such as 404
|
||||
and 405 errors during routing. Be sure to craft your handler carefully
|
||||
so you don't lose information about the HTTP error.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import json
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_exception(e):
|
||||
"""Return JSON instead of HTML for HTTP errors."""
|
||||
# start with the correct headers and status code from the error
|
||||
response = e.get_response()
|
||||
# replace the body with JSON
|
||||
response.data = json.dumps({
|
||||
"code": e.code,
|
||||
"name": e.name,
|
||||
"description": e.description,
|
||||
})
|
||||
response.content_type = "application/json"
|
||||
return response
|
||||
|
||||
An error handler for ``Exception`` might seem useful for changing how
|
||||
all errors, even unhandled ones, are presented to the user. However,
|
||||
this is similar to doing ``except Exception:`` in Python, it will
|
||||
capture *all* otherwise unhandled errors, including all HTTP status
|
||||
codes.
|
||||
|
||||
In most cases it will be safer to register handlers for more
|
||||
specific exceptions. Since ``HTTPException`` instances are valid WSGI
|
||||
responses, you could also pass them through directly.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
# pass through HTTP errors
|
||||
if isinstance(e, HTTPException):
|
||||
return e
|
||||
|
||||
# now you're handling non-HTTP exceptions only
|
||||
return render_template("500_generic.html", e=e), 500
|
||||
|
||||
Error handlers still respect the exception class hierarchy. If you
|
||||
register handlers for both ``HTTPException`` and ``Exception``, the
|
||||
``Exception`` handler will not handle ``HTTPException`` subclasses
|
||||
because the ``HTTPException`` handler is more specific.
|
||||
|
||||
|
||||
Unhandled Exceptions
|
||||
````````````````````
|
||||
|
||||
When there is no error handler registered for an exception, a 500
|
||||
Internal Server Error will be returned instead. See
|
||||
:meth:`flask.Flask.handle_exception` for information about this
|
||||
behavior.
|
||||
|
||||
If there is an error handler registered for ``InternalServerError``,
|
||||
this will be invoked. As of Flask 1.1.0, this error handler will always
|
||||
be passed an instance of ``InternalServerError``, not the original
|
||||
unhandled error.
|
||||
|
||||
The original error is available as ``e.original_exception``.
|
||||
|
||||
An error handler for "500 Internal Server Error" will be passed uncaught
|
||||
exceptions in addition to explicit 500 errors. In debug mode, a handler
|
||||
for "500 Internal Server Error" will not be used. Instead, the
|
||||
interactive debugger will be shown.
|
||||
|
||||
|
||||
Custom Error Pages
|
||||
------------------
|
||||
|
||||
Sometimes when building a Flask application, you might want to raise a
|
||||
:exc:`~werkzeug.exceptions.HTTPException` to signal to the user that
|
||||
something is wrong with the request. Fortunately, Flask comes with a handy
|
||||
:func:`~flask.abort` function that aborts a request with a HTTP error from
|
||||
werkzeug as desired. It will also provide a plain black and white error page
|
||||
for you with a basic description, but nothing fancy.
|
||||
|
||||
Depending on the error code it is less or more likely for the user to
|
||||
actually see such an error.
|
||||
|
||||
Consider the code below, we might have a user profile route, and if the user
|
||||
fails to pass a username we can raise a "400 Bad Request". If the user passes a
|
||||
username and we can't find it, we raise a "404 Not Found".
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import abort, render_template, request
|
||||
|
||||
# a username needs to be supplied in the query args
|
||||
# a successful request would be like /profile?username=jack
|
||||
@app.route("/profile")
|
||||
def user_profile():
|
||||
username = request.arg.get("username")
|
||||
# if a username isn't supplied in the request, return a 400 bad request
|
||||
if username is None:
|
||||
abort(400)
|
||||
|
||||
user = get_user(username=username)
|
||||
# if a user can't be found by their username, return 404 not found
|
||||
if user is None:
|
||||
abort(404)
|
||||
|
||||
return render_template("profile.html", user=user)
|
||||
|
||||
Here is another example implementation for a "404 Page Not Found" exception:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# note that we set the 404 status explicitly
|
||||
return render_template('404.html'), 404
|
||||
|
||||
When using :doc:`/patterns/appfactories`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(404, page_not_found)
|
||||
return app
|
||||
|
||||
An example template might be this:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Page Not Found{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Page Not Found</h1>
|
||||
<p>What you were looking for is just not there.
|
||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Further Examples
|
||||
````````````````
|
||||
|
||||
The above examples wouldn't actually be an improvement on the default
|
||||
exception pages. We can create a custom 500.html template like this:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Internal Server Error{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Internal Server Error</h1>
|
||||
<p>Oops... we seem to have made a mistake, sorry!</p>
|
||||
<p><a href="{{ url_for('index') }}">Go somewhere nice instead</a>
|
||||
{% endblock %}
|
||||
|
||||
It can be implemented by rendering the template on "500 Internal Server Error":
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
# note that we set the 500 status explicitly
|
||||
return render_template('500.html'), 500
|
||||
|
||||
When using :doc:`/patterns/appfactories`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def internal_server_error(e):
|
||||
return render_template('500.html'), 500
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(500, internal_server_error)
|
||||
return app
|
||||
|
||||
When using :doc:`/blueprints`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
blog = Blueprint('blog', __name__)
|
||||
|
||||
# as a decorator
|
||||
@blog.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
return render_template('500.html'), 500
|
||||
|
||||
# or with register_error_handler
|
||||
blog.register_error_handler(500, internal_server_error)
|
||||
|
||||
|
||||
Blueprint Error Handlers
|
||||
------------------------
|
||||
|
||||
In :doc:`/blueprints`, most error handlers will work as expected.
|
||||
However, there is a caveat concerning handlers for 404 and 405
|
||||
exceptions. These error handlers are only invoked from an appropriate
|
||||
``raise`` statement or a call to ``abort`` in another of the blueprint's
|
||||
view functions; they are not invoked by, e.g., an invalid URL access.
|
||||
|
||||
This is because the blueprint does not "own" a certain URL space, so
|
||||
the application instance has no way of knowing which blueprint error
|
||||
handler it should run if given an invalid URL. If you would like to
|
||||
execute different handling strategies for these errors based on URL
|
||||
prefixes, they may be defined at the application level using the
|
||||
``request`` proxy object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import jsonify, render_template
|
||||
|
||||
# at the application level
|
||||
# not the blueprint level
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# if a request is in our blog URL space
|
||||
if request.path.startswith('/blog/'):
|
||||
# we return a custom blog 404 page
|
||||
return render_template("blog/404.html"), 404
|
||||
else:
|
||||
# otherwise we return our generic site-wide 404 page
|
||||
return render_template("404.html"), 404
|
||||
|
||||
@app.errorhandler(405)
|
||||
def method_not_allowed(e):
|
||||
# if a request has the wrong method to our API
|
||||
if request.path.startswith('/api/'):
|
||||
# we return a json saying so
|
||||
return jsonify(message="Method Not Allowed"), 405
|
||||
else:
|
||||
# otherwise we return a generic site-wide 405 page
|
||||
return render_template("405.html"), 405
|
||||
|
||||
|
||||
Returning API Errors as JSON
|
||||
----------------------------
|
||||
|
||||
When building APIs in Flask, some developers realise that the built-in
|
||||
exceptions are not expressive enough for APIs and that the content type of
|
||||
:mimetype:`text/html` they are emitting is not very useful for API consumers.
|
||||
|
||||
Using the same techniques as above and :func:`~flask.json.jsonify` we can return JSON
|
||||
responses to API errors. :func:`~flask.abort` is called
|
||||
with a ``description`` parameter. The error handler will
|
||||
use that as the JSON error message, and set the status code to 404.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import abort, jsonify
|
||||
|
||||
@app.errorhandler(404)
|
||||
def resource_not_found(e):
|
||||
return jsonify(error=str(e)), 404
|
||||
|
||||
@app.route("/cheese")
|
||||
def get_one_cheese():
|
||||
resource = get_resource()
|
||||
|
||||
if resource is None:
|
||||
abort(404, description="Resource not found")
|
||||
|
||||
return jsonify(resource)
|
||||
|
||||
We can also create custom exception classes. For instance, we can
|
||||
introduce a new custom exception for an API that can take a proper human readable message,
|
||||
a status code for the error and some optional payload to give more context
|
||||
for the error.
|
||||
|
||||
This is a simple example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import jsonify, request
|
||||
|
||||
class InvalidAPIUsage(Exception):
|
||||
status_code = 400
|
||||
|
||||
def __init__(self, message, status_code=None, payload=None):
|
||||
super().__init__()
|
||||
self.message = message
|
||||
if status_code is not None:
|
||||
self.status_code = status_code
|
||||
self.payload = payload
|
||||
|
||||
def to_dict(self):
|
||||
rv = dict(self.payload or ())
|
||||
rv['message'] = self.message
|
||||
return rv
|
||||
|
||||
@app.errorhandler(InvalidAPIUsage)
|
||||
def invalid_api_usage(e):
|
||||
return jsonify(e.to_dict()), e.status_code
|
||||
|
||||
# an API app route for getting user information
|
||||
# a correct request might be /api/user?user_id=420
|
||||
@app.route("/api/user")
|
||||
def user_api(user_id):
|
||||
user_id = request.arg.get("user_id")
|
||||
if not user_id:
|
||||
raise InvalidAPIUsage("No user id provided!")
|
||||
|
||||
user = get_user(user_id=user_id)
|
||||
if not user:
|
||||
raise InvalidAPIUsage("No such user!", status_code=404)
|
||||
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
A view can now raise that exception with an error message. Additionally
|
||||
some extra payload can be provided as a dictionary through the `payload`
|
||||
parameter.
|
||||
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
See :doc:`/logging` for information about how to log exceptions, such as
|
||||
by emailing them to admins.
|
||||
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
See :doc:`/debugging` for information about how to debug errors in
|
||||
development and production.
|
||||
@ -0,0 +1,305 @@
|
||||
Flask Extension Development
|
||||
===========================
|
||||
|
||||
.. currentmodule:: flask
|
||||
|
||||
Extensions are extra packages that add functionality to a Flask
|
||||
application. While `PyPI`_ contains many Flask extensions, you may not
|
||||
find one that fits your need. If this is the case, you can create your
|
||||
own, and publish it for others to use as well.
|
||||
|
||||
This guide will show how to create a Flask extension, and some of the
|
||||
common patterns and requirements involved. Since extensions can do
|
||||
anything, this guide won't be able to cover every possibility.
|
||||
|
||||
The best ways to learn about extensions are to look at how other
|
||||
extensions you use are written, and discuss with others. Discuss your
|
||||
design ideas with others on our `Discord Chat`_ or
|
||||
`GitHub Discussions`_.
|
||||
|
||||
The best extensions share common patterns, so that anyone familiar with
|
||||
using one extension won't feel completely lost with another. This can
|
||||
only work if collaboration happens early.
|
||||
|
||||
|
||||
Naming
|
||||
------
|
||||
|
||||
A Flask extension typically has ``flask`` in its name as a prefix or
|
||||
suffix. If it wraps another library, it should include the library name
|
||||
as well. This makes it easy to search for extensions, and makes their
|
||||
purpose clearer.
|
||||
|
||||
A general Python packaging recommendation is that the install name from
|
||||
the package index and the name used in ``import`` statements should be
|
||||
related. The import name is lowercase, with words separated by
|
||||
underscores (``_``). The install name is either lower case or title
|
||||
case, with words separated by dashes (``-``). If it wraps another
|
||||
library, prefer using the same case as that library's name.
|
||||
|
||||
Here are some example install and import names:
|
||||
|
||||
- ``Flask-Name`` imported as ``flask_name``
|
||||
- ``flask-name-lower`` imported as ``flask_name_lower``
|
||||
- ``Flask-ComboName`` imported as ``flask_comboname``
|
||||
- ``Name-Flask`` imported as ``name_flask``
|
||||
|
||||
|
||||
The Extension Class and Initialization
|
||||
--------------------------------------
|
||||
|
||||
All extensions will need some entry point that initializes the
|
||||
extension with the application. The most common pattern is to create a
|
||||
class that represents the extension's configuration and behavior, with
|
||||
an ``init_app`` method to apply the extension instance to the given
|
||||
application instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class HelloExtension:
|
||||
def __init__(self, app=None):
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
def init_app(self, app):
|
||||
app.before_request(...)
|
||||
|
||||
It is important that the app is not stored on the extension, don't do
|
||||
``self.app = app``. The only time the extension should have direct
|
||||
access to an app is during ``init_app``, otherwise it should use
|
||||
:data:`.current_app`.
|
||||
|
||||
This allows the extension to support the application factory pattern,
|
||||
avoids circular import issues when importing the extension instance
|
||||
elsewhere in a user's code, and makes testing with different
|
||||
configurations easier.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
hello = HelloExtension()
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
hello.init_app(app)
|
||||
return app
|
||||
|
||||
Above, the ``hello`` extension instance exists independently of the
|
||||
application. This means that other modules in a user's project can do
|
||||
``from project import hello`` and use the extension in blueprints before
|
||||
the app exists.
|
||||
|
||||
The :attr:`Flask.extensions` dict can be used to store a reference to
|
||||
the extension on the application, or some other state specific to the
|
||||
application. Be aware that this is a single namespace, so use a name
|
||||
unique to your extension, such as the extension's name without the
|
||||
"flask" prefix.
|
||||
|
||||
|
||||
Adding Behavior
|
||||
---------------
|
||||
|
||||
There are many ways that an extension can add behavior. Any setup
|
||||
methods that are available on the :class:`Flask` object can be used
|
||||
during an extension's ``init_app`` method.
|
||||
|
||||
A common pattern is to use :meth:`~Flask.before_request` to initialize
|
||||
some data or a connection at the beginning of each request, then
|
||||
:meth:`~Flask.teardown_request` to clean it up at the end. This can be
|
||||
stored on :data:`.g`, discussed more below.
|
||||
|
||||
A more lazy approach is to provide a method that initializes and caches
|
||||
the data or connection. For example, a ``ext.get_db`` method could
|
||||
create a database connection the first time it's called, so that a view
|
||||
that doesn't use the database doesn't create a connection.
|
||||
|
||||
Besides doing something before and after every view, your extension
|
||||
might want to add some specific views as well. In this case, you could
|
||||
define a :class:`Blueprint`, then call :meth:`~Flask.register_blueprint`
|
||||
during ``init_app`` to add the blueprint to the app.
|
||||
|
||||
|
||||
Configuration Techniques
|
||||
------------------------
|
||||
|
||||
There can be multiple levels and sources of configuration for an
|
||||
extension. You should consider what parts of your extension fall into
|
||||
each one.
|
||||
|
||||
- Configuration per application instance, through ``app.config``
|
||||
values. This is configuration that could reasonably change for each
|
||||
deployment of an application. A common example is a URL to an
|
||||
external resource, such as a database. Configuration keys should
|
||||
start with the extension's name so that they don't interfere with
|
||||
other extensions.
|
||||
- Configuration per extension instance, through ``__init__``
|
||||
arguments. This configuration usually affects how the extension
|
||||
is used, such that it wouldn't make sense to change it per
|
||||
deployment.
|
||||
- Configuration per extension instance, through instance attributes
|
||||
and decorator methods. It might be more ergonomic to assign to
|
||||
``ext.value``, or use a ``@ext.register`` decorator to register a
|
||||
function, after the extension instance has been created.
|
||||
- Global configuration through class attributes. Changing a class
|
||||
attribute like ``Ext.connection_class`` can customize default
|
||||
behavior without making a subclass. This could be combined
|
||||
per-extension configuration to override defaults.
|
||||
- Subclassing and overriding methods and attributes. Making the API of
|
||||
the extension itself something that can be overridden provides a
|
||||
very powerful tool for advanced customization.
|
||||
|
||||
The :class:`~flask.Flask` object itself uses all of these techniques.
|
||||
|
||||
It's up to you to decide what configuration is appropriate for your
|
||||
extension, based on what you need and what you want to support.
|
||||
|
||||
Configuration should not be changed after the application setup phase is
|
||||
complete and the server begins handling requests. Configuration is
|
||||
global, any changes to it are not guaranteed to be visible to other
|
||||
workers.
|
||||
|
||||
|
||||
Data During a Request
|
||||
---------------------
|
||||
|
||||
When writing a Flask application, the :data:`~flask.g` object is used to
|
||||
store information during a request. For example the
|
||||
:doc:`tutorial <tutorial/database>` stores a connection to a SQLite
|
||||
database as ``g.db``. Extensions can also use this, with some care.
|
||||
Since ``g`` is a single global namespace, extensions must use unique
|
||||
names that won't collide with user data. For example, use the extension
|
||||
name as a prefix, or as a namespace.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# an internal prefix with the extension name
|
||||
g._hello_user_id = 2
|
||||
|
||||
# or an internal prefix as a namespace
|
||||
from types import SimpleNamespace
|
||||
g._hello = SimpleNamespace()
|
||||
g._hello.user_id = 2
|
||||
|
||||
The data in ``g`` lasts for an application context. An application context is
|
||||
active during a request, CLI command, or ``with app.app_context()`` block. If
|
||||
you're storing something that should be closed, use
|
||||
:meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed when the
|
||||
app context ends. If it should only be valid during a request, or would not be
|
||||
used in the CLI outside a request, use :meth:`~flask.Flask.teardown_request`.
|
||||
|
||||
|
||||
Views and Models
|
||||
----------------
|
||||
|
||||
Your extension views might want to interact with specific models in your
|
||||
database, or some other extension or data connected to your application.
|
||||
For example, let's consider a ``Flask-SimpleBlog`` extension that works
|
||||
with Flask-SQLAlchemy to provide a ``Post`` model and views to write
|
||||
and read posts.
|
||||
|
||||
The ``Post`` model needs to subclass the Flask-SQLAlchemy ``db.Model``
|
||||
object, but that's only available once you've created an instance of
|
||||
that extension, not when your extension is defining its views. So how
|
||||
can the view code, defined before the model exists, access the model?
|
||||
|
||||
One method could be to use :doc:`views`. During ``__init__``, create
|
||||
the model, then create the views by passing the model to the view
|
||||
class's :meth:`~views.View.as_view` method.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PostAPI(MethodView):
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
|
||||
def get(self, id):
|
||||
post = self.model.query.get(id)
|
||||
return jsonify(post.to_json())
|
||||
|
||||
class BlogExtension:
|
||||
def __init__(self, db):
|
||||
class Post(db.Model):
|
||||
id = db.Column(primary_key=True)
|
||||
title = db.Column(db.String, nullable=False)
|
||||
|
||||
self.post_model = Post
|
||||
|
||||
def init_app(self, app):
|
||||
api_view = PostAPI.as_view(model=self.post_model)
|
||||
|
||||
db = SQLAlchemy()
|
||||
blog = BlogExtension(db)
|
||||
db.init_app(app)
|
||||
blog.init_app(app)
|
||||
|
||||
Another technique could be to use an attribute on the extension, such as
|
||||
``self.post_model`` from above. Add the extension to ``app.extensions``
|
||||
in ``init_app``, then access
|
||||
``current_app.extensions["simple_blog"].post_model`` from views.
|
||||
|
||||
You may also want to provide base classes so that users can provide
|
||||
their own ``Post`` model that conforms to the API your extension
|
||||
expects. So they could implement ``class Post(blog.BasePost)``, then
|
||||
set it as ``blog.post_model``.
|
||||
|
||||
As you can see, this can get a bit complex. Unfortunately, there's no
|
||||
perfect solution here, only different strategies and tradeoffs depending
|
||||
on your needs and how much customization you want to offer. Luckily,
|
||||
this sort of resource dependency is not a common need for most
|
||||
extensions. Remember, if you need help with design, ask on our
|
||||
`Discord Chat`_ or `GitHub Discussions`_.
|
||||
|
||||
|
||||
Recommended Extension Guidelines
|
||||
--------------------------------
|
||||
|
||||
Flask previously had the concept of "approved extensions", where the
|
||||
Flask maintainers evaluated the quality, support, and compatibility of
|
||||
the extensions before listing them. While the list became too difficult
|
||||
to maintain over time, the guidelines are still relevant to all
|
||||
extensions maintained and developed today, as they help the Flask
|
||||
ecosystem remain consistent and compatible.
|
||||
|
||||
1. An extension requires a maintainer. In the event an extension author
|
||||
would like to move beyond the project, the project should find a new
|
||||
maintainer and transfer access to the repository, documentation,
|
||||
PyPI, and any other services. The `Pallets-Eco`_ organization on
|
||||
GitHub allows for community maintenance with oversight from the
|
||||
Pallets maintainers.
|
||||
2. The naming scheme is *Flask-ExtensionName* or *ExtensionName-Flask*.
|
||||
It must provide exactly one package or module named
|
||||
``flask_extension_name``.
|
||||
3. The extension must use an open source license. The Python web
|
||||
ecosystem tends to prefer BSD or MIT. It must be open source and
|
||||
publicly available.
|
||||
4. The extension's API must have the following characteristics:
|
||||
|
||||
- It must support multiple applications running in the same Python
|
||||
process. Use ``current_app`` instead of ``self.app``, store
|
||||
configuration and state per application instance.
|
||||
- It must be possible to use the factory pattern for creating
|
||||
applications. Use the ``ext.init_app()`` pattern.
|
||||
|
||||
5. From a clone of the repository, an extension with its dependencies
|
||||
must be installable in editable mode with ``pip install -e .``.
|
||||
6. It must ship tests that can be invoked with a common tool like
|
||||
``tox -e py``, ``nox -s test`` or ``pytest``. If not using ``tox``,
|
||||
the test dependencies should be specified in a requirements file.
|
||||
The tests must be part of the sdist distribution.
|
||||
7. A link to the documentation or project website must be in the PyPI
|
||||
metadata or the readme. The documentation should use the Flask theme
|
||||
from the `Official Pallets Themes`_.
|
||||
8. The extension's dependencies should not use upper bounds or assume
|
||||
any particular version scheme, but should use lower bounds to
|
||||
indicate minimum compatibility support. For example,
|
||||
``sqlalchemy>=1.4``.
|
||||
9. Indicate the versions of Python supported using ``python_requires=">=version"``.
|
||||
Flask and Pallets policy is to support all Python versions that are not
|
||||
within six months of end of life (EOL). See Python's `EOL calendar`_ for
|
||||
timing.
|
||||
|
||||
.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
|
||||
.. _Discord Chat: https://discord.gg/pallets
|
||||
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
|
||||
.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/
|
||||
.. _Pallets-Eco: https://github.com/pallets-eco
|
||||
.. _EOL calendar: https://devguide.python.org/versions/
|
||||
@ -0,0 +1,48 @@
|
||||
Extensions
|
||||
==========
|
||||
|
||||
Extensions are extra packages that add functionality to a Flask
|
||||
application. For example, an extension might add support for sending
|
||||
email or connecting to a database. Some extensions add entire new
|
||||
frameworks to help build certain types of applications, like a REST API.
|
||||
|
||||
|
||||
Finding Extensions
|
||||
------------------
|
||||
|
||||
Flask extensions are usually named "Flask-Foo" or "Foo-Flask". You can
|
||||
search PyPI for packages tagged with `Framework :: Flask <pypi_>`_.
|
||||
|
||||
|
||||
Using Extensions
|
||||
----------------
|
||||
|
||||
Consult each extension's documentation for installation, configuration,
|
||||
and usage instructions. Generally, extensions pull their own
|
||||
configuration from :attr:`app.config <flask.Flask.config>` and are
|
||||
passed an application instance during initialization. For example,
|
||||
an extension called "Flask-Foo" might be used like this::
|
||||
|
||||
from flask_foo import Foo
|
||||
|
||||
foo = Foo()
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.update(
|
||||
FOO_BAR='baz',
|
||||
FOO_SPAM='eggs',
|
||||
)
|
||||
|
||||
foo.init_app(app)
|
||||
|
||||
|
||||
Building Extensions
|
||||
-------------------
|
||||
|
||||
While `PyPI <pypi_>`_ contains many Flask extensions, you may not find
|
||||
an extension that fits your need. If this is the case, you can create
|
||||
your own, and publish it for others to use as well. Read
|
||||
:doc:`extensiondev` to develop your own Flask extension.
|
||||
|
||||
|
||||
.. _pypi: https://pypi.org/search/?c=Framework+%3A%3A+Flask
|
||||
@ -0,0 +1,88 @@
|
||||
.. rst-class:: hide-header
|
||||
|
||||
Welcome to Flask
|
||||
================
|
||||
|
||||
.. image:: _static/flask-name.svg
|
||||
:align: center
|
||||
:height: 200px
|
||||
|
||||
Welcome to Flask's documentation. Flask is a lightweight WSGI web application framework.
|
||||
It is designed to make getting started quick and easy, with the ability to scale up to
|
||||
complex applications.
|
||||
|
||||
Get started with :doc:`installation`
|
||||
and then get an overview with the :doc:`quickstart`. There is also a
|
||||
more detailed :doc:`tutorial/index` that shows how to create a small but
|
||||
complete application with Flask. Common patterns are described in the
|
||||
:doc:`patterns/index` section. The rest of the docs describe each
|
||||
component of Flask in detail, with a full reference in the :doc:`api`
|
||||
section.
|
||||
|
||||
Flask depends on the `Werkzeug`_ WSGI toolkit, the `Jinja`_ template engine, and the
|
||||
`Click`_ CLI toolkit. Be sure to check their documentation as well as Flask's when
|
||||
looking for information.
|
||||
|
||||
.. _Werkzeug: https://werkzeug.palletsprojects.com
|
||||
.. _Jinja: https://jinja.palletsprojects.com
|
||||
.. _Click: https://click.palletsprojects.com
|
||||
|
||||
|
||||
User's Guide
|
||||
------------
|
||||
|
||||
Flask provides configuration and conventions, with sensible defaults, to get started.
|
||||
This section of the documentation explains the different parts of the Flask framework
|
||||
and how they can be used, customized, and extended. Beyond Flask itself, look for
|
||||
community-maintained extensions to add even more functionality.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
installation
|
||||
quickstart
|
||||
tutorial/index
|
||||
templating
|
||||
testing
|
||||
errorhandling
|
||||
debugging
|
||||
logging
|
||||
config
|
||||
signals
|
||||
views
|
||||
lifecycle
|
||||
appcontext
|
||||
blueprints
|
||||
extensions
|
||||
cli
|
||||
server
|
||||
shell
|
||||
patterns/index
|
||||
web-security
|
||||
deploying/index
|
||||
async-await
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
If you are looking for information on a specific function, class or
|
||||
method, this part of the documentation is for you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
|
||||
|
||||
Additional Notes
|
||||
----------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
design
|
||||
extensiondev
|
||||
contributing
|
||||
license
|
||||
changes
|
||||
@ -0,0 +1,144 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
Python Version
|
||||
--------------
|
||||
|
||||
We recommend using the latest version of Python. Flask supports Python 3.10 and newer.
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
These distributions will be installed automatically when installing Flask.
|
||||
|
||||
* `Werkzeug`_ implements WSGI, the standard Python interface between
|
||||
applications and servers.
|
||||
* `Jinja`_ is a template language that renders the pages your application
|
||||
serves.
|
||||
* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering
|
||||
templates to avoid injection attacks.
|
||||
* `ItsDangerous`_ securely signs data to ensure its integrity. This is used
|
||||
to protect Flask's session cookie.
|
||||
* `Click`_ is a framework for writing command line applications. It provides
|
||||
the ``flask`` command and allows adding custom management commands.
|
||||
* `Blinker`_ provides support for :doc:`signals`.
|
||||
|
||||
.. _Werkzeug: https://palletsprojects.com/p/werkzeug/
|
||||
.. _Jinja: https://palletsprojects.com/p/jinja/
|
||||
.. _MarkupSafe: https://palletsprojects.com/p/markupsafe/
|
||||
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
|
||||
.. _Click: https://palletsprojects.com/p/click/
|
||||
.. _Blinker: https://blinker.readthedocs.io/
|
||||
|
||||
|
||||
Optional dependencies
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These distributions will not be installed automatically. Flask will detect and
|
||||
use them if you install them.
|
||||
|
||||
* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask``
|
||||
commands.
|
||||
* `Watchdog`_ provides a faster, more efficient reloader for the development
|
||||
server.
|
||||
|
||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||
.. _watchdog: https://pythonhosted.org/watchdog/
|
||||
|
||||
|
||||
greenlet
|
||||
~~~~~~~~
|
||||
|
||||
You may choose to use gevent or eventlet with your application. In this
|
||||
case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is
|
||||
required.
|
||||
|
||||
These are not minimum supported versions, they only indicate the first
|
||||
versions that added necessary features. You should use the latest
|
||||
versions of each.
|
||||
|
||||
|
||||
Virtual environments
|
||||
--------------------
|
||||
|
||||
Use a virtual environment to manage the dependencies for your project, both in
|
||||
development and in production.
|
||||
|
||||
What problem does a virtual environment solve? The more Python projects you
|
||||
have, the more likely it is that you need to work with different versions of
|
||||
Python libraries, or even Python itself. Newer versions of libraries for one
|
||||
project can break compatibility in another project.
|
||||
|
||||
Virtual environments are independent groups of Python libraries, one for each
|
||||
project. Packages installed for one project will not affect other projects or
|
||||
the operating system's packages.
|
||||
|
||||
Python comes bundled with the :mod:`venv` module to create virtual
|
||||
environments.
|
||||
|
||||
|
||||
.. _install-create-env:
|
||||
|
||||
Create an environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Create a project folder and a :file:`.venv` folder within:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: macOS/Linux
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ mkdir myproject
|
||||
$ cd myproject
|
||||
$ python3 -m venv .venv
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> mkdir myproject
|
||||
> cd myproject
|
||||
> py -3 -m venv .venv
|
||||
|
||||
|
||||
.. _install-activate-env:
|
||||
|
||||
Activate the environment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before you work on your project, activate the corresponding environment:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: macOS/Linux
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ . .venv/bin/activate
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> .venv\Scripts\activate
|
||||
|
||||
Your shell prompt will change to show the name of the activated
|
||||
environment.
|
||||
|
||||
|
||||
Install Flask
|
||||
-------------
|
||||
|
||||
Within the activated environment, use the following command to install
|
||||
Flask:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ pip install Flask
|
||||
|
||||
Flask is now installed. Check out the :doc:`/quickstart` or go to the
|
||||
:doc:`Documentation Overview </index>`.
|
||||
@ -0,0 +1,5 @@
|
||||
BSD-3-Clause License
|
||||
====================
|
||||
|
||||
.. literalinclude:: ../LICENSE.txt
|
||||
:language: text
|
||||
@ -0,0 +1,171 @@
|
||||
Application Structure and Lifecycle
|
||||
===================================
|
||||
|
||||
Flask makes it pretty easy to write a web application. But there are quite a few
|
||||
different parts to an application and to each request it handles. Knowing what happens
|
||||
during application setup, serving, and handling requests will help you know what's
|
||||
possible in Flask and how to structure your application.
|
||||
|
||||
|
||||
Application Setup
|
||||
-----------------
|
||||
|
||||
The first step in creating a Flask application is creating the application object. Each
|
||||
Flask application is an instance of the :class:`.Flask` class, which collects all
|
||||
configuration, extensions, and views.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_mapping(
|
||||
SECRET_KEY="dev",
|
||||
)
|
||||
app.config.from_prefixed_env()
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello, World!"
|
||||
|
||||
This is known as the "application setup phase", it's the code you write that's outside
|
||||
any view functions or other handlers. It can be split up between different modules and
|
||||
sub-packages, but all code that you want to be part of your application must be imported
|
||||
in order for it to be registered.
|
||||
|
||||
All application setup must be completed before you start serving your application and
|
||||
handling requests. This is because WSGI servers divide work between multiple workers, or
|
||||
can be distributed across multiple machines. If the configuration changed in one worker,
|
||||
there's no way for Flask to ensure consistency between other workers.
|
||||
|
||||
Flask tries to help developers catch some of these setup ordering issues by showing an
|
||||
error if setup-related methods are called after requests are handled. In that case
|
||||
you'll see this error:
|
||||
|
||||
The setup method 'route' can no longer be called on the application. It has already
|
||||
handled its first request, any changes will not be applied consistently.
|
||||
Make sure all imports, decorators, functions, etc. needed to set up the application
|
||||
are done before running it.
|
||||
|
||||
However, it is not possible for Flask to detect all cases of out-of-order setup. In
|
||||
general, don't do anything to modify the ``Flask`` app object and ``Blueprint`` objects
|
||||
from within view functions that run during requests. This includes:
|
||||
|
||||
- Adding routes, view functions, and other request handlers with ``@app.route``,
|
||||
``@app.errorhandler``, ``@app.before_request``, etc.
|
||||
- Registering blueprints.
|
||||
- Loading configuration with ``app.config``.
|
||||
- Setting up the Jinja template environment with ``app.jinja_env``.
|
||||
- Setting a session interface, instead of the default itsdangerous cookie.
|
||||
- Setting a JSON provider with ``app.json``, instead of the default provider.
|
||||
- Creating and initializing Flask extensions.
|
||||
|
||||
|
||||
Serving the Application
|
||||
-----------------------
|
||||
|
||||
Flask is a WSGI application framework. The other half of WSGI is the WSGI server. During
|
||||
development, Flask, through Werkzeug, provides a development WSGI server with the
|
||||
``flask run`` CLI command. When you are done with development, use a production server
|
||||
to serve your application, see :doc:`deploying/index`.
|
||||
|
||||
Regardless of what server you're using, it will follow the :pep:`3333` WSGI spec. The
|
||||
WSGI server will be told how to access your Flask application object, which is the WSGI
|
||||
application. Then it will start listening for HTTP requests, translate the request data
|
||||
into a WSGI environ, and call the WSGI application with that data. The WSGI application
|
||||
will return data that is translated into an HTTP response.
|
||||
|
||||
#. Browser or other client makes HTTP request.
|
||||
#. WSGI server receives request.
|
||||
#. WSGI server converts HTTP data to WSGI ``environ`` dict.
|
||||
#. WSGI server calls WSGI application with the ``environ``.
|
||||
#. Flask, the WSGI application, does all its internal processing to route the request
|
||||
to a view function, handle errors, etc.
|
||||
#. Flask translates View function return into WSGI response data, passes it to WSGI
|
||||
server.
|
||||
#. WSGI server creates and send an HTTP response.
|
||||
#. Client receives the HTTP response.
|
||||
|
||||
|
||||
Middleware
|
||||
~~~~~~~~~~
|
||||
|
||||
The WSGI application above is a callable that behaves in a certain way. Middleware
|
||||
is a WSGI application that wraps another WSGI application. It's a similar concept to
|
||||
Python decorators. The outermost middleware will be called by the server. It can modify
|
||||
the data passed to it, then call the WSGI application (or further middleware) that it
|
||||
wraps, and so on. And it can take the return value of that call and modify it further.
|
||||
|
||||
From the WSGI server's perspective, there is one WSGI application, the one it calls
|
||||
directly. Typically, Flask is the "real" application at the end of the chain of
|
||||
middleware. But even Flask can call further WSGI applications, although that's an
|
||||
advanced, uncommon use case.
|
||||
|
||||
A common middleware you'll see used with Flask is Werkzeug's
|
||||
:class:`~werkzeug.middleware.proxy_fix.ProxyFix`, which modifies the request to look
|
||||
like it came directly from a client even if it passed through HTTP proxies on the way.
|
||||
There are other middleware that can handle serving static files, authentication, etc.
|
||||
|
||||
|
||||
How a Request is Handled
|
||||
------------------------
|
||||
|
||||
For us, the interesting part of the steps above is when Flask gets called by the WSGI
|
||||
server (or middleware). At that point, it will do quite a lot to handle the request and
|
||||
generate the response. At the most basic, it will match the URL to a view function, call
|
||||
the view function, and pass the return value back to the server. But there are many more
|
||||
parts that you can use to customize its behavior.
|
||||
|
||||
#. WSGI server calls the Flask object, which calls :meth:`.Flask.wsgi_app`.
|
||||
#. An :class:`.AppContext` object is created. This converts the WSGI ``environ``
|
||||
dict into a :class:`.Request` object.
|
||||
#. The :doc:`app context <appcontext>` is pushed, which makes
|
||||
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session`
|
||||
available.
|
||||
#. The :data:`.appcontext_pushed` signal is sent.
|
||||
#. The URL is matched against the URL rules registered with the :meth:`~.Flask.route`
|
||||
decorator during application setup. If there is no match, the error - usually a 404,
|
||||
405, or redirect - is stored to be handled later.
|
||||
#. The :data:`.request_started` signal is sent.
|
||||
#. Any :meth:`~.Flask.url_value_preprocessor` decorated functions are called.
|
||||
#. Any :meth:`~.Flask.before_request` decorated functions are called. If any of
|
||||
these function returns a value it is treated as the response immediately.
|
||||
#. If the URL didn't match a route a few steps ago, that error is raised now.
|
||||
#. The :meth:`~.Flask.route` decorated view function associated with the matched URL
|
||||
is called and returns a value to be used as the response.
|
||||
#. If any step so far raised an exception, and there is an :meth:`~.Flask.errorhandler`
|
||||
decorated function that matches the exception class or HTTP error code, it is
|
||||
called to handle the error and return a response.
|
||||
#. Whatever returned a response value - a before request function, the view, or an
|
||||
error handler, that value is converted to a :class:`.Response` object.
|
||||
#. Any :func:`~.after_this_request` decorated functions are called, which can modify
|
||||
the response object. They are then cleared.
|
||||
#. Any :meth:`~.Flask.after_request` decorated functions are called, which can modify
|
||||
the response object.
|
||||
#. The session is saved, persisting any modified session data using the app's
|
||||
:attr:`~.Flask.session_interface`.
|
||||
#. The :data:`.request_finished` signal is sent.
|
||||
#. If any step so far raised an exception, and it was not handled by an error handler
|
||||
function, it is handled now. HTTP exceptions are treated as responses with their
|
||||
corresponding status code, other exceptions are converted to a generic 500 response.
|
||||
The :data:`.got_request_exception` signal is sent.
|
||||
#. The response object's status, headers, and body are returned to the WSGI server.
|
||||
#. Any :meth:`~.Flask.teardown_request` decorated functions are called.
|
||||
#. The :data:`.request_tearing_down` signal is sent.
|
||||
#. Any :meth:`~.Flask.teardown_appcontext` decorated functions are called.
|
||||
#. The :data:`.appcontext_tearing_down` signal is sent.
|
||||
#. The app context is popped, :data:`.current_app`, :data:`.g`, :data:`.request`,
|
||||
and :data:`.session` are no longer available.
|
||||
#. The :data:`.appcontext_popped` signal is sent.
|
||||
|
||||
When executing a CLI command or plain app context without request data, the same
|
||||
order of steps is followed, omitting the steps that refer to the request.
|
||||
|
||||
A :class:`Blueprint` can add handlers for these events that are specific to the
|
||||
blueprint. The handlers for a blueprint will run if the blueprint
|
||||
owns the route that matches the request.
|
||||
|
||||
There are even more decorators and customization points than this, but that aren't part
|
||||
of every request lifecycle. They're more specific to certain things you might use during
|
||||
a request, such as templates, building URLs, or handling JSON data. See the rest of this
|
||||
documentation, as well as the :doc:`api` to explore further.
|
||||
@ -0,0 +1,183 @@
|
||||
Logging
|
||||
=======
|
||||
|
||||
Flask uses standard Python :mod:`logging`. Messages about your Flask
|
||||
application are logged with :meth:`app.logger <flask.Flask.logger>`,
|
||||
which takes the same name as :attr:`app.name <flask.Flask.name>`. This
|
||||
logger can also be used to log your own messages.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
user = get_user(request.form['username'])
|
||||
|
||||
if user.check_password(request.form['password']):
|
||||
login_user(user)
|
||||
app.logger.info('%s logged in successfully', user.username)
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
app.logger.info('%s failed to log in', user.username)
|
||||
abort(401)
|
||||
|
||||
If you don't configure logging, Python's default log level is usually
|
||||
'warning'. Nothing below the configured level will be visible.
|
||||
|
||||
|
||||
Basic Configuration
|
||||
-------------------
|
||||
|
||||
When you want to configure logging for your project, you should do it as soon
|
||||
as possible when the program starts. If :meth:`app.logger <flask.Flask.logger>`
|
||||
is accessed before logging is configured, it will add a default handler. If
|
||||
possible, configure logging before creating the application object.
|
||||
|
||||
This example uses :func:`~logging.config.dictConfig` to create a logging
|
||||
configuration similar to Flask's default, except for all logs::
|
||||
|
||||
from logging.config import dictConfig
|
||||
|
||||
dictConfig({
|
||||
'version': 1,
|
||||
'formatters': {'default': {
|
||||
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
|
||||
}},
|
||||
'handlers': {'wsgi': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'stream': 'ext://flask.logging.wsgi_errors_stream',
|
||||
'formatter': 'default'
|
||||
}},
|
||||
'root': {
|
||||
'level': 'INFO',
|
||||
'handlers': ['wsgi']
|
||||
}
|
||||
})
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
Default Configuration
|
||||
`````````````````````
|
||||
|
||||
If you do not configure logging yourself, Flask will add a
|
||||
:class:`~logging.StreamHandler` to :meth:`app.logger <flask.Flask.logger>`
|
||||
automatically. During requests, it will write to the stream specified by the
|
||||
WSGI server in ``environ['wsgi.errors']`` (which is usually
|
||||
:data:`sys.stderr`). Outside a request, it will log to :data:`sys.stderr`.
|
||||
|
||||
|
||||
Removing the Default Handler
|
||||
````````````````````````````
|
||||
|
||||
If you configured logging after accessing
|
||||
:meth:`app.logger <flask.Flask.logger>`, and need to remove the default
|
||||
handler, you can import and remove it::
|
||||
|
||||
from flask.logging import default_handler
|
||||
|
||||
app.logger.removeHandler(default_handler)
|
||||
|
||||
|
||||
Email Errors to Admins
|
||||
----------------------
|
||||
|
||||
When running the application on a remote server for production, you probably
|
||||
won't be looking at the log messages very often. The WSGI server will probably
|
||||
send log messages to a file, and you'll only check that file if a user tells
|
||||
you something went wrong.
|
||||
|
||||
To be proactive about discovering and fixing bugs, you can configure a
|
||||
:class:`logging.handlers.SMTPHandler` to send an email when errors and higher
|
||||
are logged. ::
|
||||
|
||||
import logging
|
||||
from logging.handlers import SMTPHandler
|
||||
|
||||
mail_handler = SMTPHandler(
|
||||
mailhost='127.0.0.1',
|
||||
fromaddr='server-error@example.com',
|
||||
toaddrs=['admin@example.com'],
|
||||
subject='Application Error'
|
||||
)
|
||||
mail_handler.setLevel(logging.ERROR)
|
||||
mail_handler.setFormatter(logging.Formatter(
|
||||
'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
||||
))
|
||||
|
||||
if not app.debug:
|
||||
app.logger.addHandler(mail_handler)
|
||||
|
||||
This requires that you have an SMTP server set up on the same server. See the
|
||||
Python docs for more information about configuring the handler.
|
||||
|
||||
|
||||
Injecting Request Information
|
||||
-----------------------------
|
||||
|
||||
Seeing more information about the request, such as the IP address, may help
|
||||
debugging some errors. You can subclass :class:`logging.Formatter` to inject
|
||||
your own fields that can be used in messages. You can change the formatter for
|
||||
Flask's default handler, the mail handler defined above, or any other
|
||||
handler. ::
|
||||
|
||||
from flask import has_request_context, request
|
||||
from flask.logging import default_handler
|
||||
|
||||
class RequestFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
if has_request_context():
|
||||
record.url = request.url
|
||||
record.remote_addr = request.remote_addr
|
||||
else:
|
||||
record.url = None
|
||||
record.remote_addr = None
|
||||
|
||||
return super().format(record)
|
||||
|
||||
formatter = RequestFormatter(
|
||||
'[%(asctime)s] %(remote_addr)s requested %(url)s\n'
|
||||
'%(levelname)s in %(module)s: %(message)s'
|
||||
)
|
||||
default_handler.setFormatter(formatter)
|
||||
mail_handler.setFormatter(formatter)
|
||||
|
||||
|
||||
Other Libraries
|
||||
---------------
|
||||
|
||||
Other libraries may use logging extensively, and you want to see relevant
|
||||
messages from those logs too. The simplest way to do this is to add handlers
|
||||
to the root logger instead of only the app logger. ::
|
||||
|
||||
from flask.logging import default_handler
|
||||
|
||||
root = logging.getLogger()
|
||||
root.addHandler(default_handler)
|
||||
root.addHandler(mail_handler)
|
||||
|
||||
Depending on your project, it may be more useful to configure each logger you
|
||||
care about separately, instead of configuring only the root logger. ::
|
||||
|
||||
for logger in (
|
||||
logging.getLogger(app.name),
|
||||
logging.getLogger('sqlalchemy'),
|
||||
logging.getLogger('other_package'),
|
||||
):
|
||||
logger.addHandler(default_handler)
|
||||
logger.addHandler(mail_handler)
|
||||
|
||||
|
||||
Werkzeug
|
||||
````````
|
||||
|
||||
Werkzeug logs basic request/response information to the ``'werkzeug'`` logger.
|
||||
If the root logger has no handlers configured, Werkzeug adds a
|
||||
:class:`~logging.StreamHandler` to its logger.
|
||||
|
||||
|
||||
Flask Extensions
|
||||
````````````````
|
||||
|
||||
Depending on the situation, an extension may choose to log to
|
||||
:meth:`app.logger <flask.Flask.logger>` or its own named logger. Consult each
|
||||
extension's documentation for details.
|
||||
@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
@ -0,0 +1,189 @@
|
||||
Application Dispatching
|
||||
=======================
|
||||
|
||||
Application dispatching is the process of combining multiple Flask
|
||||
applications on the WSGI level. You can combine not only Flask
|
||||
applications but any WSGI application. This would allow you to run a
|
||||
Django and a Flask application in the same interpreter side by side if
|
||||
you want. The usefulness of this depends on how the applications work
|
||||
internally.
|
||||
|
||||
The fundamental difference from :doc:`packages` is that in this case you
|
||||
are running the same or different Flask applications that are entirely
|
||||
isolated from each other. They run different configurations and are
|
||||
dispatched on the WSGI level.
|
||||
|
||||
|
||||
Working with this Document
|
||||
--------------------------
|
||||
|
||||
Each of the techniques and examples below results in an ``application``
|
||||
object that can be run with any WSGI server. For development, use the
|
||||
``flask run`` command to start a development server. For production, see
|
||||
:doc:`/deploying/index`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello World!'
|
||||
|
||||
|
||||
Combining Applications
|
||||
----------------------
|
||||
|
||||
If you have entirely separated applications and you want them to work next
|
||||
to each other in the same Python interpreter process you can take
|
||||
advantage of the :class:`werkzeug.wsgi.DispatcherMiddleware`. The idea
|
||||
here is that each Flask application is a valid WSGI application and they
|
||||
are combined by the dispatcher middleware into a larger one that is
|
||||
dispatched based on prefix.
|
||||
|
||||
For example you could have your main application run on ``/`` and your
|
||||
backend interface on ``/backend``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||
from frontend_app import application as frontend
|
||||
from backend_app import application as backend
|
||||
|
||||
application = DispatcherMiddleware(frontend, {
|
||||
'/backend': backend
|
||||
})
|
||||
|
||||
|
||||
Dispatch by Subdomain
|
||||
---------------------
|
||||
|
||||
Sometimes you might want to use multiple instances of the same application
|
||||
with different configurations. Assuming the application is created inside
|
||||
a function and you can call that function to instantiate it, that is
|
||||
really easy to implement. In order to develop your application to support
|
||||
creating new instances in functions have a look at the
|
||||
:doc:`appfactories` pattern.
|
||||
|
||||
A very common example would be creating applications per subdomain. For
|
||||
instance you configure your webserver to dispatch all requests for all
|
||||
subdomains to your application and you then use the subdomain information
|
||||
to create user-specific instances. Once you have your server set up to
|
||||
listen on all subdomains you can use a very simple WSGI application to do
|
||||
the dynamic application creation.
|
||||
|
||||
The perfect level for abstraction in that regard is the WSGI layer. You
|
||||
write your own WSGI application that looks at the request that comes and
|
||||
delegates it to your Flask application. If that application does not
|
||||
exist yet, it is dynamically created and remembered.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from threading import Lock
|
||||
|
||||
class SubdomainDispatcher:
|
||||
|
||||
def __init__(self, domain, create_app):
|
||||
self.domain = domain
|
||||
self.create_app = create_app
|
||||
self.lock = Lock()
|
||||
self.instances = {}
|
||||
|
||||
def get_application(self, host):
|
||||
host = host.split(':')[0]
|
||||
assert host.endswith(self.domain), 'Configuration error'
|
||||
subdomain = host[:-len(self.domain)].rstrip('.')
|
||||
with self.lock:
|
||||
app = self.instances.get(subdomain)
|
||||
if app is None:
|
||||
app = self.create_app(subdomain)
|
||||
self.instances[subdomain] = app
|
||||
return app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
app = self.get_application(environ['HTTP_HOST'])
|
||||
return app(environ, start_response)
|
||||
|
||||
|
||||
This dispatcher can then be used like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from myapplication import create_app, get_user_for_subdomain
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
def make_app(subdomain):
|
||||
user = get_user_for_subdomain(subdomain)
|
||||
if user is None:
|
||||
# if there is no user for that subdomain we still have
|
||||
# to return a WSGI application that handles that request.
|
||||
# We can then just return the NotFound() exception as
|
||||
# application which will render a default 404 page.
|
||||
# You might also redirect the user to the main page then
|
||||
return NotFound()
|
||||
|
||||
# otherwise create the application for the specific user
|
||||
return create_app(user)
|
||||
|
||||
application = SubdomainDispatcher('example.com', make_app)
|
||||
|
||||
|
||||
Dispatch by Path
|
||||
----------------
|
||||
|
||||
Dispatching by a path on the URL is very similar. Instead of looking at
|
||||
the ``Host`` header to figure out the subdomain one simply looks at the
|
||||
request path up to the first slash.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from threading import Lock
|
||||
from wsgiref.util import shift_path_info
|
||||
|
||||
class PathDispatcher:
|
||||
|
||||
def __init__(self, default_app, create_app):
|
||||
self.default_app = default_app
|
||||
self.create_app = create_app
|
||||
self.lock = Lock()
|
||||
self.instances = {}
|
||||
|
||||
def get_application(self, prefix):
|
||||
with self.lock:
|
||||
app = self.instances.get(prefix)
|
||||
if app is None:
|
||||
app = self.create_app(prefix)
|
||||
if app is not None:
|
||||
self.instances[prefix] = app
|
||||
return app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
app = self.get_application(_peek_path_info(environ))
|
||||
if app is not None:
|
||||
shift_path_info(environ)
|
||||
else:
|
||||
app = self.default_app
|
||||
return app(environ, start_response)
|
||||
|
||||
def _peek_path_info(environ):
|
||||
segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1)
|
||||
if segments:
|
||||
return segments[0]
|
||||
|
||||
return None
|
||||
|
||||
The big difference between this and the subdomain one is that this one
|
||||
falls back to another application if the creator function returns ``None``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from myapplication import create_app, default_app, get_user_for_prefix
|
||||
|
||||
def make_app(prefix):
|
||||
user = get_user_for_prefix(prefix)
|
||||
if user is not None:
|
||||
return create_app(user)
|
||||
|
||||
application = PathDispatcher(default_app, make_app)
|
||||
@ -0,0 +1,118 @@
|
||||
Application Factories
|
||||
=====================
|
||||
|
||||
If you are already using packages and blueprints for your application
|
||||
(:doc:`/blueprints`) there are a couple of really nice ways to further improve
|
||||
the experience. A common pattern is creating the application object when
|
||||
the blueprint is imported. But if you move the creation of this object
|
||||
into a function, you can then create multiple instances of this app later.
|
||||
|
||||
So why would you want to do this?
|
||||
|
||||
1. Testing. You can have instances of the application with different
|
||||
settings to test every case.
|
||||
2. Multiple instances. Imagine you want to run different versions of the
|
||||
same application. Of course you could have multiple instances with
|
||||
different configs set up in your webserver, but if you use factories,
|
||||
you can have multiple instances of the same application running in the
|
||||
same application process which can be handy.
|
||||
|
||||
So how would you then actually implement that?
|
||||
|
||||
Basic Factories
|
||||
---------------
|
||||
|
||||
The idea is to set up the application in a function. Like this::
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile(config_filename)
|
||||
|
||||
from yourapplication.model import db
|
||||
db.init_app(app)
|
||||
|
||||
from yourapplication.views.admin import admin
|
||||
from yourapplication.views.frontend import frontend
|
||||
app.register_blueprint(admin)
|
||||
app.register_blueprint(frontend)
|
||||
|
||||
return app
|
||||
|
||||
The downside is that you cannot use the application object in the blueprints
|
||||
at import time. You can however use it from within a request. How do you
|
||||
get access to the application with the config? Use
|
||||
:data:`~flask.current_app`::
|
||||
|
||||
from flask import current_app, Blueprint, render_template
|
||||
admin = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
@admin.route('/')
|
||||
def index():
|
||||
return render_template(current_app.config['INDEX_TEMPLATE'])
|
||||
|
||||
Here we look up the name of a template in the config.
|
||||
|
||||
Factories & Extensions
|
||||
----------------------
|
||||
|
||||
It's preferable to create your extensions and app factories so that the
|
||||
extension object does not initially get bound to the application.
|
||||
|
||||
Using `Flask-SQLAlchemy <https://flask-sqlalchemy.palletsprojects.com/>`_,
|
||||
as an example, you should not do something along those lines::
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile(config_filename)
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
But, rather, in model.py (or equivalent)::
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
and in your application.py (or equivalent)::
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile(config_filename)
|
||||
|
||||
from yourapplication.model import db
|
||||
db.init_app(app)
|
||||
|
||||
Using this design pattern, no application-specific state is stored on the
|
||||
extension object, so one extension object can be used for multiple apps.
|
||||
For more information about the design of extensions refer to :doc:`/extensiondev`.
|
||||
|
||||
Using Applications
|
||||
------------------
|
||||
|
||||
To run such an application, you can use the :command:`flask` command:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app hello run
|
||||
|
||||
Flask will automatically detect the factory if it is named
|
||||
``create_app`` or ``make_app`` in ``hello``. You can also pass arguments
|
||||
to the factory like this:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app 'hello:create_app(local_auth=True)' run
|
||||
|
||||
Then the ``create_app`` factory in ``hello`` is called with the keyword
|
||||
argument ``local_auth=True``. See :doc:`/cli` for more detail.
|
||||
|
||||
Factory Improvements
|
||||
--------------------
|
||||
|
||||
The factory function above is not very clever, but you can improve it.
|
||||
The following changes are straightforward to implement:
|
||||
|
||||
1. Make it possible to pass in configuration values for unit tests so that
|
||||
you don't have to create config files on the filesystem.
|
||||
2. Call a function from a blueprint when the application is setting up so
|
||||
that you have a place to modify attributes of the application (like
|
||||
hooking in before/after request handlers etc.)
|
||||
3. Add in WSGI middlewares when the application is being created if necessary.
|
||||
@ -0,0 +1,16 @@
|
||||
Caching
|
||||
=======
|
||||
|
||||
When your application runs slow, throw some caches in. Well, at least
|
||||
it's the easiest way to speed up things. What does a cache do? Say you
|
||||
have a function that takes some time to complete but the results would
|
||||
still be good enough if they were 5 minutes old. So then the idea is that
|
||||
you actually put the result of that calculation into a cache for some
|
||||
time.
|
||||
|
||||
Flask itself does not provide caching for you, but `Flask-Caching`_, an
|
||||
extension for Flask does. Flask-Caching supports various backends, and it is
|
||||
even possible to develop your own caching backend.
|
||||
|
||||
|
||||
.. _Flask-Caching: https://flask-caching.readthedocs.io/en/latest/
|
||||
@ -0,0 +1,242 @@
|
||||
Background Tasks with Celery
|
||||
============================
|
||||
|
||||
If your application has a long running task, such as processing some uploaded data or
|
||||
sending email, you don't want to wait for it to finish during a request. Instead, use a
|
||||
task queue to send the necessary data to another process that will run the task in the
|
||||
background while the request returns immediately.
|
||||
|
||||
`Celery`_ is a powerful task queue that can be used for simple background tasks as well
|
||||
as complex multi-stage programs and schedules. This guide will show you how to configure
|
||||
Celery using Flask. Read Celery's `First Steps with Celery`_ guide to learn how to use
|
||||
Celery itself.
|
||||
|
||||
.. _Celery: https://celery.readthedocs.io
|
||||
.. _First Steps with Celery: https://celery.readthedocs.io/en/latest/getting-started/first-steps-with-celery.html
|
||||
|
||||
The Flask repository contains `an example <https://github.com/pallets/flask/tree/main/examples/celery>`_
|
||||
based on the information on this page, which also shows how to use JavaScript to submit
|
||||
tasks and poll for progress and results.
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
Install Celery from PyPI, for example using pip:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install celery
|
||||
|
||||
|
||||
Integrate Celery with Flask
|
||||
---------------------------
|
||||
|
||||
You can use Celery without any integration with Flask, but it's convenient to configure
|
||||
it through Flask's config, and to let tasks access the Flask application.
|
||||
|
||||
Celery uses similar ideas to Flask, with a ``Celery`` app object that has configuration
|
||||
and registers tasks. While creating a Flask app, use the following code to create and
|
||||
configure a Celery app as well.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from celery import Celery, Task
|
||||
|
||||
def celery_init_app(app: Flask) -> Celery:
|
||||
class FlaskTask(Task):
|
||||
def __call__(self, *args: object, **kwargs: object) -> object:
|
||||
with app.app_context():
|
||||
return self.run(*args, **kwargs)
|
||||
|
||||
celery_app = Celery(app.name, task_cls=FlaskTask)
|
||||
celery_app.config_from_object(app.config["CELERY"])
|
||||
celery_app.set_default()
|
||||
app.extensions["celery"] = celery_app
|
||||
return celery_app
|
||||
|
||||
This creates and returns a ``Celery`` app object. Celery `configuration`_ is taken from
|
||||
the ``CELERY`` key in the Flask configuration. The Celery app is set as the default, so
|
||||
that it is seen during each request. The ``Task`` subclass automatically runs task
|
||||
functions with a Flask app context active, so that services like your database
|
||||
connections are available.
|
||||
|
||||
.. _configuration: https://celery.readthedocs.io/en/stable/userguide/configuration.html
|
||||
|
||||
Here's a basic ``example.py`` that configures Celery to use Redis for communication. We
|
||||
enable a result backend, but ignore results by default. This allows us to store results
|
||||
only for tasks where we care about the result.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_mapping(
|
||||
CELERY=dict(
|
||||
broker_url="redis://localhost",
|
||||
result_backend="redis://localhost",
|
||||
task_ignore_result=True,
|
||||
),
|
||||
)
|
||||
celery_app = celery_init_app(app)
|
||||
|
||||
Point the ``celery worker`` command at this and it will find the ``celery_app`` object.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ celery -A example worker --loglevel INFO
|
||||
|
||||
You can also run the ``celery beat`` command to run tasks on a schedule. See Celery's
|
||||
docs for more information about defining schedules.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ celery -A example beat --loglevel INFO
|
||||
|
||||
|
||||
Application Factory
|
||||
-------------------
|
||||
|
||||
When using the Flask application factory pattern, call the ``celery_init_app`` function
|
||||
inside the factory. It sets ``app.extensions["celery"]`` to the Celery app object, which
|
||||
can be used to get the Celery app from the Flask app returned by the factory.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def create_app() -> Flask:
|
||||
app = Flask(__name__)
|
||||
app.config.from_mapping(
|
||||
CELERY=dict(
|
||||
broker_url="redis://localhost",
|
||||
result_backend="redis://localhost",
|
||||
task_ignore_result=True,
|
||||
),
|
||||
)
|
||||
app.config.from_prefixed_env()
|
||||
celery_init_app(app)
|
||||
return app
|
||||
|
||||
To use ``celery`` commands, Celery needs an app object, but that's no longer directly
|
||||
available. Create a ``make_celery.py`` file that calls the Flask app factory and gets
|
||||
the Celery app from the returned Flask app.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from example import create_app
|
||||
|
||||
flask_app = create_app()
|
||||
celery_app = flask_app.extensions["celery"]
|
||||
|
||||
Point the ``celery`` command to this file.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ celery -A make_celery worker --loglevel INFO
|
||||
$ celery -A make_celery beat --loglevel INFO
|
||||
|
||||
|
||||
Defining Tasks
|
||||
--------------
|
||||
|
||||
Using ``@celery_app.task`` to decorate task functions requires access to the
|
||||
``celery_app`` object, which won't be available when using the factory pattern. It also
|
||||
means that the decorated tasks are tied to the specific Flask and Celery app instances,
|
||||
which could be an issue during testing if you change configuration for a test.
|
||||
|
||||
Instead, use Celery's ``@shared_task`` decorator. This creates task objects that will
|
||||
access whatever the "current app" is, which is a similar concept to Flask's blueprints
|
||||
and app context. This is why we called ``celery_app.set_default()`` above.
|
||||
|
||||
Here's an example task that adds two numbers together and returns the result.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from celery import shared_task
|
||||
|
||||
@shared_task(ignore_result=False)
|
||||
def add_together(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
Earlier, we configured Celery to ignore task results by default. Since we want to know
|
||||
the return value of this task, we set ``ignore_result=False``. On the other hand, a task
|
||||
that didn't need a result, such as sending an email, wouldn't set this.
|
||||
|
||||
|
||||
Calling Tasks
|
||||
-------------
|
||||
|
||||
The decorated function becomes a task object with methods to call it in the background.
|
||||
The simplest way is to use the ``delay(*args, **kwargs)`` method. See Celery's docs for
|
||||
more methods.
|
||||
|
||||
A Celery worker must be running to run the task. Starting a worker is shown in the
|
||||
previous sections.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import request
|
||||
|
||||
@app.post("/add")
|
||||
def start_add() -> dict[str, object]:
|
||||
a = request.form.get("a", type=int)
|
||||
b = request.form.get("b", type=int)
|
||||
result = add_together.delay(a, b)
|
||||
return {"result_id": result.id}
|
||||
|
||||
The route doesn't get the task's result immediately. That would defeat the purpose by
|
||||
blocking the response. Instead, we return the running task's result id, which we can use
|
||||
later to get the result.
|
||||
|
||||
|
||||
Getting Results
|
||||
---------------
|
||||
|
||||
To fetch the result of the task we started above, we'll add another route that takes the
|
||||
result id we returned before. We return whether the task is finished (ready), whether it
|
||||
finished successfully, and what the return value (or error) was if it is finished.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from celery.result import AsyncResult
|
||||
|
||||
@app.get("/result/<id>")
|
||||
def task_result(id: str) -> dict[str, object]:
|
||||
result = AsyncResult(id)
|
||||
return {
|
||||
"ready": result.ready(),
|
||||
"successful": result.successful(),
|
||||
"value": result.result if result.ready() else None,
|
||||
}
|
||||
|
||||
Now you can start the task using the first route, then poll for the result using the
|
||||
second route. This keeps the Flask request workers from being blocked waiting for tasks
|
||||
to finish.
|
||||
|
||||
The Flask repository contains `an example <https://github.com/pallets/flask/tree/main/examples/celery>`_
|
||||
using JavaScript to submit tasks and poll for progress and results.
|
||||
|
||||
|
||||
Passing Data to Tasks
|
||||
---------------------
|
||||
|
||||
The "add" task above took two integers as arguments. To pass arguments to tasks, Celery
|
||||
has to serialize them to a format that it can pass to other processes. Therefore,
|
||||
passing complex objects is not recommended. For example, it would be impossible to pass
|
||||
a SQLAlchemy model object, since that object is probably not serializable and is tied to
|
||||
the session that queried it.
|
||||
|
||||
Pass the minimal amount of data necessary to fetch or recreate any complex data within
|
||||
the task. Consider a task that will run when the logged in user asks for an archive of
|
||||
their data. The Flask request knows the logged in user, and has the user object queried
|
||||
from the database. It got that by querying the database for a given id, so the task can
|
||||
do the same thing. Pass the user's id rather than the user object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@shared_task
|
||||
def generate_user_archive(user_id: str) -> None:
|
||||
user = db.session.get(User, user_id)
|
||||
...
|
||||
|
||||
generate_user_archive.delay(current_user.id)
|
||||
@ -0,0 +1,44 @@
|
||||
Deferred Request Callbacks
|
||||
==========================
|
||||
|
||||
One of the design principles of Flask is that response objects are created and
|
||||
passed down a chain of potential callbacks that can modify them or replace
|
||||
them. When the request handling starts, there is no response object yet. It is
|
||||
created as necessary either by a view function or by some other component in
|
||||
the system.
|
||||
|
||||
What happens if you want to modify the response at a point where the response
|
||||
does not exist yet? A common example for that would be a
|
||||
:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the
|
||||
response object.
|
||||
|
||||
One way is to avoid the situation. Very often that is possible. For instance
|
||||
you can try to move that logic into a :meth:`~flask.Flask.after_request`
|
||||
callback instead. However, sometimes moving code there makes it
|
||||
more complicated or awkward to reason about.
|
||||
|
||||
As an alternative, you can use :func:`~flask.after_this_request` to register
|
||||
callbacks that will execute after only the current request. This way you can
|
||||
defer code execution from anywhere in the application, based on the current
|
||||
request.
|
||||
|
||||
At any time during a request, we can register a function to be called at the
|
||||
end of the request. For example you can remember the current language of the
|
||||
user in a cookie in a :meth:`~flask.Flask.before_request` callback::
|
||||
|
||||
from flask import request, after_this_request
|
||||
|
||||
@app.before_request
|
||||
def detect_user_language():
|
||||
language = request.cookies.get('user_lang')
|
||||
|
||||
if language is None:
|
||||
language = guess_language_from_request()
|
||||
|
||||
# when the response exists, set a cookie with the language
|
||||
@after_this_request
|
||||
def remember_language(response):
|
||||
response.set_cookie('user_lang', language)
|
||||
return response
|
||||
|
||||
g.language = language
|
||||
@ -0,0 +1,182 @@
|
||||
Uploading Files
|
||||
===============
|
||||
|
||||
Ah yes, the good old problem of file uploads. The basic idea of file
|
||||
uploads is actually quite simple. It basically works like this:
|
||||
|
||||
1. A ``<form>`` tag is marked with ``enctype=multipart/form-data``
|
||||
and an ``<input type=file>`` is placed in that form.
|
||||
2. The application accesses the file from the :attr:`~flask.request.files`
|
||||
dictionary on the request object.
|
||||
3. use the :meth:`~werkzeug.datastructures.FileStorage.save` method of the file to save
|
||||
the file permanently somewhere on the filesystem.
|
||||
|
||||
A Gentle Introduction
|
||||
---------------------
|
||||
|
||||
Let's start with a very basic application that uploads a file to a
|
||||
specific upload folder and displays a file to the user. Let's look at the
|
||||
bootstrapping code for our application::
|
||||
|
||||
import os
|
||||
from flask import Flask, flash, request, redirect, url_for
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||
|
||||
So first we need a couple of imports. Most should be straightforward, the
|
||||
:func:`werkzeug.secure_filename` is explained a little bit later. The
|
||||
``UPLOAD_FOLDER`` is where we will store the uploaded files and the
|
||||
``ALLOWED_EXTENSIONS`` is the set of allowed file extensions.
|
||||
|
||||
Why do we limit the extensions that are allowed? You probably don't want
|
||||
your users to be able to upload everything there if the server is directly
|
||||
sending out the data to the client. That way you can make sure that users
|
||||
are not able to upload HTML files that would cause XSS problems (see
|
||||
:ref:`security-xss`). Also make sure to disallow ``.php`` files if the server
|
||||
executes them, but who has PHP installed on their server, right? :)
|
||||
|
||||
Next the functions that check if an extension is valid and that uploads
|
||||
the file and redirects the user to the URL for the uploaded file::
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
# check if the post request has the file part
|
||||
if 'file' not in request.files:
|
||||
flash('No file part')
|
||||
return redirect(request.url)
|
||||
file = request.files['file']
|
||||
# If the user does not select a file, the browser submits an
|
||||
# empty file without a filename.
|
||||
if file.filename == '':
|
||||
flash('No selected file')
|
||||
return redirect(request.url)
|
||||
if file and allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
||||
return redirect(url_for('download_file', name=filename))
|
||||
return '''
|
||||
<!doctype html>
|
||||
<title>Upload new File</title>
|
||||
<h1>Upload new File</h1>
|
||||
<form method=post enctype=multipart/form-data>
|
||||
<input type=file name=file>
|
||||
<input type=submit value=Upload>
|
||||
</form>
|
||||
'''
|
||||
|
||||
So what does that :func:`~werkzeug.utils.secure_filename` function actually do?
|
||||
Now the problem is that there is that principle called "never trust user
|
||||
input". This is also true for the filename of an uploaded file. All
|
||||
submitted form data can be forged, and filenames can be dangerous. For
|
||||
the moment just remember: always use that function to secure a filename
|
||||
before storing it directly on the filesystem.
|
||||
|
||||
.. admonition:: Information for the Pros
|
||||
|
||||
So you're interested in what that :func:`~werkzeug.utils.secure_filename`
|
||||
function does and what the problem is if you're not using it? So just
|
||||
imagine someone would send the following information as `filename` to
|
||||
your application::
|
||||
|
||||
filename = "../../../../home/username/.bashrc"
|
||||
|
||||
Assuming the number of ``../`` is correct and you would join this with
|
||||
the ``UPLOAD_FOLDER`` the user might have the ability to modify a file on
|
||||
the server's filesystem he or she should not modify. This does require some
|
||||
knowledge about how the application looks like, but trust me, hackers
|
||||
are patient :)
|
||||
|
||||
Now let's look how that function works:
|
||||
|
||||
>>> secure_filename('../../../../home/username/.bashrc')
|
||||
'home_username_.bashrc'
|
||||
|
||||
We want to be able to serve the uploaded files so they can be downloaded
|
||||
by users. We'll define a ``download_file`` view to serve files in the
|
||||
upload folder by name. ``url_for("download_file", name=name)`` generates
|
||||
download URLs.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import send_from_directory
|
||||
|
||||
@app.route('/uploads/<name>')
|
||||
def download_file(name):
|
||||
return send_from_directory(app.config["UPLOAD_FOLDER"], name)
|
||||
|
||||
If you're using middleware or the HTTP server to serve files, you can
|
||||
register the ``download_file`` endpoint as ``build_only`` so ``url_for``
|
||||
will work without a view function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.add_url_rule(
|
||||
"/uploads/<name>", endpoint="download_file", build_only=True
|
||||
)
|
||||
|
||||
|
||||
Improving Uploads
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
So how exactly does Flask handle uploads? Well it will store them in the
|
||||
webserver's memory if the files are reasonably small, otherwise in a
|
||||
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
||||
do you specify the maximum file size after which an upload is aborted? By
|
||||
default Flask will happily accept file uploads with an unlimited amount of
|
||||
memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH``
|
||||
config key::
|
||||
|
||||
from flask import Flask, Request
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000
|
||||
|
||||
The code above will limit the maximum allowed payload to 16 megabytes.
|
||||
If a larger file is transmitted, Flask will raise a
|
||||
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception.
|
||||
|
||||
.. admonition:: Connection Reset Issue
|
||||
|
||||
When using the local development server, you may get a connection
|
||||
reset error instead of a 413 response. You will get the correct
|
||||
status response when running the app with a production WSGI server.
|
||||
|
||||
This feature was added in Flask 0.6 but can be achieved in older versions
|
||||
as well by subclassing the request object. For more information on that
|
||||
consult the Werkzeug documentation on file handling.
|
||||
|
||||
|
||||
Upload Progress Bars
|
||||
--------------------
|
||||
|
||||
A while ago many developers had the idea to read the incoming file in
|
||||
small chunks and store the upload progress in the database to be able to
|
||||
poll the progress with JavaScript from the client. The client asks the
|
||||
server every 5 seconds how much it has transmitted, but this is
|
||||
something it should already know.
|
||||
|
||||
An Easier Solution
|
||||
------------------
|
||||
|
||||
Now there are better solutions that work faster and are more reliable. There
|
||||
are JavaScript libraries like jQuery_ that have form plugins to ease the
|
||||
construction of progress bar.
|
||||
|
||||
Because the common pattern for file uploads exists almost unchanged in all
|
||||
applications dealing with uploads, there are also some Flask extensions that
|
||||
implement a full fledged upload mechanism that allows controlling which
|
||||
file extensions are allowed to be uploaded.
|
||||
|
||||
.. _jQuery: https://jquery.com/
|
||||
@ -0,0 +1,40 @@
|
||||
Patterns for Flask
|
||||
==================
|
||||
|
||||
Certain features and interactions are common enough that you will find
|
||||
them in most web applications. For example, many applications use a
|
||||
relational database and user authentication. They will open a database
|
||||
connection at the beginning of the request and get the information for
|
||||
the logged in user. At the end of the request, the database connection
|
||||
is closed.
|
||||
|
||||
These types of patterns may be a bit outside the scope of Flask itself,
|
||||
but Flask makes it easy to implement them. Some common patterns are
|
||||
collected in the following pages.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
packages
|
||||
appfactories
|
||||
appdispatch
|
||||
urlprocessors
|
||||
sqlite3
|
||||
sqlalchemy
|
||||
fileuploads
|
||||
caching
|
||||
viewdecorators
|
||||
wtforms
|
||||
templateinheritance
|
||||
flashing
|
||||
javascript
|
||||
lazyloading
|
||||
mongoengine
|
||||
favicon
|
||||
streaming
|
||||
deferredcallbacks
|
||||
methodoverrides
|
||||
requestchecksum
|
||||
celery
|
||||
subclassing
|
||||
singlepageapplications
|
||||
@ -0,0 +1,259 @@
|
||||
JavaScript, ``fetch``, and JSON
|
||||
===============================
|
||||
|
||||
You may want to make your HTML page dynamic, by changing data without
|
||||
reloading the entire page. Instead of submitting an HTML ``<form>`` and
|
||||
performing a redirect to re-render the template, you can add
|
||||
`JavaScript`_ that calls |fetch|_ and replaces content on the page.
|
||||
|
||||
|fetch|_ is the modern, built-in JavaScript solution to making
|
||||
requests from a page. You may have heard of other "AJAX" methods and
|
||||
libraries, such as |XHR|_ or `jQuery`_. These are no longer needed in
|
||||
modern browsers, although you may choose to use them or another library
|
||||
depending on your application's requirements. These docs will only focus
|
||||
on built-in JavaScript features.
|
||||
|
||||
.. _JavaScript: https://developer.mozilla.org/Web/JavaScript
|
||||
.. |fetch| replace:: ``fetch()``
|
||||
.. _fetch: https://developer.mozilla.org/Web/API/Fetch_API
|
||||
.. |XHR| replace:: ``XMLHttpRequest()``
|
||||
.. _XHR: https://developer.mozilla.org/Web/API/XMLHttpRequest
|
||||
.. _jQuery: https://jquery.com/
|
||||
|
||||
|
||||
Rendering Templates
|
||||
-------------------
|
||||
|
||||
It is important to understand the difference between templates and
|
||||
JavaScript. Templates are rendered on the server, before the response is
|
||||
sent to the user's browser. JavaScript runs in the user's browser, after
|
||||
the template is rendered and sent. Therefore, it is impossible to use
|
||||
JavaScript to affect how the Jinja template is rendered, but it is
|
||||
possible to render data into the JavaScript that will run.
|
||||
|
||||
To provide data to JavaScript when rendering the template, use the
|
||||
:func:`~jinja-filters.tojson` filter in a ``<script>`` block. This will
|
||||
convert the data to a valid JavaScript object, and ensure that any
|
||||
unsafe HTML characters are rendered safely. If you do not use the
|
||||
``tojson`` filter, you will get a ``SyntaxError`` in the browser
|
||||
console.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
data = generate_report()
|
||||
return render_template("report.html", chart_data=data)
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
<script>
|
||||
const chart_data = {{ chart_data|tojson }}
|
||||
chartLib.makeChart(chart_data)
|
||||
</script>
|
||||
|
||||
A less common pattern is to add the data to a ``data-`` attribute on an
|
||||
HTML tag. In this case, you must use single quotes around the value, not
|
||||
double quotes, otherwise you will produce invalid or unsafe HTML.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
<div data-chart='{{ chart_data|tojson }}'></div>
|
||||
|
||||
|
||||
Generating URLs
|
||||
---------------
|
||||
|
||||
The other way to get data from the server to JavaScript is to make a
|
||||
request for it. First, you need to know the URL to request.
|
||||
|
||||
The simplest way to generate URLs is to continue to use
|
||||
:func:`~flask.url_for` when rendering the template. For example:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
const user_url = {{ url_for("user", id=current_user.id)|tojson }}
|
||||
fetch(user_url).then(...)
|
||||
|
||||
However, you might need to generate a URL based on information you only
|
||||
know in JavaScript. As discussed above, JavaScript runs in the user's
|
||||
browser, not as part of the template rendering, so you can't use
|
||||
``url_for`` at that point.
|
||||
|
||||
In this case, you need to know the "root URL" under which your
|
||||
application is served. In simple setups, this is ``/``, but it might
|
||||
also be something else, like ``https://example.com/myapp/``.
|
||||
|
||||
A simple way to tell your JavaScript code about this root is to set it
|
||||
as a global variable when rendering the template. Then you can use it
|
||||
when generating URLs from JavaScript.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
const SCRIPT_ROOT = {{ request.script_root|tojson }}
|
||||
let user_id = ... // do something to get a user id from the page
|
||||
let user_url = `${SCRIPT_ROOT}/user/${user_id}`
|
||||
fetch(user_url).then(...)
|
||||
|
||||
|
||||
Making a Request with ``fetch``
|
||||
-------------------------------
|
||||
|
||||
|fetch|_ takes two arguments, a URL and an object with other options,
|
||||
and returns a |Promise|_. We won't cover all the available options, and
|
||||
will only use ``then()`` on the promise, not other callbacks or
|
||||
``await`` syntax. Read the linked MDN docs for more information about
|
||||
those features.
|
||||
|
||||
By default, the GET method is used. If the response contains JSON, it
|
||||
can be used with a ``then()`` callback chain.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
const room_url = {{ url_for("room_detail", id=room.id)|tojson }}
|
||||
fetch(room_url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// data is a parsed JSON object
|
||||
})
|
||||
|
||||
To send data, use a data method such as POST, and pass the ``body``
|
||||
option. The most common types for data are form data or JSON data.
|
||||
|
||||
To send form data, pass a populated |FormData|_ object. This uses the
|
||||
same format as an HTML form, and would be accessed with ``request.form``
|
||||
in a Flask view.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
let data = new FormData()
|
||||
data.append("name", "Flask Room")
|
||||
data.append("description", "Talk about Flask here.")
|
||||
fetch(room_url, {
|
||||
"method": "POST",
|
||||
"body": data,
|
||||
}).then(...)
|
||||
|
||||
In general, prefer sending request data as form data, as would be used
|
||||
when submitting an HTML form. JSON can represent more complex data, but
|
||||
unless you need that it's better to stick with the simpler format. When
|
||||
sending JSON data, the ``Content-Type: application/json`` header must be
|
||||
sent as well, otherwise Flask will return a 400 error.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
let data = {
|
||||
"name": "Flask Room",
|
||||
"description": "Talk about Flask here.",
|
||||
}
|
||||
fetch(room_url, {
|
||||
"method": "POST",
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"body": JSON.stringify(data),
|
||||
}).then(...)
|
||||
|
||||
.. |Promise| replace:: ``Promise``
|
||||
.. _Promise: https://developer.mozilla.org/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
.. |FormData| replace:: ``FormData``
|
||||
.. _FormData: https://developer.mozilla.org/en-US/docs/Web/API/FormData
|
||||
|
||||
|
||||
Following Redirects
|
||||
-------------------
|
||||
|
||||
A response might be a redirect, for example if you logged in with
|
||||
JavaScript instead of a traditional HTML form, and your view returned
|
||||
a redirect instead of JSON. JavaScript requests do follow redirects, but
|
||||
they don't change the page. If you want to make the page change you can
|
||||
inspect the response and apply the redirect manually.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
fetch("/login", {"body": ...}).then(
|
||||
response => {
|
||||
if (response.redirected) {
|
||||
window.location = response.url
|
||||
} else {
|
||||
showLoginError()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Replacing Content
|
||||
-----------------
|
||||
|
||||
A response might be new HTML, either a new section of the page to add or
|
||||
replace, or an entirely new page. In general, if you're returning the
|
||||
entire page, it would be better to handle that with a redirect as shown
|
||||
in the previous section. The following example shows how to replace a
|
||||
``<div>`` with the HTML returned by a request.
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<div id="geology-fact">
|
||||
{{ include "geology_fact.html" }}
|
||||
</div>
|
||||
<script>
|
||||
const geology_url = {{ url_for("geology_fact")|tojson }}
|
||||
const geology_div = getElementById("geology-fact")
|
||||
fetch(geology_url)
|
||||
.then(response => response.text)
|
||||
.then(text => geology_div.innerHTML = text)
|
||||
</script>
|
||||
|
||||
|
||||
Return JSON from Views
|
||||
----------------------
|
||||
|
||||
To return a JSON object from your API view, you can directly return a
|
||||
dict from the view. It will be serialized to JSON automatically.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route("/user/<int:id>")
|
||||
def user_detail(id):
|
||||
user = User.query.get_or_404(id)
|
||||
return {
|
||||
"username": User.username,
|
||||
"email": User.email,
|
||||
"picture": url_for("static", filename=f"users/{id}/profile.png"),
|
||||
}
|
||||
|
||||
If you want to return another JSON type, use the
|
||||
:func:`~flask.json.jsonify` function, which creates a response object
|
||||
with the given data serialized to JSON.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
@app.route("/users")
|
||||
def user_list():
|
||||
users = User.query.order_by(User.name).all()
|
||||
return jsonify([u.to_json() for u in users])
|
||||
|
||||
It is usually not a good idea to return file data in a JSON response.
|
||||
JSON cannot represent binary data directly, so it must be base64
|
||||
encoded, which can be slow, takes more bandwidth to send, and is not as
|
||||
easy to cache. Instead, serve files using one view, and generate a URL
|
||||
to the desired file to include in the JSON. Then the client can make a
|
||||
separate request to get the linked resource after getting the JSON.
|
||||
|
||||
|
||||
Receiving JSON in Views
|
||||
-----------------------
|
||||
|
||||
Use the :attr:`~flask.Request.json` property of the
|
||||
:data:`~flask.request` object to decode the request's body as JSON. If
|
||||
the body is not valid JSON, or the ``Content-Type`` header is not set to
|
||||
``application/json``, a 400 Bad Request error will be raised.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import request
|
||||
|
||||
@app.post("/user/<int:id>")
|
||||
def user_update(id):
|
||||
user = User.query.get_or_404(id)
|
||||
user.update_from_json(request.json)
|
||||
db.session.commit()
|
||||
return user.to_json()
|
||||
@ -0,0 +1,6 @@
|
||||
:orphan:
|
||||
|
||||
AJAX with jQuery
|
||||
================
|
||||
|
||||
Obsolete, see :doc:`/patterns/javascript` instead.
|
||||
@ -0,0 +1,109 @@
|
||||
Lazily Loading Views
|
||||
====================
|
||||
|
||||
Flask is usually used with the decorators. Decorators are simple and you
|
||||
have the URL right next to the function that is called for that specific
|
||||
URL. However there is a downside to this approach: it means all your code
|
||||
that uses decorators has to be imported upfront or Flask will never
|
||||
actually find your function.
|
||||
|
||||
This can be a problem if your application has to import quick. It might
|
||||
have to do that on systems like Google's App Engine or other systems. So
|
||||
if you suddenly notice that your application outgrows this approach you
|
||||
can fall back to a centralized URL mapping.
|
||||
|
||||
The system that enables having a central URL map is the
|
||||
:meth:`~flask.Flask.add_url_rule` function. Instead of using decorators,
|
||||
you have a file that sets up the application with all URLs.
|
||||
|
||||
Converting to Centralized URL Map
|
||||
---------------------------------
|
||||
|
||||
Imagine the current application looks somewhat like this::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
pass
|
||||
|
||||
@app.route('/user/<username>')
|
||||
def user(username):
|
||||
pass
|
||||
|
||||
Then, with the centralized approach you would have one file with the views
|
||||
(:file:`views.py`) but without any decorator::
|
||||
|
||||
def index():
|
||||
pass
|
||||
|
||||
def user(username):
|
||||
pass
|
||||
|
||||
And then a file that sets up an application which maps the functions to
|
||||
URLs::
|
||||
|
||||
from flask import Flask
|
||||
from yourapplication import views
|
||||
app = Flask(__name__)
|
||||
app.add_url_rule('/', view_func=views.index)
|
||||
app.add_url_rule('/user/<username>', view_func=views.user)
|
||||
|
||||
Loading Late
|
||||
------------
|
||||
|
||||
So far we only split up the views and the routing, but the module is still
|
||||
loaded upfront. The trick is to actually load the view function as needed.
|
||||
This can be accomplished with a helper class that behaves just like a
|
||||
function but internally imports the real function on first use::
|
||||
|
||||
from werkzeug.utils import import_string, cached_property
|
||||
|
||||
class LazyView(object):
|
||||
|
||||
def __init__(self, import_name):
|
||||
self.__module__, self.__name__ = import_name.rsplit('.', 1)
|
||||
self.import_name = import_name
|
||||
|
||||
@cached_property
|
||||
def view(self):
|
||||
return import_string(self.import_name)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.view(*args, **kwargs)
|
||||
|
||||
What's important here is is that `__module__` and `__name__` are properly
|
||||
set. This is used by Flask internally to figure out how to name the
|
||||
URL rules in case you don't provide a name for the rule yourself.
|
||||
|
||||
Then you can define your central place to combine the views like this::
|
||||
|
||||
from flask import Flask
|
||||
from yourapplication.helpers import LazyView
|
||||
app = Flask(__name__)
|
||||
app.add_url_rule('/',
|
||||
view_func=LazyView('yourapplication.views.index'))
|
||||
app.add_url_rule('/user/<username>',
|
||||
view_func=LazyView('yourapplication.views.user'))
|
||||
|
||||
You can further optimize this in terms of amount of keystrokes needed to
|
||||
write this by having a function that calls into
|
||||
:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project
|
||||
name and a dot, and by wrapping `view_func` in a `LazyView` as needed. ::
|
||||
|
||||
def url(import_name, url_rules=[], **options):
|
||||
view = LazyView(f"yourapplication.{import_name}")
|
||||
for url_rule in url_rules:
|
||||
app.add_url_rule(url_rule, view_func=view, **options)
|
||||
|
||||
# add a single route to the index view
|
||||
url('views.index', ['/'])
|
||||
|
||||
# add two routes to a single function endpoint
|
||||
url_rules = ['/user/','/user/<username>']
|
||||
url('views.user', url_rules)
|
||||
|
||||
One thing to keep in mind is that before and after request handlers have
|
||||
to be in a file that is imported upfront to work properly on the first
|
||||
request. The same goes for any kind of remaining decorator.
|
||||
@ -0,0 +1,42 @@
|
||||
Adding HTTP Method Overrides
|
||||
============================
|
||||
|
||||
Some HTTP proxies do not support arbitrary HTTP methods or newer HTTP
|
||||
methods (such as PATCH). In that case it's possible to "proxy" HTTP
|
||||
methods through another HTTP method in total violation of the protocol.
|
||||
|
||||
The way this works is by letting the client do an HTTP POST request and
|
||||
set the ``X-HTTP-Method-Override`` header. Then the method is replaced
|
||||
with the header value before being passed to Flask.
|
||||
|
||||
This can be accomplished with an HTTP middleware::
|
||||
|
||||
class HTTPMethodOverrideMiddleware(object):
|
||||
allowed_methods = frozenset([
|
||||
'GET',
|
||||
'HEAD',
|
||||
'POST',
|
||||
'DELETE',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'OPTIONS'
|
||||
])
|
||||
bodyless_methods = frozenset(['GET', 'HEAD', 'OPTIONS', 'DELETE'])
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
method = environ.get('HTTP_X_HTTP_METHOD_OVERRIDE', '').upper()
|
||||
if method in self.allowed_methods:
|
||||
environ['REQUEST_METHOD'] = method
|
||||
if method in self.bodyless_methods:
|
||||
environ['CONTENT_LENGTH'] = '0'
|
||||
return self.app(environ, start_response)
|
||||
|
||||
To use this with Flask, wrap the app object with the middleware::
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app)
|
||||
@ -0,0 +1,103 @@
|
||||
MongoDB with MongoEngine
|
||||
========================
|
||||
|
||||
Using a document database like MongoDB is a common alternative to
|
||||
relational SQL databases. This pattern shows how to use
|
||||
`MongoEngine`_, a document mapper library, to integrate with MongoDB.
|
||||
|
||||
A running MongoDB server and `Flask-MongoEngine`_ are required. ::
|
||||
|
||||
pip install flask-mongoengine
|
||||
|
||||
.. _MongoEngine: http://mongoengine.org
|
||||
.. _Flask-MongoEngine: https://flask-mongoengine.readthedocs.io
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Basic setup can be done by defining ``MONGODB_SETTINGS`` on
|
||||
``app.config`` and creating a ``MongoEngine`` instance. ::
|
||||
|
||||
from flask import Flask
|
||||
from flask_mongoengine import MongoEngine
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['MONGODB_SETTINGS'] = {
|
||||
"db": "myapp",
|
||||
}
|
||||
db = MongoEngine(app)
|
||||
|
||||
|
||||
Mapping Documents
|
||||
-----------------
|
||||
|
||||
To declare a model that represents a Mongo document, create a class that
|
||||
inherits from ``Document`` and declare each of the fields. ::
|
||||
|
||||
import mongoengine as me
|
||||
|
||||
class Movie(me.Document):
|
||||
title = me.StringField(required=True)
|
||||
year = me.IntField()
|
||||
rated = me.StringField()
|
||||
director = me.StringField()
|
||||
actors = me.ListField()
|
||||
|
||||
If the document has nested fields, use ``EmbeddedDocument`` to
|
||||
defined the fields of the embedded document and
|
||||
``EmbeddedDocumentField`` to declare it on the parent document. ::
|
||||
|
||||
class Imdb(me.EmbeddedDocument):
|
||||
imdb_id = me.StringField()
|
||||
rating = me.DecimalField()
|
||||
votes = me.IntField()
|
||||
|
||||
class Movie(me.Document):
|
||||
...
|
||||
imdb = me.EmbeddedDocumentField(Imdb)
|
||||
|
||||
|
||||
Creating Data
|
||||
-------------
|
||||
|
||||
Instantiate your document class with keyword arguments for the fields.
|
||||
You can also assign values to the field attributes after instantiation.
|
||||
Then call ``doc.save()``. ::
|
||||
|
||||
bttf = Movie(title="Back To The Future", year=1985)
|
||||
bttf.actors = [
|
||||
"Michael J. Fox",
|
||||
"Christopher Lloyd"
|
||||
]
|
||||
bttf.imdb = Imdb(imdb_id="tt0088763", rating=8.5)
|
||||
bttf.save()
|
||||
|
||||
|
||||
Queries
|
||||
-------
|
||||
|
||||
Use the class ``objects`` attribute to make queries. A keyword argument
|
||||
looks for an equal value on the field. ::
|
||||
|
||||
bttf = Movie.objects(title="Back To The Future").get_or_404()
|
||||
|
||||
Query operators may be used by concatenating them with the field name
|
||||
using a double-underscore. ``objects``, and queries returned by
|
||||
calling it, are iterable. ::
|
||||
|
||||
some_theron_movie = Movie.objects(actors__in=["Charlize Theron"]).first()
|
||||
|
||||
for recents in Movie.objects(year__gte=2017):
|
||||
print(recents.title)
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
There are many more ways to define and query documents with MongoEngine.
|
||||
For more information, check out the `official documentation
|
||||
<MongoEngine_>`_.
|
||||
|
||||
Flask-MongoEngine adds helpful utilities on top of MongoEngine. Check
|
||||
out their `documentation <Flask-MongoEngine_>`_ as well.
|
||||
@ -0,0 +1,133 @@
|
||||
Large Applications as Packages
|
||||
==============================
|
||||
|
||||
Imagine a simple flask application structure that looks like this::
|
||||
|
||||
/yourapplication
|
||||
yourapplication.py
|
||||
/static
|
||||
style.css
|
||||
/templates
|
||||
layout.html
|
||||
index.html
|
||||
login.html
|
||||
...
|
||||
|
||||
While this is fine for small applications, for larger applications
|
||||
it's a good idea to use a package instead of a module.
|
||||
The :doc:`/tutorial/index` is structured to use the package pattern,
|
||||
see the :gh:`example code <examples/tutorial>`.
|
||||
|
||||
Simple Packages
|
||||
---------------
|
||||
|
||||
To convert that into a larger one, just create a new folder
|
||||
:file:`yourapplication` inside the existing one and move everything below it.
|
||||
Then rename :file:`yourapplication.py` to :file:`__init__.py`. (Make sure to delete
|
||||
all ``.pyc`` files first, otherwise things would most likely break)
|
||||
|
||||
You should then end up with something like that::
|
||||
|
||||
/yourapplication
|
||||
/yourapplication
|
||||
__init__.py
|
||||
/static
|
||||
style.css
|
||||
/templates
|
||||
layout.html
|
||||
index.html
|
||||
login.html
|
||||
...
|
||||
|
||||
But how do you run your application now? The naive ``python
|
||||
yourapplication/__init__.py`` will not work. Let's just say that Python
|
||||
does not want modules in packages to be the startup file. But that is not
|
||||
a big problem, just add a new file called :file:`pyproject.toml` next to the inner
|
||||
:file:`yourapplication` folder with the following contents:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[project]
|
||||
name = "yourapplication"
|
||||
dependencies = [
|
||||
"flask",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["flit_core<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
Install your application so it is importable:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install -e .
|
||||
|
||||
To use the ``flask`` command and run your application you need to set
|
||||
the ``--app`` option that tells Flask where to find the application
|
||||
instance:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app yourapplication run
|
||||
|
||||
What did we gain from this? Now we can restructure the application a bit
|
||||
into multiple modules. The only thing you have to remember is the
|
||||
following quick checklist:
|
||||
|
||||
1. the `Flask` application object creation has to be in the
|
||||
:file:`__init__.py` file. That way each module can import it safely and the
|
||||
`__name__` variable will resolve to the correct package.
|
||||
2. all the view functions (the ones with a :meth:`~flask.Flask.route`
|
||||
decorator on top) have to be imported in the :file:`__init__.py` file.
|
||||
Not the object itself, but the module it is in. Import the view module
|
||||
**after the application object is created**.
|
||||
|
||||
Here's an example :file:`__init__.py`::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
import yourapplication.views
|
||||
|
||||
And this is what :file:`views.py` would look like::
|
||||
|
||||
from yourapplication import app
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Hello World!'
|
||||
|
||||
You should then end up with something like that::
|
||||
|
||||
/yourapplication
|
||||
pyproject.toml
|
||||
/yourapplication
|
||||
__init__.py
|
||||
views.py
|
||||
/static
|
||||
style.css
|
||||
/templates
|
||||
layout.html
|
||||
index.html
|
||||
login.html
|
||||
...
|
||||
|
||||
.. admonition:: Circular Imports
|
||||
|
||||
Every Python programmer hates them, and yet we just added some:
|
||||
circular imports (That's when two modules depend on each other. In this
|
||||
case :file:`views.py` depends on :file:`__init__.py`). Be advised that this is a
|
||||
bad idea in general but here it is actually fine. The reason for this is
|
||||
that we are not actually using the views in :file:`__init__.py` and just
|
||||
ensuring the module is imported and we are doing that at the bottom of
|
||||
the file.
|
||||
|
||||
|
||||
Working with Blueprints
|
||||
-----------------------
|
||||
|
||||
If you have larger applications it's recommended to divide them into
|
||||
smaller groups where each group is implemented with the help of a
|
||||
blueprint. For a gentle introduction into this topic refer to the
|
||||
:doc:`/blueprints` chapter of the documentation.
|
||||
@ -0,0 +1,55 @@
|
||||
Request Content Checksums
|
||||
=========================
|
||||
|
||||
Various pieces of code can consume the request data and preprocess it.
|
||||
For instance JSON data ends up on the request object already read and
|
||||
processed, form data ends up there as well but goes through a different
|
||||
code path. This seems inconvenient when you want to calculate the
|
||||
checksum of the incoming request data. This is necessary sometimes for
|
||||
some APIs.
|
||||
|
||||
Fortunately this is however very simple to change by wrapping the input
|
||||
stream.
|
||||
|
||||
The following example calculates the SHA1 checksum of the incoming data as
|
||||
it gets read and stores it in the WSGI environment::
|
||||
|
||||
import hashlib
|
||||
|
||||
class ChecksumCalcStream(object):
|
||||
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
self._hash = hashlib.sha1()
|
||||
|
||||
def read(self, bytes):
|
||||
rv = self._stream.read(bytes)
|
||||
self._hash.update(rv)
|
||||
return rv
|
||||
|
||||
def readline(self, size_hint):
|
||||
rv = self._stream.readline(size_hint)
|
||||
self._hash.update(rv)
|
||||
return rv
|
||||
|
||||
def generate_checksum(request):
|
||||
env = request.environ
|
||||
stream = ChecksumCalcStream(env['wsgi.input'])
|
||||
env['wsgi.input'] = stream
|
||||
return stream._hash
|
||||
|
||||
To use this, all you need to do is to hook the calculating stream in
|
||||
before the request starts consuming data. (Eg: be careful accessing
|
||||
``request.form`` or anything of that nature. ``before_request_handlers``
|
||||
for instance should be careful not to access it).
|
||||
|
||||
Example usage::
|
||||
|
||||
@app.route('/special-api', methods=['POST'])
|
||||
def special_api():
|
||||
hash = generate_checksum(request)
|
||||
# Accessing this parses the input stream
|
||||
files = request.files
|
||||
# At this point the hash is fully constructed.
|
||||
checksum = hash.hexdigest()
|
||||
return f"Hash was: {checksum}"
|
||||
@ -0,0 +1,24 @@
|
||||
Single-Page Applications
|
||||
========================
|
||||
|
||||
Flask can be used to serve Single-Page Applications (SPA) by placing static
|
||||
files produced by your frontend framework in a subfolder inside of your
|
||||
project. You will also need to create a catch-all endpoint that routes all
|
||||
requests to your SPA.
|
||||
|
||||
The following example demonstrates how to serve an SPA along with an API::
|
||||
|
||||
from flask import Flask, jsonify
|
||||
|
||||
app = Flask(__name__, static_folder='app', static_url_path="/app")
|
||||
|
||||
|
||||
@app.route("/heartbeat")
|
||||
def heartbeat():
|
||||
return jsonify({"status": "healthy"})
|
||||
|
||||
|
||||
@app.route('/', defaults={'path': ''})
|
||||
@app.route('/<path:path>')
|
||||
def catch_all(path):
|
||||
return app.send_static_file("index.html")
|
||||
@ -0,0 +1,213 @@
|
||||
SQLAlchemy in Flask
|
||||
===================
|
||||
|
||||
Many people prefer `SQLAlchemy`_ for database access. In this case it's
|
||||
encouraged to use a package instead of a module for your flask application
|
||||
and drop the models into a separate module (:doc:`packages`). While that
|
||||
is not necessary, it makes a lot of sense.
|
||||
|
||||
There are four very common ways to use SQLAlchemy. I will outline each
|
||||
of them here:
|
||||
|
||||
Flask-SQLAlchemy Extension
|
||||
--------------------------
|
||||
|
||||
Because SQLAlchemy is a common database abstraction layer and object
|
||||
relational mapper that requires a little bit of configuration effort,
|
||||
there is a Flask extension that handles that for you. This is recommended
|
||||
if you want to get started quickly.
|
||||
|
||||
You can download `Flask-SQLAlchemy`_ from `PyPI
|
||||
<https://pypi.org/project/Flask-SQLAlchemy/>`_.
|
||||
|
||||
.. _Flask-SQLAlchemy: https://flask-sqlalchemy.palletsprojects.com/
|
||||
|
||||
|
||||
Declarative
|
||||
-----------
|
||||
|
||||
The declarative extension in SQLAlchemy is the most recent method of using
|
||||
SQLAlchemy. It allows you to define tables and models in one go, similar
|
||||
to how Django works. In addition to the following text I recommend the
|
||||
official documentation on the `declarative`_ extension.
|
||||
|
||||
Here's the example :file:`database.py` module for your application::
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker, declarative_base
|
||||
|
||||
engine = create_engine('sqlite:////tmp/test.db')
|
||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
Base = declarative_base()
|
||||
Base.query = db_session.query_property()
|
||||
|
||||
def init_db():
|
||||
# import all modules here that might define models so that
|
||||
# they will be registered properly on the metadata. Otherwise
|
||||
# you will have to import them first before calling init_db()
|
||||
import yourapplication.models
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
To define your models, just subclass the `Base` class that was created by
|
||||
the code above. If you are wondering why we don't have to care about
|
||||
threads here (like we did in the SQLite3 example above with the
|
||||
:data:`~flask.g` object): that's because SQLAlchemy does that for us
|
||||
already with the :class:`~sqlalchemy.orm.scoped_session`.
|
||||
|
||||
To use SQLAlchemy in a declarative way with your application, you just
|
||||
have to put the following code into your application module. Flask will
|
||||
automatically remove database sessions at the end of the request or
|
||||
when the application shuts down::
|
||||
|
||||
from yourapplication.database import db_session
|
||||
|
||||
@app.teardown_appcontext
|
||||
def shutdown_session(exception=None):
|
||||
db_session.remove()
|
||||
|
||||
Here is an example model (put this into :file:`models.py`, e.g.)::
|
||||
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from yourapplication.database import Base
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), unique=True)
|
||||
email = Column(String(120), unique=True)
|
||||
|
||||
def __init__(self, name=None, email=None):
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.name!r}>'
|
||||
|
||||
To create the database you can use the `init_db` function:
|
||||
|
||||
>>> from yourapplication.database import init_db
|
||||
>>> init_db()
|
||||
|
||||
You can insert entries into the database like this:
|
||||
|
||||
>>> from yourapplication.database import db_session
|
||||
>>> from yourapplication.models import User
|
||||
>>> u = User('admin', 'admin@localhost')
|
||||
>>> db_session.add(u)
|
||||
>>> db_session.commit()
|
||||
|
||||
Querying is simple as well:
|
||||
|
||||
>>> User.query.all()
|
||||
[<User 'admin'>]
|
||||
>>> User.query.filter(User.name == 'admin').first()
|
||||
<User 'admin'>
|
||||
|
||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||
.. _declarative: https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/
|
||||
|
||||
Manual Object Relational Mapping
|
||||
--------------------------------
|
||||
|
||||
Manual object relational mapping has a few upsides and a few downsides
|
||||
versus the declarative approach from above. The main difference is that
|
||||
you define tables and classes separately and map them together. It's more
|
||||
flexible but a little more to type. In general it works like the
|
||||
declarative approach, so make sure to also split up your application into
|
||||
multiple modules in a package.
|
||||
|
||||
Here is an example :file:`database.py` module for your application::
|
||||
|
||||
from sqlalchemy import create_engine, MetaData
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
engine = create_engine('sqlite:////tmp/test.db')
|
||||
metadata = MetaData()
|
||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
def init_db():
|
||||
metadata.create_all(bind=engine)
|
||||
|
||||
As in the declarative approach, you need to close the session after each app
|
||||
context. Put this into your application module::
|
||||
|
||||
from yourapplication.database import db_session
|
||||
|
||||
@app.teardown_appcontext
|
||||
def shutdown_session(exception=None):
|
||||
db_session.remove()
|
||||
|
||||
Here is an example table and model (put this into :file:`models.py`)::
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, String
|
||||
from sqlalchemy.orm import mapper
|
||||
from yourapplication.database import metadata, db_session
|
||||
|
||||
class User(object):
|
||||
query = db_session.query_property()
|
||||
|
||||
def __init__(self, name=None, email=None):
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.name!r}>'
|
||||
|
||||
users = Table('users', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String(50), unique=True),
|
||||
Column('email', String(120), unique=True)
|
||||
)
|
||||
mapper(User, users)
|
||||
|
||||
Querying and inserting works exactly the same as in the example above.
|
||||
|
||||
|
||||
SQL Abstraction Layer
|
||||
---------------------
|
||||
|
||||
If you just want to use the database system (and SQL) abstraction layer
|
||||
you basically only need the engine::
|
||||
|
||||
from sqlalchemy import create_engine, MetaData, Table
|
||||
|
||||
engine = create_engine('sqlite:////tmp/test.db')
|
||||
metadata = MetaData(bind=engine)
|
||||
|
||||
Then you can either declare the tables in your code like in the examples
|
||||
above, or automatically load them::
|
||||
|
||||
from sqlalchemy import Table
|
||||
|
||||
users = Table('users', metadata, autoload=True)
|
||||
|
||||
To insert data you can use the `insert` method. We have to get a
|
||||
connection first so that we can use a transaction:
|
||||
|
||||
>>> con = engine.connect()
|
||||
>>> con.execute(users.insert(), name='admin', email='admin@localhost')
|
||||
|
||||
SQLAlchemy will automatically commit for us.
|
||||
|
||||
To query your database, you use the engine directly or use a connection:
|
||||
|
||||
>>> users.select(users.c.id == 1).execute().first()
|
||||
(1, 'admin', 'admin@localhost')
|
||||
|
||||
These results are also dict-like tuples:
|
||||
|
||||
>>> r = users.select(users.c.id == 1).execute().first()
|
||||
>>> r['name']
|
||||
'admin'
|
||||
|
||||
You can also pass strings of SQL statements to the
|
||||
:meth:`~sqlalchemy.engine.base.Connection.execute` method:
|
||||
|
||||
>>> engine.execute('select * from users where id = :1', [1]).first()
|
||||
(1, 'admin', 'admin@localhost')
|
||||
|
||||
For more information about SQLAlchemy, head over to the
|
||||
`website <https://www.sqlalchemy.org/>`_.
|
||||
@ -0,0 +1,147 @@
|
||||
Using SQLite 3 with Flask
|
||||
=========================
|
||||
|
||||
You can implement a few functions to work with a SQLite connection during a
|
||||
request context. The connection is created the first time it's accessed,
|
||||
reused on subsequent access, until it is closed when the request context ends.
|
||||
|
||||
Here is a simple example of how you can use SQLite 3 with Flask::
|
||||
|
||||
import sqlite3
|
||||
from flask import g
|
||||
|
||||
DATABASE = '/path/to/database.db'
|
||||
|
||||
def get_db():
|
||||
db = getattr(g, '_database', None)
|
||||
if db is None:
|
||||
db = g._database = sqlite3.connect(DATABASE)
|
||||
return db
|
||||
|
||||
@app.teardown_appcontext
|
||||
def close_connection(exception):
|
||||
db = getattr(g, '_database', None)
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
Now, to use the database, the application must either have an active
|
||||
application context (which is always true if there is a request in flight)
|
||||
or create an application context itself. At that point the ``get_db``
|
||||
function can be used to get the current database connection. Whenever the
|
||||
context is destroyed the database connection will be terminated.
|
||||
|
||||
Example::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
cur = get_db().cursor()
|
||||
...
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Please keep in mind that the teardown request and appcontext functions
|
||||
are always executed, even if a before-request handler failed or was
|
||||
never executed. Because of this we have to make sure here that the
|
||||
database is there before we close it.
|
||||
|
||||
Connect on Demand
|
||||
-----------------
|
||||
|
||||
The upside of this approach (connecting on first use) is that this will
|
||||
only open the connection if truly necessary. If you want to use this
|
||||
code outside a request context you can use it in a Python shell by opening
|
||||
the application context by hand::
|
||||
|
||||
with app.app_context():
|
||||
# now you can use get_db()
|
||||
|
||||
|
||||
Easy Querying
|
||||
-------------
|
||||
|
||||
Now in each request handling function you can access `get_db()` to get the
|
||||
current open database connection. To simplify working with SQLite, a
|
||||
row factory function is useful. It is executed for every result returned
|
||||
from the database to convert the result. For instance, in order to get
|
||||
dictionaries instead of tuples, this could be inserted into the ``get_db``
|
||||
function we created above::
|
||||
|
||||
def make_dicts(cursor, row):
|
||||
return dict((cursor.description[idx][0], value)
|
||||
for idx, value in enumerate(row))
|
||||
|
||||
db.row_factory = make_dicts
|
||||
|
||||
This will make the sqlite3 module return dicts for this database connection, which are much nicer to deal with. Even more simply, we could place this in ``get_db`` instead::
|
||||
|
||||
db.row_factory = sqlite3.Row
|
||||
|
||||
This would use Row objects rather than dicts to return the results of queries. These are ``namedtuple`` s, so we can access them either by index or by key. For example, assuming we have a ``sqlite3.Row`` called ``r`` for the rows ``id``, ``FirstName``, ``LastName``, and ``MiddleInitial``::
|
||||
|
||||
>>> # You can get values based on the row's name
|
||||
>>> r['FirstName']
|
||||
John
|
||||
>>> # Or, you can get them based on index
|
||||
>>> r[1]
|
||||
John
|
||||
# Row objects are also iterable:
|
||||
>>> for value in r:
|
||||
... print(value)
|
||||
1
|
||||
John
|
||||
Doe
|
||||
M
|
||||
|
||||
Additionally, it is a good idea to provide a query function that combines
|
||||
getting the cursor, executing and fetching the results::
|
||||
|
||||
def query_db(query, args=(), one=False):
|
||||
cur = get_db().execute(query, args)
|
||||
rv = cur.fetchall()
|
||||
cur.close()
|
||||
return (rv[0] if rv else None) if one else rv
|
||||
|
||||
This handy little function, in combination with a row factory, makes
|
||||
working with the database much more pleasant than it is by just using the
|
||||
raw cursor and connection objects.
|
||||
|
||||
Here is how you can use it::
|
||||
|
||||
for user in query_db('select * from users'):
|
||||
print(user['username'], 'has the id', user['user_id'])
|
||||
|
||||
Or if you just want a single result::
|
||||
|
||||
user = query_db('select * from users where username = ?',
|
||||
[the_username], one=True)
|
||||
if user is None:
|
||||
print('No such user')
|
||||
else:
|
||||
print(the_username, 'has the id', user['user_id'])
|
||||
|
||||
To pass variable parts to the SQL statement, use a question mark in the
|
||||
statement and pass in the arguments as a list. Never directly add them to
|
||||
the SQL statement with string formatting because this makes it possible
|
||||
to attack the application using `SQL Injections
|
||||
<https://en.wikipedia.org/wiki/SQL_injection>`_.
|
||||
|
||||
Initial Schemas
|
||||
---------------
|
||||
|
||||
Relational databases need schemas, so applications often ship a
|
||||
`schema.sql` file that creates the database. It's a good idea to provide
|
||||
a function that creates the database based on that schema. This function
|
||||
can do that for you::
|
||||
|
||||
def init_db():
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
with app.open_resource('schema.sql', mode='r') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
|
||||
You can then create such a database from the Python shell:
|
||||
|
||||
>>> from yourapplication import init_db
|
||||
>>> init_db()
|
||||
@ -0,0 +1,85 @@
|
||||
Streaming Contents
|
||||
==================
|
||||
|
||||
Sometimes you want to send an enormous amount of data to the client, much
|
||||
more than you want to keep in memory. When you are generating the data on
|
||||
the fly though, how do you send that back to the client without the
|
||||
roundtrip to the filesystem?
|
||||
|
||||
The answer is by using generators and direct responses.
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
This is a basic view function that generates a lot of CSV data on the fly.
|
||||
The trick is to have an inner function that uses a generator to generate
|
||||
data and to then invoke that function and pass it to a response object::
|
||||
|
||||
@app.route('/large.csv')
|
||||
def generate_large_csv():
|
||||
def generate():
|
||||
for row in iter_all_rows():
|
||||
yield f"{','.join(row)}\n"
|
||||
return generate(), {"Content-Type": "text/csv"}
|
||||
|
||||
Each ``yield`` expression is directly sent to the browser. Note though
|
||||
that some WSGI middlewares might break streaming, so be careful there in
|
||||
debug environments with profilers and other things you might have enabled.
|
||||
|
||||
Streaming from Templates
|
||||
------------------------
|
||||
|
||||
The Jinja template engine supports rendering a template piece by
|
||||
piece, returning an iterator of strings. Flask provides the
|
||||
:func:`~flask.stream_template` and :func:`~flask.stream_template_string`
|
||||
functions to make this easier to use.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import stream_template
|
||||
|
||||
@app.get("/timeline")
|
||||
def timeline():
|
||||
return stream_template("timeline.html")
|
||||
|
||||
The parts yielded by the render stream tend to match statement blocks in
|
||||
the template.
|
||||
|
||||
|
||||
Streaming with Context
|
||||
----------------------
|
||||
|
||||
The :data:`.request` proxy will not be active while the generator is
|
||||
running, because the app has already returned control to the WSGI server at that
|
||||
point. If you try to access ``request``, you'll get a ``RuntimeError``.
|
||||
|
||||
If your generator function relies on data in ``request``, use the
|
||||
:func:`.stream_with_context` wrapper. This will keep the request context active
|
||||
during the generator.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import stream_with_context, request
|
||||
from markupsafe import escape
|
||||
|
||||
@app.route('/stream')
|
||||
def streamed_response():
|
||||
def generate():
|
||||
yield '<p>Hello '
|
||||
yield escape(request.args['name'])
|
||||
yield '!</p>'
|
||||
return stream_with_context(generate())
|
||||
|
||||
It can also be used as a decorator.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@stream_with_context
|
||||
def generate():
|
||||
...
|
||||
|
||||
return generate()
|
||||
|
||||
The :func:`~flask.stream_template` and
|
||||
:func:`~flask.stream_template_string` functions automatically
|
||||
use :func:`~flask.stream_with_context` if a request is active.
|
||||
@ -0,0 +1,17 @@
|
||||
Subclassing Flask
|
||||
=================
|
||||
|
||||
The :class:`~flask.Flask` class is designed for subclassing.
|
||||
|
||||
For example, you may want to override how request parameters are handled to preserve their order::
|
||||
|
||||
from flask import Flask, Request
|
||||
from werkzeug.datastructures import ImmutableOrderedMultiDict
|
||||
class MyRequest(Request):
|
||||
"""Request subclass to override request parameter storage"""
|
||||
parameter_storage_class = ImmutableOrderedMultiDict
|
||||
class MyFlask(Flask):
|
||||
"""Flask subclass using the custom request class"""
|
||||
request_class = MyRequest
|
||||
|
||||
This is the recommended approach for overriding or augmenting Flask's internal functionality.
|
||||
@ -0,0 +1,68 @@
|
||||
Template Inheritance
|
||||
====================
|
||||
|
||||
The most powerful part of Jinja is template inheritance. Template inheritance
|
||||
allows you to build a base "skeleton" template that contains all the common
|
||||
elements of your site and defines **blocks** that child templates can override.
|
||||
|
||||
Sounds complicated but is very basic. It's easiest to understand it by starting
|
||||
with an example.
|
||||
|
||||
|
||||
Base Template
|
||||
-------------
|
||||
|
||||
This template, which we'll call :file:`layout.html`, defines a simple HTML skeleton
|
||||
document that you might use for a simple two-column page. It's the job of
|
||||
"child" templates to fill the empty blocks with content:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<title>{% block title %}{% endblock %} - My Webpage</title>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">{% block content %}{% endblock %}</div>
|
||||
<div id="footer">
|
||||
{% block footer %}
|
||||
© Copyright 2010 by <a href="http://domain.invalid/">you</a>.
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
In this example, the ``{% block %}`` tags define four blocks that child templates
|
||||
can fill in. All the `block` tag does is tell the template engine that a
|
||||
child template may override those portions of the template.
|
||||
|
||||
Child Template
|
||||
--------------
|
||||
|
||||
A child template might look like this:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Index{% endblock %}
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<style type="text/css">
|
||||
.important { color: #336699; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Index</h1>
|
||||
<p class="important">
|
||||
Welcome on my awesome homepage.
|
||||
{% endblock %}
|
||||
|
||||
The ``{% extends %}`` tag is the key here. It tells the template engine that
|
||||
this template "extends" another template. When the template system evaluates
|
||||
this template, first it locates the parent. The extends tag must be the
|
||||
first tag in the template. To render the contents of a block defined in
|
||||
the parent template, use ``{{ super() }}``.
|
||||
@ -0,0 +1,126 @@
|
||||
Using URL Processors
|
||||
====================
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
Flask 0.7 introduces the concept of URL processors. The idea is that you
|
||||
might have a bunch of resources with common parts in the URL that you
|
||||
don't always explicitly want to provide. For instance you might have a
|
||||
bunch of URLs that have the language code in it but you don't want to have
|
||||
to handle it in every single function yourself.
|
||||
|
||||
URL processors are especially helpful when combined with blueprints. We
|
||||
will handle both application specific URL processors here as well as
|
||||
blueprint specifics.
|
||||
|
||||
Internationalized Application URLs
|
||||
----------------------------------
|
||||
|
||||
Consider an application like this::
|
||||
|
||||
from flask import Flask, g
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/<lang_code>/')
|
||||
def index(lang_code):
|
||||
g.lang_code = lang_code
|
||||
...
|
||||
|
||||
@app.route('/<lang_code>/about')
|
||||
def about(lang_code):
|
||||
g.lang_code = lang_code
|
||||
...
|
||||
|
||||
This is an awful lot of repetition as you have to handle the language code
|
||||
setting on the :data:`~flask.g` object yourself in every single function.
|
||||
Sure, a decorator could be used to simplify this, but if you want to
|
||||
generate URLs from one function to another you would have to still provide
|
||||
the language code explicitly which can be annoying.
|
||||
|
||||
For the latter, this is where :func:`~flask.Flask.url_defaults` functions
|
||||
come in. They can automatically inject values into a call to
|
||||
:func:`~flask.url_for`. The code below checks if the
|
||||
language code is not yet in the dictionary of URL values and if the
|
||||
endpoint wants a value named ``'lang_code'``::
|
||||
|
||||
@app.url_defaults
|
||||
def add_language_code(endpoint, values):
|
||||
if 'lang_code' in values or not g.lang_code:
|
||||
return
|
||||
if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
|
||||
values['lang_code'] = g.lang_code
|
||||
|
||||
The method :meth:`~werkzeug.routing.Map.is_endpoint_expecting` of the URL
|
||||
map can be used to figure out if it would make sense to provide a language
|
||||
code for the given endpoint.
|
||||
|
||||
The reverse of that function are
|
||||
:meth:`~flask.Flask.url_value_preprocessor`\s. They are executed right
|
||||
after the request was matched and can execute code based on the URL
|
||||
values. The idea is that they pull information out of the values
|
||||
dictionary and put it somewhere else::
|
||||
|
||||
@app.url_value_preprocessor
|
||||
def pull_lang_code(endpoint, values):
|
||||
g.lang_code = values.pop('lang_code', None)
|
||||
|
||||
That way you no longer have to do the `lang_code` assignment to
|
||||
:data:`~flask.g` in every function. You can further improve that by
|
||||
writing your own decorator that prefixes URLs with the language code, but
|
||||
the more beautiful solution is using a blueprint. Once the
|
||||
``'lang_code'`` is popped from the values dictionary and it will no longer
|
||||
be forwarded to the view function reducing the code to this::
|
||||
|
||||
from flask import Flask, g
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.url_defaults
|
||||
def add_language_code(endpoint, values):
|
||||
if 'lang_code' in values or not g.lang_code:
|
||||
return
|
||||
if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
|
||||
values['lang_code'] = g.lang_code
|
||||
|
||||
@app.url_value_preprocessor
|
||||
def pull_lang_code(endpoint, values):
|
||||
g.lang_code = values.pop('lang_code', None)
|
||||
|
||||
@app.route('/<lang_code>/')
|
||||
def index():
|
||||
...
|
||||
|
||||
@app.route('/<lang_code>/about')
|
||||
def about():
|
||||
...
|
||||
|
||||
Internationalized Blueprint URLs
|
||||
--------------------------------
|
||||
|
||||
Because blueprints can automatically prefix all URLs with a common string
|
||||
it's easy to automatically do that for every function. Furthermore
|
||||
blueprints can have per-blueprint URL processors which removes a whole lot
|
||||
of logic from the :meth:`~flask.Flask.url_defaults` function because it no
|
||||
longer has to check if the URL is really interested in a ``'lang_code'``
|
||||
parameter::
|
||||
|
||||
from flask import Blueprint, g
|
||||
|
||||
bp = Blueprint('frontend', __name__, url_prefix='/<lang_code>')
|
||||
|
||||
@bp.url_defaults
|
||||
def add_language_code(endpoint, values):
|
||||
values.setdefault('lang_code', g.lang_code)
|
||||
|
||||
@bp.url_value_preprocessor
|
||||
def pull_lang_code(endpoint, values):
|
||||
g.lang_code = values.pop('lang_code')
|
||||
|
||||
@bp.route('/')
|
||||
def index():
|
||||
...
|
||||
|
||||
@bp.route('/about')
|
||||
def about():
|
||||
...
|
||||
@ -0,0 +1,171 @@
|
||||
View Decorators
|
||||
===============
|
||||
|
||||
Python has a really interesting feature called function decorators. This
|
||||
allows some really neat things for web applications. Because each view in
|
||||
Flask is a function, decorators can be used to inject additional
|
||||
functionality to one or more functions. The :meth:`~flask.Flask.route`
|
||||
decorator is the one you probably used already. But there are use cases
|
||||
for implementing your own decorator. For instance, imagine you have a
|
||||
view that should only be used by people that are logged in. If a user
|
||||
goes to the site and is not logged in, they should be redirected to the
|
||||
login page. This is a good example of a use case where a decorator is an
|
||||
excellent solution.
|
||||
|
||||
Login Required Decorator
|
||||
------------------------
|
||||
|
||||
So let's implement such a decorator. A decorator is a function that
|
||||
wraps and replaces another function. Since the original function is
|
||||
replaced, you need to remember to copy the original function's information
|
||||
to the new function. Use :func:`functools.wraps` to handle this for you.
|
||||
|
||||
This example assumes that the login page is called ``'login'`` and that
|
||||
the current user is stored in ``g.user`` and is ``None`` if there is no-one
|
||||
logged in. ::
|
||||
|
||||
from functools import wraps
|
||||
from flask import g, request, redirect, url_for
|
||||
|
||||
def login_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if g.user is None:
|
||||
return redirect(url_for('login', next=request.url))
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
To use the decorator, apply it as innermost decorator to a view function.
|
||||
When applying further decorators, always remember
|
||||
that the :meth:`~flask.Flask.route` decorator is the outermost. ::
|
||||
|
||||
@app.route('/secret_page')
|
||||
@login_required
|
||||
def secret_page():
|
||||
pass
|
||||
|
||||
.. note::
|
||||
The ``next`` value will exist in ``request.args`` after a ``GET`` request for
|
||||
the login page. You'll have to pass it along when sending the ``POST`` request
|
||||
from the login form. You can do this with a hidden input tag, then retrieve it
|
||||
from ``request.form`` when logging the user in. ::
|
||||
|
||||
<input type="hidden" value="{{ request.args.get('next', '') }}"/>
|
||||
|
||||
|
||||
Caching Decorator
|
||||
-----------------
|
||||
|
||||
Imagine you have a view function that does an expensive calculation and
|
||||
because of that you would like to cache the generated results for a
|
||||
certain amount of time. A decorator would be nice for that. We're
|
||||
assuming you have set up a cache like mentioned in :doc:`caching`.
|
||||
|
||||
Here is an example cache function. It generates the cache key from a
|
||||
specific prefix (actually a format string) and the current path of the
|
||||
request. Notice that we are using a function that first creates the
|
||||
decorator that then decorates the function. Sounds awful? Unfortunately
|
||||
it is a little bit more complex, but the code should still be
|
||||
straightforward to read.
|
||||
|
||||
The decorated function will then work as follows
|
||||
|
||||
1. get the unique cache key for the current request based on the current
|
||||
path.
|
||||
2. get the value for that key from the cache. If the cache returned
|
||||
something we will return that value.
|
||||
3. otherwise the original function is called and the return value is
|
||||
stored in the cache for the timeout provided (by default 5 minutes).
|
||||
|
||||
Here the code::
|
||||
|
||||
from functools import wraps
|
||||
from flask import request
|
||||
|
||||
def cached(timeout=5 * 60, key='view/{}'):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
cache_key = key.format(request.path)
|
||||
rv = cache.get(cache_key)
|
||||
if rv is not None:
|
||||
return rv
|
||||
rv = f(*args, **kwargs)
|
||||
cache.set(cache_key, rv, timeout=timeout)
|
||||
return rv
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
Notice that this assumes an instantiated ``cache`` object is available, see
|
||||
:doc:`caching`.
|
||||
|
||||
|
||||
Templating Decorator
|
||||
--------------------
|
||||
|
||||
A common pattern invented by the TurboGears guys a while back is a
|
||||
templating decorator. The idea of that decorator is that you return a
|
||||
dictionary with the values passed to the template from the view function
|
||||
and the template is automatically rendered. With that, the following
|
||||
three examples do exactly the same::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html', value=42)
|
||||
|
||||
@app.route('/')
|
||||
@templated('index.html')
|
||||
def index():
|
||||
return dict(value=42)
|
||||
|
||||
@app.route('/')
|
||||
@templated()
|
||||
def index():
|
||||
return dict(value=42)
|
||||
|
||||
As you can see, if no template name is provided it will use the endpoint
|
||||
of the URL map with dots converted to slashes + ``'.html'``. Otherwise
|
||||
the provided template name is used. When the decorated function returns,
|
||||
the dictionary returned is passed to the template rendering function. If
|
||||
``None`` is returned, an empty dictionary is assumed, if something else than
|
||||
a dictionary is returned we return it from the function unchanged. That
|
||||
way you can still use the redirect function or return simple strings.
|
||||
|
||||
Here is the code for that decorator::
|
||||
|
||||
from functools import wraps
|
||||
from flask import request, render_template
|
||||
|
||||
def templated(template=None):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
template_name = template
|
||||
if template_name is None:
|
||||
template_name = f"{request.endpoint.replace('.', '/')}.html"
|
||||
ctx = f(*args, **kwargs)
|
||||
if ctx is None:
|
||||
ctx = {}
|
||||
elif not isinstance(ctx, dict):
|
||||
return ctx
|
||||
return render_template(template_name, **ctx)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
|
||||
Endpoint Decorator
|
||||
------------------
|
||||
|
||||
When you want to use the werkzeug routing system for more flexibility you
|
||||
need to map the endpoint as defined in the :class:`~werkzeug.routing.Rule`
|
||||
to a view function. This is possible with this decorator. For example::
|
||||
|
||||
from flask import Flask
|
||||
from werkzeug.routing import Rule
|
||||
|
||||
app = Flask(__name__)
|
||||
app.url_map.add(Rule('/', endpoint='index'))
|
||||
|
||||
@app.endpoint('index')
|
||||
def my_index():
|
||||
return "Hello world"
|
||||
@ -0,0 +1,126 @@
|
||||
Form Validation with WTForms
|
||||
============================
|
||||
|
||||
When you have to work with form data submitted by a browser view, code
|
||||
quickly becomes very hard to read. There are libraries out there designed
|
||||
to make this process easier to manage. One of them is `WTForms`_ which we
|
||||
will handle here. If you find yourself in the situation of having many
|
||||
forms, you might want to give it a try.
|
||||
|
||||
When you are working with WTForms you have to define your forms as classes
|
||||
first. I recommend breaking up the application into multiple modules
|
||||
(:doc:`packages`) for that and adding a separate module for the
|
||||
forms.
|
||||
|
||||
.. admonition:: Getting the most out of WTForms with an Extension
|
||||
|
||||
The `Flask-WTF`_ extension expands on this pattern and adds a
|
||||
few little helpers that make working with forms and Flask more
|
||||
fun. You can get it from `PyPI
|
||||
<https://pypi.org/project/Flask-WTF/>`_.
|
||||
|
||||
.. _Flask-WTF: https://flask-wtf.readthedocs.io/
|
||||
|
||||
The Forms
|
||||
---------
|
||||
|
||||
This is an example form for a typical registration page::
|
||||
|
||||
from wtforms import Form, BooleanField, StringField, PasswordField, validators
|
||||
|
||||
class RegistrationForm(Form):
|
||||
username = StringField('Username', [validators.Length(min=4, max=25)])
|
||||
email = StringField('Email Address', [validators.Length(min=6, max=35)])
|
||||
password = PasswordField('New Password', [
|
||||
validators.DataRequired(),
|
||||
validators.EqualTo('confirm', message='Passwords must match')
|
||||
])
|
||||
confirm = PasswordField('Repeat Password')
|
||||
accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])
|
||||
|
||||
In the View
|
||||
-----------
|
||||
|
||||
In the view function, the usage of this form looks like this::
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
form = RegistrationForm(request.form)
|
||||
if request.method == 'POST' and form.validate():
|
||||
user = User(form.username.data, form.email.data,
|
||||
form.password.data)
|
||||
db_session.add(user)
|
||||
flash('Thanks for registering')
|
||||
return redirect(url_for('login'))
|
||||
return render_template('register.html', form=form)
|
||||
|
||||
Notice we're implying that the view is using SQLAlchemy here
|
||||
(:doc:`sqlalchemy`), but that's not a requirement, of course. Adapt
|
||||
the code as necessary.
|
||||
|
||||
Things to remember:
|
||||
|
||||
1. create the form from the request :attr:`~flask.request.form` value if
|
||||
the data is submitted via the HTTP ``POST`` method and
|
||||
:attr:`~flask.request.args` if the data is submitted as ``GET``.
|
||||
2. to validate the data, call the :func:`~wtforms.form.Form.validate`
|
||||
method, which will return ``True`` if the data validates, ``False``
|
||||
otherwise.
|
||||
3. to access individual values from the form, access `form.<NAME>.data`.
|
||||
|
||||
Forms in Templates
|
||||
------------------
|
||||
|
||||
Now to the template side. When you pass the form to the templates, you can
|
||||
easily render them there. Look at the following example template to see
|
||||
how easy this is. WTForms does half the form generation for us already.
|
||||
To make it even nicer, we can write a macro that renders a field with
|
||||
label and a list of errors if there are any.
|
||||
|
||||
Here's an example :file:`_formhelpers.html` template with such a macro:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% macro render_field(field) %}
|
||||
<dt>{{ field.label }}
|
||||
<dd>{{ field(**kwargs)|safe }}
|
||||
{% if field.errors %}
|
||||
<ul class=errors>
|
||||
{% for error in field.errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endmacro %}
|
||||
|
||||
This macro accepts a couple of keyword arguments that are forwarded to
|
||||
WTForm's field function, which renders the field for us. The keyword
|
||||
arguments will be inserted as HTML attributes. So, for example, you can
|
||||
call ``render_field(form.username, class='username')`` to add a class to
|
||||
the input element. Note that WTForms returns standard Python strings,
|
||||
so we have to tell Jinja that this data is already HTML-escaped with
|
||||
the ``|safe`` filter.
|
||||
|
||||
Here is the :file:`register.html` template for the function we used above, which
|
||||
takes advantage of the :file:`_formhelpers.html` template:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% from "_formhelpers.html" import render_field %}
|
||||
<form method=post>
|
||||
<dl>
|
||||
{{ render_field(form.username) }}
|
||||
{{ render_field(form.email) }}
|
||||
{{ render_field(form.password) }}
|
||||
{{ render_field(form.confirm) }}
|
||||
{{ render_field(form.accept_tos) }}
|
||||
</dl>
|
||||
<p><input type=submit value=Register>
|
||||
</form>
|
||||
|
||||
For more information about WTForms, head over to the `WTForms
|
||||
website`_.
|
||||
|
||||
.. _WTForms: https://wtforms.readthedocs.io/
|
||||
.. _WTForms website: https://wtforms.readthedocs.io/
|
||||
@ -0,0 +1,858 @@
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
Eager to get started? This page gives a good introduction to Flask.
|
||||
Follow :doc:`installation` to set up a project and install Flask first.
|
||||
|
||||
|
||||
A Minimal Application
|
||||
---------------------
|
||||
|
||||
A minimal Flask application looks something like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def hello_world():
|
||||
return "<p>Hello, World!</p>"
|
||||
|
||||
So what did that code do?
|
||||
|
||||
1. First we imported the :class:`~flask.Flask` class. An instance of
|
||||
this class will be our WSGI application.
|
||||
2. Next we create an instance of this class. The first argument is the
|
||||
name of the application's module or package. ``__name__`` is a
|
||||
convenient shortcut for this that is appropriate for most cases.
|
||||
This is needed so that Flask knows where to look for resources such
|
||||
as templates and static files.
|
||||
3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask
|
||||
what URL should trigger our function.
|
||||
4. The function returns the message we want to display in the user's
|
||||
browser. The default content type is HTML, so HTML in the string
|
||||
will be rendered by the browser.
|
||||
|
||||
Save it as :file:`hello.py` or something similar. Make sure to not call
|
||||
your application :file:`flask.py` because this would conflict with Flask
|
||||
itself.
|
||||
|
||||
To run the application, use the ``flask`` command or
|
||||
``python -m flask``. You need to tell the Flask where your application
|
||||
is with the ``--app`` option.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app hello run
|
||||
* Serving Flask app 'hello'
|
||||
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
|
||||
|
||||
.. admonition:: Application Discovery Behavior
|
||||
|
||||
As a shortcut, if the file is named ``app.py`` or ``wsgi.py``, you
|
||||
don't have to use ``--app``. See :doc:`/cli` for more details.
|
||||
|
||||
This launches a very simple builtin server, which is good enough for
|
||||
testing but probably not what you want to use in production. For
|
||||
deployment options see :doc:`deploying/index`.
|
||||
|
||||
Now head over to http://127.0.0.1:5000/, and you should see your hello
|
||||
world greeting.
|
||||
|
||||
If another program is already using port 5000, you'll see
|
||||
``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the
|
||||
server tries to start. See :ref:`address-already-in-use` for how to
|
||||
handle that.
|
||||
|
||||
.. _public-server:
|
||||
|
||||
.. admonition:: Externally Visible Server
|
||||
|
||||
If you run the server you will notice that the server is only accessible
|
||||
from your own computer, not from any other in the network. This is the
|
||||
default because in debugging mode a user of the application can execute
|
||||
arbitrary Python code on your computer.
|
||||
|
||||
If you have the debugger disabled or trust the users on your network,
|
||||
you can make the server publicly available simply by adding
|
||||
``--host=0.0.0.0`` to the command line::
|
||||
|
||||
$ flask run --host=0.0.0.0
|
||||
|
||||
This tells your operating system to listen on all public IPs.
|
||||
|
||||
|
||||
Debug Mode
|
||||
----------
|
||||
|
||||
The ``flask run`` command can do more than just start the development
|
||||
server. By enabling debug mode, the server will automatically reload if
|
||||
code changes, and will show an interactive debugger in the browser if an
|
||||
error occurs during a request.
|
||||
|
||||
.. image:: _static/debugger.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: The interactive debugger in action.
|
||||
|
||||
.. warning::
|
||||
|
||||
The debugger allows executing arbitrary Python code from the
|
||||
browser. It is protected by a pin, but still represents a major
|
||||
security risk. Do not run the development server or debugger in a
|
||||
production environment.
|
||||
|
||||
To enable debug mode, use the ``--debug`` option.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app hello run --debug
|
||||
* Serving Flask app 'hello'
|
||||
* Debug mode: on
|
||||
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
|
||||
* Restarting with stat
|
||||
* Debugger is active!
|
||||
* Debugger PIN: nnn-nnn-nnn
|
||||
|
||||
See also:
|
||||
|
||||
- :doc:`/server` and :doc:`/cli` for information about running in debug mode.
|
||||
- :doc:`/debugging` for information about using the built-in debugger
|
||||
and other debuggers.
|
||||
- :doc:`/logging` and :doc:`/errorhandling` to log errors and display
|
||||
nice error pages.
|
||||
|
||||
|
||||
HTML Escaping
|
||||
-------------
|
||||
|
||||
When returning HTML (the default response type in Flask), any
|
||||
user-provided values rendered in the output must be escaped to protect
|
||||
from injection attacks. HTML templates rendered with Jinja, introduced
|
||||
later, will do this automatically.
|
||||
|
||||
:func:`~markupsafe.escape`, shown here, can be used manually. It is
|
||||
omitted in most examples for brevity, but you should always be aware of
|
||||
how you're using untrusted data.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import request
|
||||
from markupsafe import escape
|
||||
|
||||
@app.route("/hello")
|
||||
def hello():
|
||||
name = request.args.get("name", "Flask")
|
||||
return f"Hello, {escape(name)}!"
|
||||
|
||||
If a user submits ``/hello?name=<script>alert("bad")</script>``, escaping causes
|
||||
it to be rendered as text, rather than running the script in the user's browser.
|
||||
|
||||
|
||||
Routing
|
||||
-------
|
||||
|
||||
Modern web applications use meaningful URLs to help users. Users are more
|
||||
likely to like a page and come back if the page uses a meaningful URL they can
|
||||
remember and use to directly visit a page.
|
||||
|
||||
Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. ::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Index Page'
|
||||
|
||||
@app.route('/hello')
|
||||
def hello():
|
||||
return 'Hello, World'
|
||||
|
||||
You can do more! You can make parts of the URL dynamic and attach multiple
|
||||
rules to a function.
|
||||
|
||||
Variable Rules
|
||||
``````````````
|
||||
|
||||
You can add variable sections to a URL by marking sections with
|
||||
``<variable_name>``. Your function then receives the ``<variable_name>``
|
||||
as a keyword argument. Optionally, you can use a converter to specify the type
|
||||
of the argument like ``<converter:variable_name>``. ::
|
||||
|
||||
from markupsafe import escape
|
||||
|
||||
@app.route('/user/<username>')
|
||||
def show_user_profile(username):
|
||||
# show the user profile for that user
|
||||
return f'User {escape(username)}'
|
||||
|
||||
@app.route('/post/<int:post_id>')
|
||||
def show_post(post_id):
|
||||
# show the post with the given id, the id is an integer
|
||||
return f'Post {post_id}'
|
||||
|
||||
@app.route('/path/<path:subpath>')
|
||||
def show_subpath(subpath):
|
||||
# show the subpath after /path/
|
||||
return f'Subpath {escape(subpath)}'
|
||||
|
||||
Converter types:
|
||||
|
||||
========== ==========================================
|
||||
``string`` (default) accepts any text without a slash
|
||||
``int`` accepts positive integers
|
||||
``float`` accepts positive floating point values
|
||||
``path`` like ``string`` but also accepts slashes
|
||||
``uuid`` accepts UUID strings
|
||||
========== ==========================================
|
||||
|
||||
|
||||
Unique URLs / Redirection Behavior
|
||||
``````````````````````````````````
|
||||
|
||||
The following two rules differ in their use of a trailing slash. ::
|
||||
|
||||
@app.route('/projects/')
|
||||
def projects():
|
||||
return 'The project page'
|
||||
|
||||
@app.route('/about')
|
||||
def about():
|
||||
return 'The about page'
|
||||
|
||||
The canonical URL for the ``projects`` endpoint has a trailing slash.
|
||||
It's similar to a folder in a file system. If you access the URL without
|
||||
a trailing slash (``/projects``), Flask redirects you to the canonical URL
|
||||
with the trailing slash (``/projects/``).
|
||||
|
||||
The canonical URL for the ``about`` endpoint does not have a trailing
|
||||
slash. It's similar to the pathname of a file. Accessing the URL with a
|
||||
trailing slash (``/about/``) produces a 404 "Not Found" error. This helps
|
||||
keep URLs unique for these resources, which helps search engines avoid
|
||||
indexing the same page twice.
|
||||
|
||||
|
||||
.. _url-building:
|
||||
|
||||
URL Building
|
||||
````````````
|
||||
|
||||
To build a URL to a specific function, use the :func:`~flask.url_for` function.
|
||||
It accepts the name of the function as its first argument and any number of
|
||||
keyword arguments, each corresponding to a variable part of the URL rule.
|
||||
Unknown variable parts are appended to the URL as query parameters.
|
||||
|
||||
Why would you want to build URLs using the URL reversing function
|
||||
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
||||
|
||||
1. Reversing is often more descriptive than hard-coding the URLs.
|
||||
2. You can change your URLs in one go instead of needing to remember to
|
||||
manually change hard-coded URLs.
|
||||
3. URL building handles escaping of special characters transparently.
|
||||
4. The generated paths are always absolute, avoiding unexpected behavior
|
||||
of relative paths in browsers.
|
||||
5. If your application is placed outside the URL root, for example, in
|
||||
``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly
|
||||
handles that for you.
|
||||
|
||||
For example, here we use the :meth:`~flask.Flask.test_request_context` method
|
||||
to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context`
|
||||
tells Flask to behave as though it's handling a request even while we use a
|
||||
Python shell. See :doc:`/appcontext`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import url_for
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'index'
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
return 'login'
|
||||
|
||||
@app.route('/user/<username>')
|
||||
def profile(username):
|
||||
return f'{username}\'s profile'
|
||||
|
||||
with app.test_request_context():
|
||||
print(url_for('index'))
|
||||
print(url_for('login'))
|
||||
print(url_for('login', next='/'))
|
||||
print(url_for('profile', username='John Doe'))
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
/
|
||||
/login
|
||||
/login?next=/
|
||||
/user/John%20Doe
|
||||
|
||||
|
||||
HTTP Methods
|
||||
````````````
|
||||
|
||||
Web applications use different HTTP methods when accessing URLs. You should
|
||||
familiarize yourself with the HTTP methods as you work with Flask. By default,
|
||||
a route only answers to ``GET`` requests. You can use the ``methods`` argument
|
||||
of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
|
||||
::
|
||||
|
||||
from flask import request
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
return do_the_login()
|
||||
else:
|
||||
return show_the_login_form()
|
||||
|
||||
The example above keeps all methods for the route within one function,
|
||||
which can be useful if each part uses some common data.
|
||||
|
||||
You can also separate views for different methods into different
|
||||
functions. Flask provides a shortcut for decorating such routes with
|
||||
:meth:`~flask.Flask.get`, :meth:`~flask.Flask.post`, etc. for each
|
||||
common HTTP method.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.get('/login')
|
||||
def login_get():
|
||||
return show_the_login_form()
|
||||
|
||||
@app.post('/login')
|
||||
def login_post():
|
||||
return do_the_login()
|
||||
|
||||
If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
|
||||
and handles ``HEAD`` requests according to the `HTTP RFC`_. Likewise,
|
||||
``OPTIONS`` is automatically implemented for you.
|
||||
|
||||
.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt
|
||||
|
||||
Static Files
|
||||
------------
|
||||
|
||||
Dynamic web applications also need static files. That's usually where
|
||||
the CSS and JavaScript files are coming from. Ideally your web server is
|
||||
configured to serve them for you, but during development Flask can do that
|
||||
as well. Just create a folder called :file:`static` in your package or next to
|
||||
your module and it will be available at ``/static`` on the application.
|
||||
|
||||
To generate URLs for static files, use the special ``'static'`` endpoint name::
|
||||
|
||||
url_for('static', filename='style.css')
|
||||
|
||||
The file has to be stored on the filesystem as :file:`static/style.css`.
|
||||
|
||||
Rendering Templates
|
||||
-------------------
|
||||
|
||||
Generating HTML from within Python is not fun, and actually pretty
|
||||
cumbersome because you have to do the HTML escaping on your own to keep
|
||||
the application secure. Because of that Flask configures the `Jinja
|
||||
<https://palletsprojects.com/p/jinja/>`_ template engine for you automatically.
|
||||
|
||||
Templates can be used to generate any type of text file. For web applications, you'll
|
||||
primarily be generating HTML pages, but you can also generate markdown, plain text for
|
||||
emails, and anything else.
|
||||
|
||||
For a reference to HTML, CSS, and other web APIs, use the `MDN Web Docs`_.
|
||||
|
||||
.. _MDN Web Docs: https://developer.mozilla.org/
|
||||
|
||||
To render a template you can use the :func:`~flask.render_template`
|
||||
method. All you have to do is provide the name of the template and the
|
||||
variables you want to pass to the template engine as keyword arguments.
|
||||
Here's a simple example of how to render a template::
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.route('/hello/')
|
||||
@app.route('/hello/<name>')
|
||||
def hello(name=None):
|
||||
return render_template('hello.html', person=name)
|
||||
|
||||
Flask will look for templates in the :file:`templates` folder. So if your
|
||||
application is a module, this folder is next to that module, if it's a
|
||||
package it's actually inside your package:
|
||||
|
||||
**Case 1**: a module::
|
||||
|
||||
/application.py
|
||||
/templates
|
||||
/hello.html
|
||||
|
||||
**Case 2**: a package::
|
||||
|
||||
/application
|
||||
/__init__.py
|
||||
/templates
|
||||
/hello.html
|
||||
|
||||
For templates you can use the full power of Jinja templates. Head over
|
||||
to the official `Jinja Template Documentation
|
||||
<https://jinja.palletsprojects.com/templates/>`_ for more information.
|
||||
|
||||
Here is an example template:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!doctype html>
|
||||
<title>Hello from Flask</title>
|
||||
{% if person %}
|
||||
<h1>Hello {{ person }}!</h1>
|
||||
{% else %}
|
||||
<h1>Hello, World!</h1>
|
||||
{% endif %}
|
||||
|
||||
Inside templates you also have access to the :data:`~flask.Flask.config`,
|
||||
:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [#]_ objects
|
||||
as well as the :func:`~flask.url_for` and :func:`~flask.get_flashed_messages` functions.
|
||||
|
||||
Templates are especially useful if inheritance is used. If you want to
|
||||
know how that works, see :doc:`patterns/templateinheritance`. Basically
|
||||
template inheritance makes it possible to keep certain elements on each
|
||||
page (like header, navigation and footer).
|
||||
|
||||
Automatic escaping is enabled, so if ``person`` contains HTML it will be escaped
|
||||
automatically. If you can trust a variable and you know that it will be
|
||||
safe HTML (for example because it came from a module that converts wiki
|
||||
markup to HTML) you can mark it as safe by using the
|
||||
:class:`~markupsafe.Markup` class or by using the ``|safe`` filter in the
|
||||
template. Head over to the Jinja 2 documentation for more examples.
|
||||
|
||||
Here is a basic introduction to how the :class:`~markupsafe.Markup` class works::
|
||||
|
||||
>>> from markupsafe import Markup
|
||||
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
||||
Markup('<strong>Hello <blink>hacker</blink>!</strong>')
|
||||
>>> Markup.escape('<blink>hacker</blink>')
|
||||
Markup('<blink>hacker</blink>')
|
||||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||
'Marked up » HTML'
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
|
||||
Autoescaping is no longer enabled for all templates. The following
|
||||
extensions for templates trigger autoescaping: ``.html``, ``.htm``,
|
||||
``.xml``, ``.xhtml``. Templates loaded from a string will have
|
||||
autoescaping disabled.
|
||||
|
||||
.. [#] Unsure what that :class:`~flask.g` object is? It's something in which
|
||||
you can store information for your own needs. See the documentation
|
||||
for :class:`flask.g` and :doc:`patterns/sqlite3`.
|
||||
|
||||
|
||||
Accessing Request Data
|
||||
----------------------
|
||||
|
||||
For web applications it's crucial to react to the data a client sends to the
|
||||
server. In Flask this information is provided by the global :data:`.request`
|
||||
object, which is an instance of :class:`.Request`. This object has many
|
||||
attributes and methods to work with the incoming request data, but here is a
|
||||
broad overview. First it needs to be imported.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import request
|
||||
|
||||
If you have some experience with Python you might be wondering how that object
|
||||
can be global when Flask handles multiple requests at a time. The answer is
|
||||
that :data:`.request` is actually a proxy, pointing at whatever request is
|
||||
currently being handled by a given worker, which is managed interanlly by Flask
|
||||
and Python. See :doc:`/appcontext` for much more information.
|
||||
|
||||
The current request method is available in the :attr:`~.Request.method`
|
||||
attribute. To access form data (data transmitted in a ``POST`` or ``PUT``
|
||||
request), use the :attr:`~flask.Request.form` attribute, which behaves like a
|
||||
dict.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
error = None
|
||||
|
||||
if request.method == "POST":
|
||||
if valid_login(request.form["username"], request.form["password"]):
|
||||
return store_login(request.form["username"])
|
||||
else:
|
||||
error = "Invalid username or password"
|
||||
|
||||
# Executed if the request method was GET or the credentials were invalid.
|
||||
return render_template("login.html", error=error)
|
||||
|
||||
If the key does not exist in ``form``, a special :exc:`KeyError` is raised. You
|
||||
can catch it like a normal ``KeyError``, otherwise it will return a HTTP 400
|
||||
Bad Request error page. You can also use the
|
||||
:meth:`~werkzeug.datastructures.MultiDict.get` method to get a default
|
||||
instead of an error.
|
||||
|
||||
To access parameters submitted in the URL (``?key=value``), use the
|
||||
:attr:`~.Request.args` attribute. Key errors behave the same as ``form``,
|
||||
returning a 400 response if not caught.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
searchword = request.args.get('key', '')
|
||||
|
||||
For a full list of methods and attributes of the request object, see the
|
||||
:class:`~.Request` documentation.
|
||||
|
||||
|
||||
File Uploads
|
||||
````````````
|
||||
|
||||
You can handle uploaded files with Flask easily. Just make sure not to
|
||||
forget to set the ``enctype="multipart/form-data"`` attribute on your HTML
|
||||
form, otherwise the browser will not transmit your files at all.
|
||||
|
||||
Uploaded files are stored in memory or at a temporary location on the
|
||||
filesystem. You can access those files by looking at the
|
||||
:attr:`~flask.request.files` attribute on the request object. Each
|
||||
uploaded file is stored in that dictionary. It behaves just like a
|
||||
standard Python :class:`file` object, but it also has a
|
||||
:meth:`~werkzeug.datastructures.FileStorage.save` method that
|
||||
allows you to store that file on the filesystem of the server.
|
||||
Here is a simple example showing how that works::
|
||||
|
||||
from flask import request
|
||||
|
||||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
f = request.files['the_file']
|
||||
f.save('/var/www/uploads/uploaded_file.txt')
|
||||
...
|
||||
|
||||
If you want to know how the file was named on the client before it was
|
||||
uploaded to your application, you can access the
|
||||
:attr:`~werkzeug.datastructures.FileStorage.filename` attribute.
|
||||
However please keep in mind that this value can be forged
|
||||
so never ever trust that value. If you want to use the filename
|
||||
of the client to store the file on the server, pass it through the
|
||||
:func:`~werkzeug.utils.secure_filename` function that
|
||||
Werkzeug provides for you::
|
||||
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
file = request.files['the_file']
|
||||
file.save(f"/var/www/uploads/{secure_filename(file.filename)}")
|
||||
...
|
||||
|
||||
For some better examples, see :doc:`patterns/fileuploads`.
|
||||
|
||||
Cookies
|
||||
```````
|
||||
|
||||
To access cookies you can use the :attr:`~flask.Request.cookies`
|
||||
attribute. To set cookies you can use the
|
||||
:attr:`~flask.Response.set_cookie` method of response objects. The
|
||||
:attr:`~flask.Request.cookies` attribute of request objects is a
|
||||
dictionary with all the cookies the client transmits. If you want to use
|
||||
sessions, do not use the cookies directly but instead use the
|
||||
:ref:`sessions` in Flask that add some security on top of cookies for you.
|
||||
|
||||
Reading cookies::
|
||||
|
||||
from flask import request
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
username = request.cookies.get('username')
|
||||
# use cookies.get(key) instead of cookies[key] to not get a
|
||||
# KeyError if the cookie is missing.
|
||||
|
||||
Storing cookies::
|
||||
|
||||
from flask import make_response
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
resp = make_response(render_template(...))
|
||||
resp.set_cookie('username', 'the username')
|
||||
return resp
|
||||
|
||||
Note that cookies are set on response objects. Since you normally
|
||||
just return strings from the view functions Flask will convert them into
|
||||
response objects for you. If you explicitly want to do that you can use
|
||||
the :meth:`~flask.make_response` function and then modify it.
|
||||
|
||||
Sometimes you might want to set a cookie at a point where the response
|
||||
object does not exist yet. This is possible by utilizing the
|
||||
:doc:`patterns/deferredcallbacks` pattern.
|
||||
|
||||
For this also see :ref:`about-responses`.
|
||||
|
||||
Redirects and Errors
|
||||
--------------------
|
||||
|
||||
To redirect a user to another endpoint, use the :func:`~flask.redirect`
|
||||
function; to abort a request early with an error code, use the
|
||||
:func:`~flask.abort` function::
|
||||
|
||||
from flask import abort, redirect, url_for
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
abort(401)
|
||||
this_is_never_executed()
|
||||
|
||||
This is a rather pointless example because a user will be redirected from
|
||||
the index to a page they cannot access (401 means access denied) but it
|
||||
shows how that works.
|
||||
|
||||
By default a black and white error page is shown for each error code. If
|
||||
you want to customize the error page, you can use the
|
||||
:meth:`~flask.Flask.errorhandler` decorator::
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
return render_template('page_not_found.html'), 404
|
||||
|
||||
Note the ``404`` after the :func:`~flask.render_template` call. This
|
||||
tells Flask that the status code of that page should be 404 which means
|
||||
not found. By default 200 is assumed which translates to: all went well.
|
||||
|
||||
See :doc:`errorhandling` for more details.
|
||||
|
||||
.. _about-responses:
|
||||
|
||||
About Responses
|
||||
---------------
|
||||
|
||||
The return value from a view function is automatically converted into
|
||||
a response object for you. If the return value is a string it's
|
||||
converted into a response object with the string as response body, a
|
||||
``200 OK`` status code and a :mimetype:`text/html` mimetype. If the
|
||||
return value is a dict or list, :func:`jsonify` is called to produce a
|
||||
response. The logic that Flask applies to converting return values into
|
||||
response objects is as follows:
|
||||
|
||||
1. If a response object of the correct type is returned it's directly
|
||||
returned from the view.
|
||||
2. If it's a string, a response object is created with that data and
|
||||
the default parameters.
|
||||
3. If it's an iterator or generator returning strings or bytes, it is
|
||||
treated as a streaming response.
|
||||
4. If it's a dict or list, a response object is created using
|
||||
:func:`~flask.json.jsonify`.
|
||||
5. If a tuple is returned the items in the tuple can provide extra
|
||||
information. Such tuples have to be in the form
|
||||
``(response, status)``, ``(response, headers)``, or
|
||||
``(response, status, headers)``. The ``status`` value will override
|
||||
the status code and ``headers`` can be a list or dictionary of
|
||||
additional header values.
|
||||
6. If none of that works, Flask will assume the return value is a
|
||||
valid WSGI application and convert that into a response object.
|
||||
|
||||
If you want to get hold of the resulting response object inside the view
|
||||
you can use the :func:`~flask.make_response` function.
|
||||
|
||||
Imagine you have a view like this::
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
return render_template('error.html'), 404
|
||||
|
||||
You just need to wrap the return expression with
|
||||
:func:`~flask.make_response` and get the response object to modify it, then
|
||||
return it::
|
||||
|
||||
from flask import make_response
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
resp = make_response(render_template('error.html'), 404)
|
||||
resp.headers['X-Something'] = 'A value'
|
||||
return resp
|
||||
|
||||
|
||||
APIs with JSON
|
||||
``````````````
|
||||
|
||||
A common response format when writing an API is JSON. It's easy to get
|
||||
started writing such an API with Flask. If you return a ``dict`` or
|
||||
``list`` from a view, it will be converted to a JSON response.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route("/me")
|
||||
def me_api():
|
||||
user = get_current_user()
|
||||
return {
|
||||
"username": user.username,
|
||||
"theme": user.theme,
|
||||
"image": url_for("user_image", filename=user.image),
|
||||
}
|
||||
|
||||
@app.route("/users")
|
||||
def users_api():
|
||||
users = get_all_users()
|
||||
return [user.to_json() for user in users]
|
||||
|
||||
This is a shortcut to passing the data to the
|
||||
:func:`~flask.json.jsonify` function, which will serialize any supported
|
||||
JSON data type. That means that all the data in the dict or list must be
|
||||
JSON serializable.
|
||||
|
||||
For complex types such as database models, you'll want to use a
|
||||
serialization library to convert the data to valid JSON types first.
|
||||
There are many serialization libraries and Flask API extensions
|
||||
maintained by the community that support more complex applications.
|
||||
|
||||
|
||||
.. _sessions:
|
||||
|
||||
Sessions
|
||||
--------
|
||||
|
||||
In addition to the request object there is also a second object called
|
||||
:class:`~flask.session` which allows you to store information specific to a
|
||||
user from one request to the next. This is implemented on top of cookies
|
||||
for you and signs the cookies cryptographically. What this means is that
|
||||
the user could look at the contents of your cookie but not modify it,
|
||||
unless they know the secret key used for signing.
|
||||
|
||||
In order to use sessions you have to set a secret key. Here is how
|
||||
sessions work::
|
||||
|
||||
from flask import session
|
||||
|
||||
# Set the secret key to some random bytes. Keep this really secret!
|
||||
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if 'username' in session:
|
||||
return f'Logged in as {session["username"]}'
|
||||
return 'You are not logged in'
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
session['username'] = request.form['username']
|
||||
return redirect(url_for('index'))
|
||||
return '''
|
||||
<form method="post">
|
||||
<p><input type=text name=username>
|
||||
<p><input type=submit value=Login>
|
||||
</form>
|
||||
'''
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
# remove the username from the session if it's there
|
||||
session.pop('username', None)
|
||||
return redirect(url_for('index'))
|
||||
|
||||
.. admonition:: How to generate good secret keys
|
||||
|
||||
A secret key should be as random as possible. Your operating system has
|
||||
ways to generate pretty random data based on a cryptographic random
|
||||
generator. Use the following command to quickly generate a value for
|
||||
:attr:`Flask.secret_key` (or :data:`SECRET_KEY`)::
|
||||
|
||||
$ python -c 'import secrets; print(secrets.token_hex())'
|
||||
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
|
||||
A note on cookie-based sessions: Flask will take the values you put into the
|
||||
session object and serialize them into a cookie. If you are finding some
|
||||
values do not persist across requests, cookies are indeed enabled, and you are
|
||||
not getting a clear error message, check the size of the cookie in your page
|
||||
responses compared to the size supported by web browsers.
|
||||
|
||||
Besides the default client-side based sessions, if you want to handle
|
||||
sessions on the server-side instead, there are several
|
||||
Flask extensions that support this.
|
||||
|
||||
Message Flashing
|
||||
----------------
|
||||
|
||||
Good applications and user interfaces are all about feedback. If the user
|
||||
does not get enough feedback they will probably end up hating the
|
||||
application. Flask provides a really simple way to give feedback to a
|
||||
user with the flashing system. The flashing system basically makes it
|
||||
possible to record a message at the end of a request and access it on the next
|
||||
(and only the next) request. This is usually combined with a layout
|
||||
template to expose the message.
|
||||
|
||||
To flash a message use the :func:`~flask.flash` method, to get hold of the
|
||||
messages you can use :func:`~flask.get_flashed_messages` which is also
|
||||
available in the templates. See :doc:`patterns/flashing` for a full
|
||||
example.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
Sometimes you might be in a situation where you deal with data that
|
||||
should be correct, but actually is not. For example you may have
|
||||
some client-side code that sends an HTTP request to the server
|
||||
but it's obviously malformed. This might be caused by a user tampering
|
||||
with the data, or the client code failing. Most of the time it's okay
|
||||
to reply with ``400 Bad Request`` in that situation, but sometimes
|
||||
that won't do and the code has to continue working.
|
||||
|
||||
You may still want to log that something fishy happened. This is where
|
||||
loggers come in handy. As of Flask 0.3 a logger is preconfigured for you
|
||||
to use.
|
||||
|
||||
Here are some example log calls::
|
||||
|
||||
app.logger.debug('A value for debugging')
|
||||
app.logger.warning('A warning occurred (%d apples)', 42)
|
||||
app.logger.error('An error occurred')
|
||||
|
||||
The attached :attr:`~flask.Flask.logger` is a standard logging
|
||||
:class:`~logging.Logger`, so head over to the official :mod:`logging`
|
||||
docs for more information.
|
||||
|
||||
See :doc:`errorhandling`.
|
||||
|
||||
|
||||
Hooking in WSGI Middleware
|
||||
--------------------------
|
||||
|
||||
To add WSGI middleware to your Flask application, wrap the application's
|
||||
``wsgi_app`` attribute. For example, to apply Werkzeug's
|
||||
:class:`~werkzeug.middleware.proxy_fix.ProxyFix` middleware for running
|
||||
behind Nginx:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||
|
||||
Wrapping ``app.wsgi_app`` instead of ``app`` means that ``app`` still
|
||||
points at your Flask application, not at the middleware, so you can
|
||||
continue to use and configure ``app`` directly.
|
||||
|
||||
Using Flask Extensions
|
||||
----------------------
|
||||
|
||||
Extensions are packages that help you accomplish common tasks. For
|
||||
example, Flask-SQLAlchemy provides SQLAlchemy support that makes it simple
|
||||
and easy to use with Flask.
|
||||
|
||||
For more on Flask extensions, see :doc:`extensions`.
|
||||
|
||||
Deploying to a Web Server
|
||||
-------------------------
|
||||
|
||||
Ready to deploy your new Flask app? See :doc:`deploying/index`.
|
||||
@ -0,0 +1,6 @@
|
||||
:orphan:
|
||||
|
||||
The Request Context
|
||||
===================
|
||||
|
||||
Obsolete, see :doc:`/appcontext` instead.
|
||||
@ -0,0 +1,115 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Development Server
|
||||
==================
|
||||
|
||||
Flask provides a ``run`` command to run the application with a development server. In
|
||||
debug mode, this server provides an interactive debugger and will reload when code is
|
||||
changed.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use the development server when deploying to production. It
|
||||
is intended for use only during local development. It is not
|
||||
designed to be particularly efficient, stable, or secure.
|
||||
|
||||
See :doc:`/deploying/index` for deployment options.
|
||||
|
||||
Command Line
|
||||
------------
|
||||
|
||||
The ``flask run`` CLI command is the recommended way to run the development server. Use
|
||||
the ``--app`` option to point to your application, and the ``--debug`` option to enable
|
||||
debug mode.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app hello run --debug
|
||||
|
||||
This enables debug mode, including the interactive debugger and reloader, and then
|
||||
starts the server on http://localhost:5000/. Use ``flask run --help`` to see the
|
||||
available options, and :doc:`/cli` for detailed instructions about configuring and using
|
||||
the CLI.
|
||||
|
||||
|
||||
.. _address-already-in-use:
|
||||
|
||||
Address already in use
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If another program is already using port 5000, you'll see an ``OSError``
|
||||
when the server tries to start. It may have one of the following
|
||||
messages:
|
||||
|
||||
- ``OSError: [Errno 98] Address already in use``
|
||||
- ``OSError: [WinError 10013] An attempt was made to access a socket
|
||||
in a way forbidden by its access permissions``
|
||||
|
||||
Either identify and stop the other program, or use
|
||||
``flask run --port 5001`` to pick a different port.
|
||||
|
||||
You can use ``netstat`` or ``lsof`` to identify what process id is using
|
||||
a port, then use other operating system tools stop that process. The
|
||||
following example shows that process id 6847 is using port 5000.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: ``netstat`` (Linux)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ netstat -nlp | grep 5000
|
||||
tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 6847/python
|
||||
|
||||
.. tab:: ``lsof`` (macOS / Linux)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ lsof -P -i :5000
|
||||
Python 6847 IPv4 TCP localhost:5000 (LISTEN)
|
||||
|
||||
.. tab:: ``netstat`` (Windows)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> netstat -ano | findstr 5000
|
||||
TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING 6847
|
||||
|
||||
macOS Monterey and later automatically starts a service that uses port
|
||||
5000. You can choose to disable this service instead of using a different port by
|
||||
searching for "AirPlay Receiver" in System Settings and toggling it off.
|
||||
|
||||
|
||||
Deferred Errors on Reload
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When using the ``flask run`` command with the reloader, the server will
|
||||
continue to run even if you introduce syntax errors or other
|
||||
initialization errors into the code. Accessing the site will show the
|
||||
interactive debugger for the error, rather than crashing the server.
|
||||
|
||||
If a syntax error is already present when calling ``flask run``, it will
|
||||
fail immediately and show the traceback rather than waiting until the
|
||||
site is accessed. This is intended to make errors more visible initially
|
||||
while still allowing the server to handle errors on reload.
|
||||
|
||||
|
||||
In Code
|
||||
-------
|
||||
|
||||
The development server can also be started from Python with the :meth:`Flask.run`
|
||||
method. This method takes arguments similar to the CLI options to control the server.
|
||||
The main difference from the CLI command is that the server will crash if there are
|
||||
errors when reloading. ``debug=True`` can be passed to enable debug mode.
|
||||
|
||||
Place the call in a main block, otherwise it will interfere when trying to import and
|
||||
run the application with a production server later.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python hello.py
|
||||
@ -0,0 +1,81 @@
|
||||
Working with the Shell
|
||||
======================
|
||||
|
||||
One of the reasons everybody loves Python is the interactive shell. It allows
|
||||
you to play around with code in real time and immediately get results back.
|
||||
Flask provides the ``flask shell`` CLI command to start an interactive Python
|
||||
shell with some setup done to make working with the Flask app easier.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask shell
|
||||
|
||||
Creating a Request Context
|
||||
--------------------------
|
||||
|
||||
``flask shell`` pushes an app context automatically, so :data:`.current_app` and
|
||||
:data:`.g` are already available. However, there is no HTTP request being
|
||||
handled in the shell, so :data:`.request` and :data:`.session` are not yet
|
||||
available.
|
||||
|
||||
The easiest way to create a proper request context from the shell is by
|
||||
using the :attr:`~flask.Flask.test_request_context` method which creates
|
||||
us a :class:`~flask.ctx.RequestContext`:
|
||||
|
||||
>>> ctx = app.test_request_context()
|
||||
|
||||
Normally you would use the ``with`` statement to make this context active, but
|
||||
in the shell it's easier to call :meth:`~.RequestContext.push` and
|
||||
:meth:`~.RequestContext.pop` manually:
|
||||
|
||||
>>> ctx.push()
|
||||
|
||||
From that point onwards you can work with the request object until you call
|
||||
``pop``:
|
||||
|
||||
>>> ctx.pop()
|
||||
|
||||
Firing Before/After Request
|
||||
---------------------------
|
||||
|
||||
By just creating a request context, you still don't have run the code that
|
||||
is normally run before a request. This might result in your database
|
||||
being unavailable if you are connecting to the database in a
|
||||
before-request callback or the current user not being stored on the
|
||||
:data:`~flask.g` object etc.
|
||||
|
||||
This however can easily be done yourself. Just call
|
||||
:meth:`~flask.Flask.preprocess_request`:
|
||||
|
||||
>>> ctx = app.test_request_context()
|
||||
>>> ctx.push()
|
||||
>>> app.preprocess_request()
|
||||
|
||||
Keep in mind that the :meth:`~flask.Flask.preprocess_request` function
|
||||
might return a response object, in that case just ignore it.
|
||||
|
||||
To shutdown a request, you need to trick a bit before the after request
|
||||
functions (triggered by :meth:`~flask.Flask.process_response`) operate on
|
||||
a response object:
|
||||
|
||||
>>> app.process_response(app.response_class())
|
||||
<Response 0 bytes [200 OK]>
|
||||
>>> ctx.pop()
|
||||
|
||||
The functions registered as :meth:`~flask.Flask.teardown_request` are
|
||||
automatically called when the context is popped. So this is the perfect
|
||||
place to automatically tear down resources that were needed by the request
|
||||
context (such as database connections).
|
||||
|
||||
|
||||
Further Improving the Shell Experience
|
||||
--------------------------------------
|
||||
|
||||
If you like the idea of experimenting in a shell, create yourself a module
|
||||
with stuff you want to star import into your interactive session. There
|
||||
you could also define some more helper methods for common things such as
|
||||
initializing the database, dropping tables etc.
|
||||
|
||||
Just put them into a module (like `shelltools`) and import from there:
|
||||
|
||||
>>> from shelltools import *
|
||||
@ -0,0 +1,166 @@
|
||||
Signals
|
||||
=======
|
||||
|
||||
Signals are a lightweight way to notify subscribers of certain events during the
|
||||
lifecycle of the application and each request. When an event occurs, it emits the
|
||||
signal, which calls each subscriber.
|
||||
|
||||
Signals are implemented by the `Blinker`_ library. See its documentation for detailed
|
||||
information. Flask provides some built-in signals. Extensions may provide their own.
|
||||
|
||||
Many signals mirror Flask's decorator-based callbacks with similar names. For example,
|
||||
the :data:`.request_started` signal is similar to the :meth:`~.Flask.before_request`
|
||||
decorator. The advantage of signals over handlers is that they can be subscribed to
|
||||
temporarily, and can't directly affect the application. This is useful for testing,
|
||||
metrics, auditing, and more. For example, if you want to know what templates were
|
||||
rendered at what parts of what requests, there is a signal that will notify you of that
|
||||
information.
|
||||
|
||||
|
||||
Core Signals
|
||||
------------
|
||||
|
||||
See :ref:`core-signals-list` for a list of all built-in signals. The :doc:`lifecycle`
|
||||
page also describes the order that signals and decorators execute.
|
||||
|
||||
|
||||
Subscribing to Signals
|
||||
----------------------
|
||||
|
||||
To subscribe to a signal, you can use the
|
||||
:meth:`~blinker.base.Signal.connect` method of a signal. The first
|
||||
argument is the function that should be called when the signal is emitted,
|
||||
the optional second argument specifies a sender. To unsubscribe from a
|
||||
signal, you can use the :meth:`~blinker.base.Signal.disconnect` method.
|
||||
|
||||
For all core Flask signals, the sender is the application that issued the
|
||||
signal. When you subscribe to a signal, be sure to also provide a sender
|
||||
unless you really want to listen for signals from all applications. This is
|
||||
especially true if you are developing an extension.
|
||||
|
||||
For example, here is a helper context manager that can be used in a unit test
|
||||
to determine which templates were rendered and what variables were passed
|
||||
to the template::
|
||||
|
||||
from flask import template_rendered
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def captured_templates(app):
|
||||
recorded = []
|
||||
def record(sender, template, context, **extra):
|
||||
recorded.append((template, context))
|
||||
template_rendered.connect(record, app)
|
||||
try:
|
||||
yield recorded
|
||||
finally:
|
||||
template_rendered.disconnect(record, app)
|
||||
|
||||
This can now easily be paired with a test client::
|
||||
|
||||
with captured_templates(app) as templates:
|
||||
rv = app.test_client().get('/')
|
||||
assert rv.status_code == 200
|
||||
assert len(templates) == 1
|
||||
template, context = templates[0]
|
||||
assert template.name == 'index.html'
|
||||
assert len(context['items']) == 10
|
||||
|
||||
Make sure to subscribe with an extra ``**extra`` argument so that your
|
||||
calls don't fail if Flask introduces new arguments to the signals.
|
||||
|
||||
All the template rendering in the code issued by the application `app`
|
||||
in the body of the ``with`` block will now be recorded in the `templates`
|
||||
variable. Whenever a template is rendered, the template object as well as
|
||||
context are appended to it.
|
||||
|
||||
Additionally there is a convenient helper method
|
||||
(:meth:`~blinker.base.Signal.connected_to`) that allows you to
|
||||
temporarily subscribe a function to a signal with a context manager on
|
||||
its own. Because the return value of the context manager cannot be
|
||||
specified that way, you have to pass the list in as an argument::
|
||||
|
||||
from flask import template_rendered
|
||||
|
||||
def captured_templates(app, recorded, **extra):
|
||||
def record(sender, template, context):
|
||||
recorded.append((template, context))
|
||||
return template_rendered.connected_to(record, app)
|
||||
|
||||
The example above would then look like this::
|
||||
|
||||
templates = []
|
||||
with captured_templates(app, templates, **extra):
|
||||
...
|
||||
template, context = templates[0]
|
||||
|
||||
Creating Signals
|
||||
----------------
|
||||
|
||||
If you want to use signals in your own application, you can use the
|
||||
blinker library directly. The most common use case are named signals in a
|
||||
custom :class:`~blinker.base.Namespace`. This is what is recommended
|
||||
most of the time::
|
||||
|
||||
from blinker import Namespace
|
||||
my_signals = Namespace()
|
||||
|
||||
Now you can create new signals like this::
|
||||
|
||||
model_saved = my_signals.signal('model-saved')
|
||||
|
||||
The name for the signal here makes it unique and also simplifies
|
||||
debugging. You can access the name of the signal with the
|
||||
:attr:`~blinker.base.NamedSignal.name` attribute.
|
||||
|
||||
.. _signals-sending:
|
||||
|
||||
Sending Signals
|
||||
---------------
|
||||
|
||||
If you want to emit a signal, you can do so by calling the
|
||||
:meth:`~blinker.base.Signal.send` method. It accepts a sender as first
|
||||
argument and optionally some keyword arguments that are forwarded to the
|
||||
signal subscribers::
|
||||
|
||||
class Model(object):
|
||||
...
|
||||
|
||||
def save(self):
|
||||
model_saved.send(self)
|
||||
|
||||
Try to always pick a good sender. If you have a class that is emitting a
|
||||
signal, pass ``self`` as sender. If you are emitting a signal from a random
|
||||
function, you can pass ``current_app._get_current_object()`` as sender.
|
||||
|
||||
.. admonition:: Passing Proxies as Senders
|
||||
|
||||
Never pass :data:`~flask.current_app` as sender to a signal. Use
|
||||
``current_app._get_current_object()`` instead. The reason for this is
|
||||
that :data:`~flask.current_app` is a proxy and not the real application
|
||||
object.
|
||||
|
||||
|
||||
Signals and Flask's Request Context
|
||||
-----------------------------------
|
||||
|
||||
Context-local proxies are available between :data:`~flask.request_started` and
|
||||
:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others
|
||||
as needed. Note the limitations described in :ref:`signals-sending` and the
|
||||
:data:`~flask.request_tearing_down` signal.
|
||||
|
||||
|
||||
Decorator Based Signal Subscriptions
|
||||
------------------------------------
|
||||
|
||||
You can also easily subscribe to signals by using the
|
||||
:meth:`~blinker.base.NamedSignal.connect_via` decorator::
|
||||
|
||||
from flask import template_rendered
|
||||
|
||||
@template_rendered.connect_via(app)
|
||||
def when_template_rendered(sender, template, context, **extra):
|
||||
print(f'Template {template.name} is rendered with {context}')
|
||||
|
||||
|
||||
.. _blinker: https://pypi.org/project/blinker/
|
||||
@ -0,0 +1,255 @@
|
||||
Templates
|
||||
=========
|
||||
|
||||
Flask leverages Jinja as its template engine. You are obviously free to use
|
||||
a different template engine, but you still have to install Jinja to run
|
||||
Flask itself. This requirement is necessary to enable rich extensions.
|
||||
An extension can depend on Jinja being present.
|
||||
|
||||
This section only gives a very quick introduction into how Jinja
|
||||
is integrated into Flask. If you want information on the template
|
||||
engine's syntax itself, head over to the official `Jinja Template
|
||||
Documentation <https://jinja.palletsprojects.com/templates/>`_ for
|
||||
more information.
|
||||
|
||||
Jinja Setup
|
||||
-----------
|
||||
|
||||
Unless customized, Jinja is configured by Flask as follows:
|
||||
|
||||
- autoescaping is enabled for all templates ending in ``.html``,
|
||||
``.htm``, ``.xml``, ``.xhtml``, as well as ``.svg`` when using
|
||||
:func:`~flask.templating.render_template`.
|
||||
- autoescaping is enabled for all strings when using
|
||||
:func:`~flask.templating.render_template_string`.
|
||||
- a template has the ability to opt in/out autoescaping with the
|
||||
``{% autoescape %}`` tag.
|
||||
- Flask inserts a couple of global functions and helpers into the
|
||||
Jinja context, additionally to the values that are present by
|
||||
default.
|
||||
|
||||
Standard Context
|
||||
----------------
|
||||
|
||||
The following global variables are available within Jinja templates
|
||||
by default:
|
||||
|
||||
.. data:: config
|
||||
:noindex:
|
||||
|
||||
The current configuration object (:data:`flask.Flask.config`)
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
.. versionchanged:: 0.10
|
||||
This is now always available, even in imported templates.
|
||||
|
||||
.. data:: request
|
||||
:noindex:
|
||||
|
||||
The current request object (:class:`flask.request`). This variable is
|
||||
unavailable if the template was rendered without an active request
|
||||
context.
|
||||
|
||||
.. data:: session
|
||||
:noindex:
|
||||
|
||||
The current session object (:class:`flask.session`). This variable
|
||||
is unavailable if the template was rendered without an active request
|
||||
context.
|
||||
|
||||
.. data:: g
|
||||
:noindex:
|
||||
|
||||
The request-bound object for global variables (:data:`flask.g`). This
|
||||
variable is unavailable if the template was rendered without an active
|
||||
request context.
|
||||
|
||||
.. function:: url_for
|
||||
:noindex:
|
||||
|
||||
The :func:`flask.url_for` function.
|
||||
|
||||
.. function:: get_flashed_messages
|
||||
:noindex:
|
||||
|
||||
The :func:`flask.get_flashed_messages` function.
|
||||
|
||||
.. admonition:: The Jinja Context Behavior
|
||||
|
||||
These variables are added to the context of variables, they are not
|
||||
global variables. The difference is that by default these will not
|
||||
show up in the context of imported templates. This is partially caused
|
||||
by performance considerations, partially to keep things explicit.
|
||||
|
||||
What does this mean for you? If you have a macro you want to import,
|
||||
that needs to access the request object you have two possibilities:
|
||||
|
||||
1. you explicitly pass the request to the macro as parameter, or
|
||||
the attribute of the request object you are interested in.
|
||||
2. you import the macro "with context".
|
||||
|
||||
Importing with context looks like this:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% from '_helpers.html' import my_macro with context %}
|
||||
|
||||
|
||||
Controlling Autoescaping
|
||||
------------------------
|
||||
|
||||
Autoescaping is the concept of automatically escaping special characters
|
||||
for you. Special characters in the sense of HTML (or XML, and thus XHTML)
|
||||
are ``&``, ``>``, ``<``, ``"`` as well as ``'``. Because these characters
|
||||
carry specific meanings in documents on their own you have to replace them
|
||||
by so called "entities" if you want to use them for text. Not doing so
|
||||
would not only cause user frustration by the inability to use these
|
||||
characters in text, but can also lead to security problems. (see
|
||||
:ref:`security-xss`)
|
||||
|
||||
Sometimes however you will need to disable autoescaping in templates.
|
||||
This can be the case if you want to explicitly inject HTML into pages, for
|
||||
example if they come from a system that generates secure HTML like a
|
||||
markdown to HTML converter.
|
||||
|
||||
There are three ways to accomplish that:
|
||||
|
||||
- In the Python code, wrap the HTML string in a :class:`~markupsafe.Markup`
|
||||
object before passing it to the template. This is in general the
|
||||
recommended way.
|
||||
- Inside the template, use the ``|safe`` filter to explicitly mark a
|
||||
string as safe HTML (``{{ myvariable|safe }}``)
|
||||
- Temporarily disable the autoescape system altogether.
|
||||
|
||||
To disable the autoescape system in templates, you can use the ``{%
|
||||
autoescape %}`` block:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% autoescape false %}
|
||||
<p>autoescaping is disabled here
|
||||
<p>{{ will_not_be_escaped }}
|
||||
{% endautoescape %}
|
||||
|
||||
Whenever you do this, please be very cautious about the variables you are
|
||||
using in this block.
|
||||
|
||||
.. _registering-filters:
|
||||
|
||||
Registering Filters, Tests, and Globals
|
||||
---------------------------------------
|
||||
|
||||
The Flask app and blueprints provide decorators and methods to register your own
|
||||
filters, tests, and global functions for use in Jinja templates. They all follow
|
||||
the same pattern, so the following examples only discuss filters.
|
||||
|
||||
Decorate a function with :meth:`~.Flask.template_filter` to register it as a
|
||||
template filter.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.template_filter
|
||||
def reverse(s):
|
||||
return reversed(s)
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% for item in data | reverse %}
|
||||
{% endfor %}
|
||||
|
||||
By default it will use the name of the function as the name of the filter, but
|
||||
that can be changed by passing a name to the decorator.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.template_filter("reverse")
|
||||
def reverse_filter(s):
|
||||
return reversed(s)
|
||||
|
||||
A filter can be registered separately using :meth:`~.Flask.add_template_filter`.
|
||||
The name is optional and will use the function name if not given.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def reverse_filter(s):
|
||||
return reversed(s)
|
||||
|
||||
app.add_template_filter(reverse_filter, "reverse")
|
||||
|
||||
For template tests, use the :meth:`~.Flask.template_test` decorator or
|
||||
:meth:`~.Flask.add_template_test` method. For template global functions, use the
|
||||
:meth:`~.Flask.template_global` decorator or :meth:`~.Flask.add_template_global`
|
||||
method.
|
||||
|
||||
The same methods also exist on :class:`.Blueprint`, prefixed with ``app_`` to
|
||||
indicate that the registered functions will be avaialble to all templates, not
|
||||
only when rendering from within the blueprint.
|
||||
|
||||
The Jinja environment is also available as :attr:`~.Flask.jinja_env`. It may be
|
||||
modified directly, as you would when using Jinja outside Flask.
|
||||
|
||||
|
||||
Context Processors
|
||||
------------------
|
||||
|
||||
To inject new variables automatically into the context of a template,
|
||||
context processors exist in Flask. Context processors run before the
|
||||
template is rendered and have the ability to inject new values into the
|
||||
template context. A context processor is a function that returns a
|
||||
dictionary. The keys and values of this dictionary are then merged with
|
||||
the template context, for all templates in the app::
|
||||
|
||||
@app.context_processor
|
||||
def inject_user():
|
||||
return dict(user=g.user)
|
||||
|
||||
The context processor above makes a variable called `user` available in
|
||||
the template with the value of `g.user`. This example is not very
|
||||
interesting because `g` is available in templates anyways, but it gives an
|
||||
idea how this works.
|
||||
|
||||
Variables are not limited to values; a context processor can also make
|
||||
functions available to templates (since Python allows passing around
|
||||
functions)::
|
||||
|
||||
@app.context_processor
|
||||
def utility_processor():
|
||||
def format_price(amount, currency="€"):
|
||||
return f"{amount:.2f}{currency}"
|
||||
return dict(format_price=format_price)
|
||||
|
||||
The context processor above makes the `format_price` function available to all
|
||||
templates::
|
||||
|
||||
{{ format_price(0.33) }}
|
||||
|
||||
You could also build `format_price` as a template filter (see
|
||||
:ref:`registering-filters`), but this demonstrates how to pass functions in a
|
||||
context processor.
|
||||
|
||||
Streaming
|
||||
---------
|
||||
|
||||
It can be useful to not render the whole template as one complete
|
||||
string, instead render it as a stream, yielding smaller incremental
|
||||
strings. This can be used for streaming HTML in chunks to speed up
|
||||
initial page load, or to save memory when rendering a very large
|
||||
template.
|
||||
|
||||
The Jinja template engine supports rendering a template piece
|
||||
by piece, returning an iterator of strings. Flask provides the
|
||||
:func:`~flask.stream_template` and :func:`~flask.stream_template_string`
|
||||
functions to make this easier to use.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import stream_template
|
||||
|
||||
@app.get("/timeline")
|
||||
def timeline():
|
||||
return stream_template("timeline.html")
|
||||
|
||||
These functions automatically apply the
|
||||
:func:`~flask.stream_with_context` wrapper if a request is active, so
|
||||
that it remains available in the template.
|
||||
@ -0,0 +1,318 @@
|
||||
Testing Flask Applications
|
||||
==========================
|
||||
|
||||
Flask provides utilities for testing an application. This documentation
|
||||
goes over techniques for working with different parts of the application
|
||||
in tests.
|
||||
|
||||
We will use the `pytest`_ framework to set up and run our tests.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install pytest
|
||||
|
||||
.. _pytest: https://docs.pytest.org/
|
||||
|
||||
The :doc:`tutorial </tutorial/index>` goes over how to write tests for
|
||||
100% coverage of the sample Flaskr blog application. See
|
||||
:doc:`the tutorial on tests </tutorial/tests>` for a detailed
|
||||
explanation of specific tests for an application.
|
||||
|
||||
|
||||
Identifying Tests
|
||||
-----------------
|
||||
|
||||
Tests are typically located in the ``tests`` folder. Tests are functions
|
||||
that start with ``test_``, in Python modules that start with ``test_``.
|
||||
Tests can also be further grouped in classes that start with ``Test``.
|
||||
|
||||
It can be difficult to know what to test. Generally, try to test the
|
||||
code that you write, not the code of libraries that you use, since they
|
||||
are already tested. Try to extract complex behaviors as separate
|
||||
functions to test individually.
|
||||
|
||||
|
||||
Fixtures
|
||||
--------
|
||||
|
||||
Pytest *fixtures* allow writing pieces of code that are reusable across
|
||||
tests. A simple fixture returns a value, but a fixture can also do
|
||||
setup, yield a value, then do teardown. Fixtures for the application,
|
||||
test client, and CLI runner are shown below, they can be placed in
|
||||
``tests/conftest.py``.
|
||||
|
||||
If you're using an
|
||||
:doc:`application factory </patterns/appfactories>`, define an ``app``
|
||||
fixture to create and configure an app instance. You can add code before
|
||||
and after the ``yield`` to set up and tear down other resources, such as
|
||||
creating and clearing a database.
|
||||
|
||||
If you're not using a factory, you already have an app object you can
|
||||
import and configure directly. You can still use an ``app`` fixture to
|
||||
set up and tear down resources.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
from my_project import create_app
|
||||
|
||||
@pytest.fixture()
|
||||
def app():
|
||||
app = create_app()
|
||||
app.config.update({
|
||||
"TESTING": True,
|
||||
})
|
||||
|
||||
# other setup can go here
|
||||
|
||||
yield app
|
||||
|
||||
# clean up / reset resources here
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(app):
|
||||
return app.test_client()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def runner(app):
|
||||
return app.test_cli_runner()
|
||||
|
||||
|
||||
Sending Requests with the Test Client
|
||||
-------------------------------------
|
||||
|
||||
The test client makes requests to the application without running a live
|
||||
server. Flask's client extends
|
||||
:doc:`Werkzeug's client <werkzeug:test>`, see those docs for additional
|
||||
information.
|
||||
|
||||
The ``client`` has methods that match the common HTTP request methods,
|
||||
such as ``client.get()`` and ``client.post()``. They take many arguments
|
||||
for building the request; you can find the full documentation in
|
||||
:class:`~werkzeug.test.EnvironBuilder`. Typically you'll use ``path``,
|
||||
``query_string``, ``headers``, and ``data`` or ``json``.
|
||||
|
||||
To make a request, call the method the request should use with the path
|
||||
to the route to test. A :class:`~werkzeug.test.TestResponse` is returned
|
||||
to examine the response data. It has all the usual properties of a
|
||||
response object. You'll usually look at ``response.data``, which is the
|
||||
bytes returned by the view. If you want to use text, Werkzeug 2.1
|
||||
provides ``response.text``, or use ``response.get_data(as_text=True)``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_request_example(client):
|
||||
response = client.get("/posts")
|
||||
assert b"<h2>Hello, World!</h2>" in response.data
|
||||
|
||||
|
||||
Pass a dict ``query_string={"key": "value", ...}`` to set arguments in
|
||||
the query string (after the ``?`` in the URL). Pass a dict
|
||||
``headers={}`` to set request headers.
|
||||
|
||||
To send a request body in a POST or PUT request, pass a value to
|
||||
``data``. If raw bytes are passed, that exact body is used. Usually,
|
||||
you'll pass a dict to set form data.
|
||||
|
||||
|
||||
Form Data
|
||||
~~~~~~~~~
|
||||
|
||||
To send form data, pass a dict to ``data``. The ``Content-Type`` header
|
||||
will be set to ``multipart/form-data`` or
|
||||
``application/x-www-form-urlencoded`` automatically.
|
||||
|
||||
If a value is a file object opened for reading bytes (``"rb"`` mode), it
|
||||
will be treated as an uploaded file. To change the detected filename and
|
||||
content type, pass a ``(file, filename, content_type)`` tuple. File
|
||||
objects will be closed after making the request, so they do not need to
|
||||
use the usual ``with open() as f:`` pattern.
|
||||
|
||||
It can be useful to store files in a ``tests/resources`` folder, then
|
||||
use ``pathlib.Path`` to get files relative to the current test file.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# get the resources folder in the tests folder
|
||||
resources = Path(__file__).parent / "resources"
|
||||
|
||||
def test_edit_user(client):
|
||||
response = client.post("/user/2/edit", data={
|
||||
"name": "Flask",
|
||||
"theme": "dark",
|
||||
"picture": (resources / "picture.png").open("rb"),
|
||||
})
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
JSON Data
|
||||
~~~~~~~~~
|
||||
|
||||
To send JSON data, pass an object to ``json``. The ``Content-Type``
|
||||
header will be set to ``application/json`` automatically.
|
||||
|
||||
Similarly, if the response contains JSON data, the ``response.json``
|
||||
attribute will contain the deserialized object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_json_data(client):
|
||||
response = client.post("/graphql", json={
|
||||
"query": """
|
||||
query User($id: String!) {
|
||||
user(id: $id) {
|
||||
name
|
||||
theme
|
||||
picture_url
|
||||
}
|
||||
}
|
||||
""",
|
||||
variables={"id": 2},
|
||||
})
|
||||
assert response.json["data"]["user"]["name"] == "Flask"
|
||||
|
||||
|
||||
Following Redirects
|
||||
-------------------
|
||||
|
||||
By default, the client does not make additional requests if the response
|
||||
is a redirect. By passing ``follow_redirects=True`` to a request method,
|
||||
the client will continue to make requests until a non-redirect response
|
||||
is returned.
|
||||
|
||||
:attr:`TestResponse.history <werkzeug.test.TestResponse.history>` is
|
||||
a tuple of the responses that led up to the final response. Each
|
||||
response has a :attr:`~werkzeug.test.TestResponse.request` attribute
|
||||
which records the request that produced that response.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_logout_redirect(client):
|
||||
response = client.get("/logout", follow_redirects=True)
|
||||
# Check that there was one redirect response.
|
||||
assert len(response.history) == 1
|
||||
# Check that the second request was to the index page.
|
||||
assert response.request.path == "/index"
|
||||
|
||||
|
||||
Accessing and Modifying the Session
|
||||
-----------------------------------
|
||||
|
||||
To access Flask's context variables, mainly
|
||||
:data:`~flask.session`, use the client in a ``with`` statement.
|
||||
The app and request context will remain active *after* making a request,
|
||||
until the ``with`` block ends.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import session
|
||||
|
||||
def test_access_session(client):
|
||||
with client:
|
||||
client.post("/auth/login", data={"username": "flask"})
|
||||
# session is still accessible
|
||||
assert session["user_id"] == 1
|
||||
|
||||
# session is no longer accessible
|
||||
|
||||
If you want to access or set a value in the session *before* making a
|
||||
request, use the client's
|
||||
:meth:`~flask.testing.FlaskClient.session_transaction` method in a
|
||||
``with`` statement. It returns a session object, and will save the
|
||||
session once the block ends.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import session
|
||||
|
||||
def test_modify_session(client):
|
||||
with client.session_transaction() as session:
|
||||
# set a user id without going through the login route
|
||||
session["user_id"] = 1
|
||||
|
||||
# session is saved now
|
||||
|
||||
response = client.get("/users/me")
|
||||
assert response.json["username"] == "flask"
|
||||
|
||||
|
||||
.. _testing-cli:
|
||||
|
||||
Running Commands with the CLI Runner
|
||||
------------------------------------
|
||||
|
||||
Flask provides :meth:`~flask.Flask.test_cli_runner` to create a
|
||||
:class:`~flask.testing.FlaskCliRunner`, which runs CLI commands in
|
||||
isolation and captures the output in a :class:`~click.testing.Result`
|
||||
object. Flask's runner extends :doc:`Click's runner <click:testing>`,
|
||||
see those docs for additional information.
|
||||
|
||||
Use the runner's :meth:`~flask.testing.FlaskCliRunner.invoke` method to
|
||||
call commands in the same way they would be called with the ``flask``
|
||||
command from the command line.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
|
||||
@app.cli.command("hello")
|
||||
@click.option("--name", default="World")
|
||||
def hello_command(name):
|
||||
click.echo(f"Hello, {name}!")
|
||||
|
||||
def test_hello_command(runner):
|
||||
result = runner.invoke(args="hello")
|
||||
assert "World" in result.output
|
||||
|
||||
result = runner.invoke(args=["hello", "--name", "Flask"])
|
||||
assert "Flask" in result.output
|
||||
|
||||
|
||||
Tests that depend on an Active Context
|
||||
--------------------------------------
|
||||
|
||||
You may have functions that are called from views or commands, that expect an
|
||||
active :doc:`app context </appcontext>` because they access :data:`.request`,
|
||||
:data:`.session`, :data:`.g`, or :data:`.current_app`. Rather than testing them by
|
||||
making a request or invoking the command, you can create and activate a context
|
||||
directly.
|
||||
|
||||
Use ``with app.app_context()`` to push an application context. For
|
||||
example, database extensions usually require an active app context to
|
||||
make queries.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_db_post_model(app):
|
||||
with app.app_context():
|
||||
post = db.session.query(Post).get(1)
|
||||
|
||||
Use ``with app.test_request_context()`` to push a request context. It
|
||||
takes the same arguments as the test client's request methods.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_validate_user_edit(app):
|
||||
with app.test_request_context(
|
||||
"/user/2/edit", method="POST", data={"name": ""}
|
||||
):
|
||||
# call a function that accesses `request`
|
||||
messages = validate_edit_user()
|
||||
|
||||
assert messages["name"][0] == "Name cannot be empty."
|
||||
|
||||
Creating a test request context doesn't run any of the Flask dispatching
|
||||
code, so ``before_request`` functions are not called. If you need to
|
||||
call these, usually it's better to make a full request instead. However,
|
||||
it's possible to call them manually.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_auth_token(app):
|
||||
with app.test_request_context("/user/2/edit", headers={"X-Auth-Token": "1"}):
|
||||
app.preprocess_request()
|
||||
assert g.user.name == "Flask"
|
||||
@ -0,0 +1,336 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Blog Blueprint
|
||||
==============
|
||||
|
||||
You'll use the same techniques you learned about when writing the
|
||||
authentication blueprint to write the blog blueprint. The blog should
|
||||
list all posts, allow logged in users to create posts, and allow the
|
||||
author of a post to edit or delete it.
|
||||
|
||||
As you implement each view, keep the development server running. As you
|
||||
save your changes, try going to the URL in your browser and testing them
|
||||
out.
|
||||
|
||||
The Blueprint
|
||||
-------------
|
||||
|
||||
Define the blueprint and register it in the application factory.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/blog.py``
|
||||
|
||||
from flask import (
|
||||
Blueprint, flash, g, redirect, render_template, request, url_for
|
||||
)
|
||||
from werkzeug.exceptions import abort
|
||||
|
||||
from flaskr.auth import login_required
|
||||
from flaskr.db import get_db
|
||||
|
||||
bp = Blueprint('blog', __name__)
|
||||
|
||||
Import and register the blueprint from the factory using
|
||||
:meth:`app.register_blueprint() <Flask.register_blueprint>`. Place the
|
||||
new code at the end of the factory function before returning the app.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/__init__.py``
|
||||
|
||||
def create_app():
|
||||
app = ...
|
||||
# existing code omitted
|
||||
|
||||
from . import blog
|
||||
app.register_blueprint(blog.bp)
|
||||
app.add_url_rule('/', endpoint='index')
|
||||
|
||||
return app
|
||||
|
||||
|
||||
Unlike the auth blueprint, the blog blueprint does not have a
|
||||
``url_prefix``. So the ``index`` view will be at ``/``, the ``create``
|
||||
view at ``/create``, and so on. The blog is the main feature of Flaskr,
|
||||
so it makes sense that the blog index will be the main index.
|
||||
|
||||
However, the endpoint for the ``index`` view defined below will be
|
||||
``blog.index``. Some of the authentication views referred to a plain
|
||||
``index`` endpoint. :meth:`app.add_url_rule() <Flask.add_url_rule>`
|
||||
associates the endpoint name ``'index'`` with the ``/`` url so that
|
||||
``url_for('index')`` or ``url_for('blog.index')`` will both work,
|
||||
generating the same ``/`` URL either way.
|
||||
|
||||
In another application you might give the blog blueprint a
|
||||
``url_prefix`` and define a separate ``index`` view in the application
|
||||
factory, similar to the ``hello`` view. Then the ``index`` and
|
||||
``blog.index`` endpoints and URLs would be different.
|
||||
|
||||
|
||||
Index
|
||||
-----
|
||||
|
||||
The index will show all of the posts, most recent first. A ``JOIN`` is
|
||||
used so that the author information from the ``user`` table is
|
||||
available in the result.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/blog.py``
|
||||
|
||||
@bp.route('/')
|
||||
def index():
|
||||
db = get_db()
|
||||
posts = db.execute(
|
||||
'SELECT p.id, title, body, created, author_id, username'
|
||||
' FROM post p JOIN user u ON p.author_id = u.id'
|
||||
' ORDER BY created DESC'
|
||||
).fetchall()
|
||||
return render_template('blog/index.html', posts=posts)
|
||||
|
||||
.. code-block:: html+jinja
|
||||
:caption: ``flaskr/templates/blog/index.html``
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Posts{% endblock %}</h1>
|
||||
{% if g.user %}
|
||||
<a class="action" href="{{ url_for('blog.create') }}">New</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% for post in posts %}
|
||||
<article class="post">
|
||||
<header>
|
||||
<div>
|
||||
<h1>{{ post['title'] }}</h1>
|
||||
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
|
||||
</div>
|
||||
{% if g.user['id'] == post['author_id'] %}
|
||||
<a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
|
||||
{% endif %}
|
||||
</header>
|
||||
<p class="body">{{ post['body'] }}</p>
|
||||
</article>
|
||||
{% if not loop.last %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
When a user is logged in, the ``header`` block adds a link to the
|
||||
``create`` view. When the user is the author of a post, they'll see an
|
||||
"Edit" link to the ``update`` view for that post. ``loop.last`` is a
|
||||
special variable available inside `Jinja for loops`_. It's used to
|
||||
display a line after each post except the last one, to visually separate
|
||||
them.
|
||||
|
||||
.. _Jinja for loops: https://jinja.palletsprojects.com/templates/#for
|
||||
|
||||
|
||||
Create
|
||||
------
|
||||
|
||||
The ``create`` view works the same as the auth ``register`` view. Either
|
||||
the form is displayed, or the posted data is validated and the post is
|
||||
added to the database or an error is shown.
|
||||
|
||||
The ``login_required`` decorator you wrote earlier is used on the blog
|
||||
views. A user must be logged in to visit these views, otherwise they
|
||||
will be redirected to the login page.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/blog.py``
|
||||
|
||||
@bp.route('/create', methods=('GET', 'POST'))
|
||||
@login_required
|
||||
def create():
|
||||
if request.method == 'POST':
|
||||
title = request.form['title']
|
||||
body = request.form['body']
|
||||
error = None
|
||||
|
||||
if not title:
|
||||
error = 'Title is required.'
|
||||
|
||||
if error is not None:
|
||||
flash(error)
|
||||
else:
|
||||
db = get_db()
|
||||
db.execute(
|
||||
'INSERT INTO post (title, body, author_id)'
|
||||
' VALUES (?, ?, ?)',
|
||||
(title, body, g.user['id'])
|
||||
)
|
||||
db.commit()
|
||||
return redirect(url_for('blog.index'))
|
||||
|
||||
return render_template('blog/create.html')
|
||||
|
||||
.. code-block:: html+jinja
|
||||
:caption: ``flaskr/templates/blog/create.html``
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}New Post{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<label for="title">Title</label>
|
||||
<input name="title" id="title" value="{{ request.form['title'] }}" required>
|
||||
<label for="body">Body</label>
|
||||
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
|
||||
<input type="submit" value="Save">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Update
|
||||
------
|
||||
|
||||
Both the ``update`` and ``delete`` views will need to fetch a ``post``
|
||||
by ``id`` and check if the author matches the logged in user. To avoid
|
||||
duplicating code, you can write a function to get the ``post`` and call
|
||||
it from each view.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/blog.py``
|
||||
|
||||
def get_post(id, check_author=True):
|
||||
post = get_db().execute(
|
||||
'SELECT p.id, title, body, created, author_id, username'
|
||||
' FROM post p JOIN user u ON p.author_id = u.id'
|
||||
' WHERE p.id = ?',
|
||||
(id,)
|
||||
).fetchone()
|
||||
|
||||
if post is None:
|
||||
abort(404, f"Post id {id} doesn't exist.")
|
||||
|
||||
if check_author and post['author_id'] != g.user['id']:
|
||||
abort(403)
|
||||
|
||||
return post
|
||||
|
||||
:func:`abort` will raise a special exception that returns an HTTP status
|
||||
code. It takes an optional message to show with the error, otherwise a
|
||||
default message is used. ``404`` means "Not Found", and ``403`` means
|
||||
"Forbidden". (``401`` means "Unauthorized", but you redirect to the
|
||||
login page instead of returning that status.)
|
||||
|
||||
The ``check_author`` argument is defined so that the function can be
|
||||
used to get a ``post`` without checking the author. This would be useful
|
||||
if you wrote a view to show an individual post on a page, where the user
|
||||
doesn't matter because they're not modifying the post.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/blog.py``
|
||||
|
||||
@bp.route('/<int:id>/update', methods=('GET', 'POST'))
|
||||
@login_required
|
||||
def update(id):
|
||||
post = get_post(id)
|
||||
|
||||
if request.method == 'POST':
|
||||
title = request.form['title']
|
||||
body = request.form['body']
|
||||
error = None
|
||||
|
||||
if not title:
|
||||
error = 'Title is required.'
|
||||
|
||||
if error is not None:
|
||||
flash(error)
|
||||
else:
|
||||
db = get_db()
|
||||
db.execute(
|
||||
'UPDATE post SET title = ?, body = ?'
|
||||
' WHERE id = ?',
|
||||
(title, body, id)
|
||||
)
|
||||
db.commit()
|
||||
return redirect(url_for('blog.index'))
|
||||
|
||||
return render_template('blog/update.html', post=post)
|
||||
|
||||
Unlike the views you've written so far, the ``update`` function takes
|
||||
an argument, ``id``. That corresponds to the ``<int:id>`` in the route.
|
||||
A real URL will look like ``/1/update``. Flask will capture the ``1``,
|
||||
ensure it's an :class:`int`, and pass it as the ``id`` argument. If you
|
||||
don't specify ``int:`` and instead do ``<id>``, it will be a string.
|
||||
To generate a URL to the update page, :func:`url_for` needs to be passed
|
||||
the ``id`` so it knows what to fill in:
|
||||
``url_for('blog.update', id=post['id'])``. This is also in the
|
||||
``index.html`` file above.
|
||||
|
||||
The ``create`` and ``update`` views look very similar. The main
|
||||
difference is that the ``update`` view uses a ``post`` object and an
|
||||
``UPDATE`` query instead of an ``INSERT``. With some clever refactoring,
|
||||
you could use one view and template for both actions, but for the
|
||||
tutorial it's clearer to keep them separate.
|
||||
|
||||
.. code-block:: html+jinja
|
||||
:caption: ``flaskr/templates/blog/update.html``
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<label for="title">Title</label>
|
||||
<input name="title" id="title"
|
||||
value="{{ request.form['title'] or post['title'] }}" required>
|
||||
<label for="body">Body</label>
|
||||
<textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
|
||||
<input type="submit" value="Save">
|
||||
</form>
|
||||
<hr>
|
||||
<form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
|
||||
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
This template has two forms. The first posts the edited data to the
|
||||
current page (``/<id>/update``). The other form contains only a button
|
||||
and specifies an ``action`` attribute that posts to the delete view
|
||||
instead. The button uses some JavaScript to show a confirmation dialog
|
||||
before submitting.
|
||||
|
||||
The pattern ``{{ request.form['title'] or post['title'] }}`` is used to
|
||||
choose what data appears in the form. When the form hasn't been
|
||||
submitted, the original ``post`` data appears, but if invalid form data
|
||||
was posted you want to display that so the user can fix the error, so
|
||||
``request.form`` is used instead. :data:`.request` is another variable
|
||||
that's automatically available in templates.
|
||||
|
||||
|
||||
Delete
|
||||
------
|
||||
|
||||
The delete view doesn't have its own template, the delete button is part
|
||||
of ``update.html`` and posts to the ``/<id>/delete`` URL. Since there
|
||||
is no template, it will only handle the ``POST`` method and then redirect
|
||||
to the ``index`` view.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/blog.py``
|
||||
|
||||
@bp.route('/<int:id>/delete', methods=('POST',))
|
||||
@login_required
|
||||
def delete(id):
|
||||
get_post(id)
|
||||
db = get_db()
|
||||
db.execute('DELETE FROM post WHERE id = ?', (id,))
|
||||
db.commit()
|
||||
return redirect(url_for('blog.index'))
|
||||
|
||||
Congratulations, you've now finished writing your application! Take some
|
||||
time to try out everything in the browser. However, there's still more
|
||||
to do before the project is complete.
|
||||
|
||||
Continue to :doc:`install`.
|
||||
@ -0,0 +1,219 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Define and Access the Database
|
||||
==============================
|
||||
|
||||
The application will use a `SQLite`_ database to store users and posts.
|
||||
Python comes with built-in support for SQLite in the :mod:`sqlite3`
|
||||
module.
|
||||
|
||||
SQLite is convenient because it doesn't require setting up a separate
|
||||
database server and is built-in to Python. However, if concurrent
|
||||
requests try to write to the database at the same time, they will slow
|
||||
down as each write happens sequentially. Small applications won't notice
|
||||
this. Once you become big, you may want to switch to a different
|
||||
database.
|
||||
|
||||
The tutorial doesn't go into detail about SQL. If you are not familiar
|
||||
with it, the SQLite docs describe the `language`_.
|
||||
|
||||
.. _SQLite: https://sqlite.org/about.html
|
||||
.. _language: https://sqlite.org/lang.html
|
||||
|
||||
|
||||
Connect to the Database
|
||||
-----------------------
|
||||
|
||||
The first thing to do when working with a SQLite database (and most
|
||||
other Python database libraries) is to create a connection to it. Any
|
||||
queries and operations are performed using the connection, which is
|
||||
closed after the work is finished.
|
||||
|
||||
In web applications this connection is typically tied to the request. It
|
||||
is created at some point when handling a request, and closed before the
|
||||
response is sent.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/db.py``
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
import click
|
||||
from flask import current_app, g
|
||||
|
||||
|
||||
def get_db():
|
||||
if 'db' not in g:
|
||||
g.db = sqlite3.connect(
|
||||
current_app.config['DATABASE'],
|
||||
detect_types=sqlite3.PARSE_DECLTYPES
|
||||
)
|
||||
g.db.row_factory = sqlite3.Row
|
||||
|
||||
return g.db
|
||||
|
||||
|
||||
def close_db(e=None):
|
||||
db = g.pop('db', None)
|
||||
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
:data:`.g` is a special object that is unique for each request. It is
|
||||
used to store data that might be accessed by multiple functions during
|
||||
the request. The connection is stored and reused instead of creating a
|
||||
new connection if ``get_db`` is called a second time in the same
|
||||
request.
|
||||
|
||||
:data:`.current_app` is another special object that points to the Flask
|
||||
application handling the request. Since you used an application factory,
|
||||
there is no application object when writing the rest of your code.
|
||||
``get_db`` will be called when the application has been created and is
|
||||
handling a request, so :data:`.current_app` can be used.
|
||||
|
||||
:func:`sqlite3.connect` establishes a connection to the file pointed at
|
||||
by the ``DATABASE`` configuration key. This file doesn't have to exist
|
||||
yet, and won't until you initialize the database later.
|
||||
|
||||
:class:`sqlite3.Row` tells the connection to return rows that behave
|
||||
like dicts. This allows accessing the columns by name.
|
||||
|
||||
``close_db`` checks if a connection was created by checking if ``g.db``
|
||||
was set. If the connection exists, it is closed. Further down you will
|
||||
tell your application about the ``close_db`` function in the application
|
||||
factory so that it is called after each request.
|
||||
|
||||
|
||||
Create the Tables
|
||||
-----------------
|
||||
|
||||
In SQLite, data is stored in *tables* and *columns*. These need to be
|
||||
created before you can store and retrieve data. Flaskr will store users
|
||||
in the ``user`` table, and posts in the ``post`` table. Create a file
|
||||
with the SQL commands needed to create empty tables:
|
||||
|
||||
.. code-block:: sql
|
||||
:caption: ``flaskr/schema.sql``
|
||||
|
||||
DROP TABLE IF EXISTS user;
|
||||
DROP TABLE IF EXISTS post;
|
||||
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE post (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
author_id INTEGER NOT NULL,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
title TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
FOREIGN KEY (author_id) REFERENCES user (id)
|
||||
);
|
||||
|
||||
Add the Python functions that will run these SQL commands to the
|
||||
``db.py`` file:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/db.py``
|
||||
|
||||
def init_db():
|
||||
db = get_db()
|
||||
|
||||
with current_app.open_resource('schema.sql') as f:
|
||||
db.executescript(f.read().decode('utf8'))
|
||||
|
||||
|
||||
@click.command('init-db')
|
||||
def init_db_command():
|
||||
"""Clear the existing data and create new tables."""
|
||||
init_db()
|
||||
click.echo('Initialized the database.')
|
||||
|
||||
|
||||
sqlite3.register_converter(
|
||||
"timestamp", lambda v: datetime.fromisoformat(v.decode())
|
||||
)
|
||||
|
||||
:meth:`open_resource() <Flask.open_resource>` opens a file relative to
|
||||
the ``flaskr`` package, which is useful since you won't necessarily know
|
||||
where that location is when deploying the application later. ``get_db``
|
||||
returns a database connection, which is used to execute the commands
|
||||
read from the file.
|
||||
|
||||
:func:`click.command` defines a command line command called ``init-db``
|
||||
that calls the ``init_db`` function and shows a success message to the
|
||||
user. You can read :doc:`/cli` to learn more about writing commands.
|
||||
|
||||
The call to :func:`sqlite3.register_converter` tells Python how to
|
||||
interpret timestamp values in the database. We convert the value to a
|
||||
:class:`datetime.datetime`.
|
||||
|
||||
|
||||
Register with the Application
|
||||
-----------------------------
|
||||
|
||||
The ``close_db`` and ``init_db_command`` functions need to be registered
|
||||
with the application instance; otherwise, they won't be used by the
|
||||
application. However, since you're using a factory function, that
|
||||
instance isn't available when writing the functions. Instead, write a
|
||||
function that takes an application and does the registration.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/db.py``
|
||||
|
||||
def init_app(app):
|
||||
app.teardown_appcontext(close_db)
|
||||
app.cli.add_command(init_db_command)
|
||||
|
||||
:meth:`app.teardown_appcontext() <Flask.teardown_appcontext>` tells
|
||||
Flask to call that function when cleaning up after returning the
|
||||
response.
|
||||
|
||||
:meth:`app.cli.add_command() <click.Group.add_command>` adds a new
|
||||
command that can be called with the ``flask`` command.
|
||||
|
||||
Import and call this function from the factory. Place the new code at
|
||||
the end of the factory function before returning the app.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/__init__.py``
|
||||
|
||||
def create_app():
|
||||
app = ...
|
||||
# existing code omitted
|
||||
|
||||
from . import db
|
||||
db.init_app(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
Initialize the Database File
|
||||
----------------------------
|
||||
|
||||
Now that ``init-db`` has been registered with the app, it can be called
|
||||
using the ``flask`` command, similar to the ``run`` command from the
|
||||
previous page.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're still running the server from the previous page, you can
|
||||
either stop the server, or run this command in a new terminal. If
|
||||
you use a new terminal, remember to change to your project directory
|
||||
and activate the env as described in :doc:`/installation`.
|
||||
|
||||
Run the ``init-db`` command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ flask --app flaskr init-db
|
||||
Initialized the database.
|
||||
|
||||
There will now be a ``flaskr.sqlite`` file in the ``instance`` folder in
|
||||
your project.
|
||||
|
||||
Continue to :doc:`views`.
|
||||
@ -0,0 +1,111 @@
|
||||
Deploy to Production
|
||||
====================
|
||||
|
||||
This part of the tutorial assumes you have a server that you want to
|
||||
deploy your application to. It gives an overview of how to create the
|
||||
distribution file and install it, but won't go into specifics about
|
||||
what server or software to use. You can set up a new environment on your
|
||||
development computer to try out the instructions below, but probably
|
||||
shouldn't use it for hosting a real public application. See
|
||||
:doc:`/deploying/index` for a list of many different ways to host your
|
||||
application.
|
||||
|
||||
|
||||
Build and Install
|
||||
-----------------
|
||||
|
||||
When you want to deploy your application elsewhere, you build a *wheel*
|
||||
(``.whl``) file. Install and use the ``build`` tool to do this.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ pip install build
|
||||
$ python -m build --wheel
|
||||
|
||||
You can find the file in ``dist/flaskr-1.0.0-py3-none-any.whl``. The
|
||||
file name is in the format of {project name}-{version}-{python tag}
|
||||
-{abi tag}-{platform tag}.
|
||||
|
||||
Copy this file to another machine,
|
||||
:ref:`set up a new virtualenv <install-create-env>`, then install the
|
||||
file with ``pip``.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ pip install flaskr-1.0.0-py3-none-any.whl
|
||||
|
||||
Pip will install your project along with its dependencies.
|
||||
|
||||
Since this is a different machine, you need to run ``init-db`` again to
|
||||
create the database in the instance folder.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app flaskr init-db
|
||||
|
||||
When Flask detects that it's installed (not in editable mode), it uses
|
||||
a different directory for the instance folder. You can find it at
|
||||
``.venv/var/flaskr-instance`` instead.
|
||||
|
||||
|
||||
Configure the Secret Key
|
||||
------------------------
|
||||
|
||||
In the beginning of the tutorial that you gave a default value for
|
||||
:data:`SECRET_KEY`. This should be changed to some random bytes in
|
||||
production. Otherwise, attackers could use the public ``'dev'`` key to
|
||||
modify the session cookie, or anything else that uses the secret key.
|
||||
|
||||
You can use the following command to output a random secret key:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ python -c 'import secrets; print(secrets.token_hex())'
|
||||
|
||||
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
|
||||
Create the ``config.py`` file in the instance folder, which the factory
|
||||
will read from if it exists. Copy the generated value into it.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``.venv/var/flaskr-instance/config.py``
|
||||
|
||||
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
|
||||
You can also set any other necessary configuration here, although
|
||||
``SECRET_KEY`` is the only one needed for Flaskr.
|
||||
|
||||
|
||||
Run with a Production Server
|
||||
----------------------------
|
||||
|
||||
When running publicly rather than in development, you should not use the
|
||||
built-in development server (``flask run``). The development server is
|
||||
provided by Werkzeug for convenience, but is not designed to be
|
||||
particularly efficient, stable, or secure.
|
||||
|
||||
Instead, use a production WSGI server. For example, to use `Waitress`_,
|
||||
first install it in the virtual environment:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ pip install waitress
|
||||
|
||||
You need to tell Waitress about your application, but it doesn't use
|
||||
``--app`` like ``flask run`` does. You need to tell it to import and
|
||||
call the application factory to get an application object.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ waitress-serve --call 'flaskr:create_app'
|
||||
|
||||
Serving on http://0.0.0.0:8080
|
||||
|
||||
See :doc:`/deploying/index` for a list of many different ways to host
|
||||
your application. Waitress is just an example, chosen for the tutorial
|
||||
because it supports both Windows and Linux. There are many more WSGI
|
||||
servers and deployment options that you may choose for your project.
|
||||
|
||||
.. _Waitress: https://docs.pylonsproject.org/projects/waitress/en/stable/
|
||||
|
||||
Continue to :doc:`next`.
|
||||
@ -0,0 +1,162 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Application Setup
|
||||
=================
|
||||
|
||||
A Flask application is an instance of the :class:`Flask` class.
|
||||
Everything about the application, such as configuration and URLs, will
|
||||
be registered with this class.
|
||||
|
||||
The most straightforward way to create a Flask application is to create
|
||||
a global :class:`Flask` instance directly at the top of your code, like
|
||||
how the "Hello, World!" example did on the previous page. While this is
|
||||
simple and useful in some cases, it can cause some tricky issues as the
|
||||
project grows.
|
||||
|
||||
Instead of creating a :class:`Flask` instance globally, you will create
|
||||
it inside a function. This function is known as the *application
|
||||
factory*. Any configuration, registration, and other setup the
|
||||
application needs will happen inside the function, then the application
|
||||
will be returned.
|
||||
|
||||
|
||||
The Application Factory
|
||||
-----------------------
|
||||
|
||||
It's time to start coding! Create the ``flaskr`` directory and add the
|
||||
``__init__.py`` file. The ``__init__.py`` serves double duty: it will
|
||||
contain the application factory, and it tells Python that the ``flaskr``
|
||||
directory should be treated as a package.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ mkdir flaskr
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/__init__.py``
|
||||
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
|
||||
|
||||
def create_app(test_config=None):
|
||||
# create and configure the app
|
||||
app = Flask(__name__, instance_relative_config=True)
|
||||
app.config.from_mapping(
|
||||
SECRET_KEY='dev',
|
||||
DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
|
||||
)
|
||||
|
||||
if test_config is None:
|
||||
# load the instance config, if it exists, when not testing
|
||||
app.config.from_pyfile('config.py', silent=True)
|
||||
else:
|
||||
# load the test config if passed in
|
||||
app.config.from_mapping(test_config)
|
||||
|
||||
# ensure the instance folder exists
|
||||
try:
|
||||
os.makedirs(app.instance_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# a simple page that says hello
|
||||
@app.route('/hello')
|
||||
def hello():
|
||||
return 'Hello, World!'
|
||||
|
||||
return app
|
||||
|
||||
``create_app`` is the application factory function. You'll add to it
|
||||
later in the tutorial, but it already does a lot.
|
||||
|
||||
#. ``app = Flask(__name__, instance_relative_config=True)`` creates the
|
||||
:class:`Flask` instance.
|
||||
|
||||
* ``__name__`` is the name of the current Python module. The app
|
||||
needs to know where it's located to set up some paths, and
|
||||
``__name__`` is a convenient way to tell it that.
|
||||
|
||||
* ``instance_relative_config=True`` tells the app that
|
||||
configuration files are relative to the
|
||||
:ref:`instance folder <instance-folders>`. The instance folder
|
||||
is located outside the ``flaskr`` package and can hold local
|
||||
data that shouldn't be committed to version control, such as
|
||||
configuration secrets and the database file.
|
||||
|
||||
#. :meth:`app.config.from_mapping() <Config.from_mapping>` sets
|
||||
some default configuration that the app will use:
|
||||
|
||||
* :data:`SECRET_KEY` is used by Flask and extensions to keep data
|
||||
safe. It's set to ``'dev'`` to provide a convenient value
|
||||
during development, but it should be overridden with a random
|
||||
value when deploying.
|
||||
|
||||
* ``DATABASE`` is the path where the SQLite database file will be
|
||||
saved. It's under
|
||||
:attr:`app.instance_path <Flask.instance_path>`, which is the
|
||||
path that Flask has chosen for the instance folder. You'll learn
|
||||
more about the database in the next section.
|
||||
|
||||
#. :meth:`app.config.from_pyfile() <Config.from_pyfile>` overrides
|
||||
the default configuration with values taken from the ``config.py``
|
||||
file in the instance folder if it exists. For example, when
|
||||
deploying, this can be used to set a real ``SECRET_KEY``.
|
||||
|
||||
* ``test_config`` can also be passed to the factory, and will be
|
||||
used instead of the instance configuration. This is so the tests
|
||||
you'll write later in the tutorial can be configured
|
||||
independently of any development values you have configured.
|
||||
|
||||
#. :func:`os.makedirs` ensures that
|
||||
:attr:`app.instance_path <Flask.instance_path>` exists. Flask
|
||||
doesn't create the instance folder automatically, but it needs to be
|
||||
created because your project will create the SQLite database file
|
||||
there.
|
||||
|
||||
#. :meth:`@app.route() <Flask.route>` creates a simple route so you can
|
||||
see the application working before getting into the rest of the
|
||||
tutorial. It creates a connection between the URL ``/hello`` and a
|
||||
function that returns a response, the string ``'Hello, World!'`` in
|
||||
this case.
|
||||
|
||||
|
||||
Run The Application
|
||||
-------------------
|
||||
|
||||
Now you can run your application using the ``flask`` command. From the
|
||||
terminal, tell Flask where to find your application, then run it in
|
||||
debug mode. Remember, you should still be in the top-level
|
||||
``flask-tutorial`` directory, not the ``flaskr`` package.
|
||||
|
||||
Debug mode shows an interactive debugger whenever a page raises an
|
||||
exception, and restarts the server whenever you make changes to the
|
||||
code. You can leave it running and just reload the browser page as you
|
||||
follow the tutorial.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app flaskr run --debug
|
||||
|
||||
You'll see output similar to this:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
* Serving Flask app "flaskr"
|
||||
* Debug mode: on
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
* Restarting with stat
|
||||
* Debugger is active!
|
||||
* Debugger PIN: nnn-nnn-nnn
|
||||
|
||||
Visit http://127.0.0.1:5000/hello in a browser and you should see the
|
||||
"Hello, World!" message. Congratulations, you're now running your Flask
|
||||
web application!
|
||||
|
||||
If another program is already using port 5000, you'll see
|
||||
``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the
|
||||
server tries to start. See :ref:`address-already-in-use` for how to
|
||||
handle that.
|
||||
|
||||
Continue to :doc:`database`.
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
@ -0,0 +1,64 @@
|
||||
Tutorial
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:caption: Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
layout
|
||||
factory
|
||||
database
|
||||
views
|
||||
templates
|
||||
static
|
||||
blog
|
||||
install
|
||||
tests
|
||||
deploy
|
||||
next
|
||||
|
||||
This tutorial will walk you through creating a basic blog application
|
||||
called Flaskr. Users will be able to register, log in, create posts,
|
||||
and edit or delete their own posts. You will be able to package and
|
||||
install the application on other computers.
|
||||
|
||||
.. image:: flaskr_index.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of index page
|
||||
|
||||
It's assumed that you're already familiar with Python. The `official
|
||||
tutorial`_ in the Python docs is a great way to learn or review first.
|
||||
|
||||
.. _official tutorial: https://docs.python.org/3/tutorial/
|
||||
|
||||
While it's designed to give a good starting point, the tutorial doesn't
|
||||
cover all of Flask's features. Check out the :doc:`/quickstart` for an
|
||||
overview of what Flask can do, then dive into the docs to find out more.
|
||||
The tutorial only uses what's provided by Flask and Python. In another
|
||||
project, you might decide to use :doc:`/extensions` or other libraries
|
||||
to make some tasks simpler.
|
||||
|
||||
.. image:: flaskr_login.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of login page
|
||||
|
||||
Flask is flexible. It doesn't require you to use any particular project
|
||||
or code layout. However, when first starting, it's helpful to use a more
|
||||
structured approach. This means that the tutorial will require a bit of
|
||||
boilerplate up front, but it's done to avoid many common pitfalls that
|
||||
new developers encounter, and it creates a project that's easy to expand
|
||||
on. Once you become more comfortable with Flask, you can step out of
|
||||
this structure and take full advantage of Flask's flexibility.
|
||||
|
||||
.. image:: flaskr_edit.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of edit page
|
||||
|
||||
:gh:`The tutorial project is available as an example in the Flask
|
||||
repository <examples/tutorial>`, if you want to compare your project
|
||||
with the final product as you follow the tutorial.
|
||||
|
||||
Continue to :doc:`layout`.
|
||||
@ -0,0 +1,89 @@
|
||||
Make the Project Installable
|
||||
============================
|
||||
|
||||
Making your project installable means that you can build a *wheel* file and install that
|
||||
in another environment, just like you installed Flask in your project's environment.
|
||||
This makes deploying your project the same as installing any other library, so you're
|
||||
using all the standard Python tools to manage everything.
|
||||
|
||||
Installing also comes with other benefits that might not be obvious from
|
||||
the tutorial or as a new Python user, including:
|
||||
|
||||
* Currently, Python and Flask understand how to use the ``flaskr``
|
||||
package only because you're running from your project's directory.
|
||||
Installing means you can import it no matter where you run from.
|
||||
|
||||
* You can manage your project's dependencies just like other packages
|
||||
do, so ``pip install yourproject.whl`` installs them.
|
||||
|
||||
* Test tools can isolate your test environment from your development
|
||||
environment.
|
||||
|
||||
.. note::
|
||||
This is being introduced late in the tutorial, but in your future
|
||||
projects you should always start with this.
|
||||
|
||||
|
||||
Describe the Project
|
||||
--------------------
|
||||
|
||||
The ``pyproject.toml`` file describes your project and how to build it.
|
||||
|
||||
.. code-block:: toml
|
||||
:caption: ``pyproject.toml``
|
||||
|
||||
[project]
|
||||
name = "flaskr"
|
||||
version = "1.0.0"
|
||||
description = "The basic blog app built in the Flask tutorial."
|
||||
dependencies = [
|
||||
"flask",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["flit_core<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
See the official `Packaging tutorial <packaging tutorial_>`_ for more
|
||||
explanation of the files and options used.
|
||||
|
||||
.. _packaging tutorial: https://packaging.python.org/tutorials/packaging-projects/
|
||||
|
||||
|
||||
Install the Project
|
||||
-------------------
|
||||
|
||||
Use ``pip`` to install your project in the virtual environment.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ pip install -e .
|
||||
|
||||
This tells pip to find ``pyproject.toml`` in the current directory and install the
|
||||
project in *editable* or *development* mode. Editable mode means that as you make
|
||||
changes to your local code, you'll only need to re-install if you change the metadata
|
||||
about the project, such as its dependencies.
|
||||
|
||||
You can observe that the project is now installed with ``pip list``.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ pip list
|
||||
|
||||
Package Version Location
|
||||
-------------- --------- ----------------------------------
|
||||
click 6.7
|
||||
Flask 1.0
|
||||
flaskr 1.0.0 /home/user/Projects/flask-tutorial
|
||||
itsdangerous 0.24
|
||||
Jinja2 2.10
|
||||
MarkupSafe 1.0
|
||||
pip 9.0.3
|
||||
Werkzeug 0.14.1
|
||||
|
||||
Nothing changes from how you've been running your project so far.
|
||||
``--app`` is still set to ``flaskr`` and ``flask run`` still runs
|
||||
the application, but you can call it from anywhere, not just the
|
||||
``flask-tutorial`` directory.
|
||||
|
||||
Continue to :doc:`tests`.
|
||||
@ -0,0 +1,110 @@
|
||||
Project Layout
|
||||
==============
|
||||
|
||||
Create a project directory and enter it:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ mkdir flask-tutorial
|
||||
$ cd flask-tutorial
|
||||
|
||||
Then follow the :doc:`installation instructions </installation>` to set
|
||||
up a Python virtual environment and install Flask for your project.
|
||||
|
||||
The tutorial will assume you're working from the ``flask-tutorial``
|
||||
directory from now on. The file names at the top of each code block are
|
||||
relative to this directory.
|
||||
|
||||
----
|
||||
|
||||
A Flask application can be as simple as a single file.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``hello.py``
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
return 'Hello, World!'
|
||||
|
||||
However, as a project gets bigger, it becomes overwhelming to keep all
|
||||
the code in one file. Python projects use *packages* to organize code
|
||||
into multiple modules that can be imported where needed, and the
|
||||
tutorial will do this as well.
|
||||
|
||||
The project directory will contain:
|
||||
|
||||
* ``flaskr/``, a Python package containing your application code and
|
||||
files.
|
||||
* ``tests/``, a directory containing test modules.
|
||||
* ``.venv/``, a Python virtual environment where Flask and other
|
||||
dependencies are installed.
|
||||
* Installation files telling Python how to install your project.
|
||||
* Version control config, such as `git`_. You should make a habit of
|
||||
using some type of version control for all your projects, no matter
|
||||
the size.
|
||||
* Any other project files you might add in the future.
|
||||
|
||||
.. _git: https://git-scm.com/
|
||||
|
||||
By the end, your project layout will look like this:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/home/user/Projects/flask-tutorial
|
||||
├── flaskr/
|
||||
│ ├── __init__.py
|
||||
│ ├── db.py
|
||||
│ ├── schema.sql
|
||||
│ ├── auth.py
|
||||
│ ├── blog.py
|
||||
│ ├── templates/
|
||||
│ │ ├── base.html
|
||||
│ │ ├── auth/
|
||||
│ │ │ ├── login.html
|
||||
│ │ │ └── register.html
|
||||
│ │ └── blog/
|
||||
│ │ ├── create.html
|
||||
│ │ ├── index.html
|
||||
│ │ └── update.html
|
||||
│ └── static/
|
||||
│ └── style.css
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── data.sql
|
||||
│ ├── test_factory.py
|
||||
│ ├── test_db.py
|
||||
│ ├── test_auth.py
|
||||
│ └── test_blog.py
|
||||
├── .venv/
|
||||
├── pyproject.toml
|
||||
└── MANIFEST.in
|
||||
|
||||
If you're using version control, the following files that are generated
|
||||
while running your project should be ignored. There may be other files
|
||||
based on the editor you use. In general, ignore files that you didn't
|
||||
write. For example, with git:
|
||||
|
||||
.. code-block:: none
|
||||
:caption: ``.gitignore``
|
||||
|
||||
.venv/
|
||||
|
||||
*.pyc
|
||||
__pycache__/
|
||||
|
||||
instance/
|
||||
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
|
||||
Continue to :doc:`factory`.
|
||||
@ -0,0 +1,38 @@
|
||||
Keep Developing!
|
||||
================
|
||||
|
||||
You've learned about quite a few Flask and Python concepts throughout
|
||||
the tutorial. Go back and review the tutorial and compare your code with
|
||||
the steps you took to get there. Compare your project to the
|
||||
:gh:`example project <examples/tutorial>`, which might look a bit
|
||||
different due to the step-by-step nature of the tutorial.
|
||||
|
||||
There's a lot more to Flask than what you've seen so far. Even so,
|
||||
you're now equipped to start developing your own web applications. Check
|
||||
out the :doc:`/quickstart` for an overview of what Flask can do, then
|
||||
dive into the docs to keep learning. Flask uses `Jinja`_, `Click`_,
|
||||
`Werkzeug`_, and `ItsDangerous`_ behind the scenes, and they all have
|
||||
their own documentation too. You'll also be interested in
|
||||
:doc:`/extensions` which make tasks like working with the database or
|
||||
validating form data easier and more powerful.
|
||||
|
||||
If you want to keep developing your Flaskr project, here are some ideas
|
||||
for what to try next:
|
||||
|
||||
* A detail view to show a single post. Click a post's title to go to
|
||||
its page.
|
||||
* Like / unlike a post.
|
||||
* Comments.
|
||||
* Tags. Clicking a tag shows all the posts with that tag.
|
||||
* A search box that filters the index page by name.
|
||||
* Paged display. Only show 5 posts per page.
|
||||
* Upload an image to go along with a post.
|
||||
* Format posts using Markdown.
|
||||
* An RSS feed of new posts.
|
||||
|
||||
Have fun and make awesome applications!
|
||||
|
||||
.. _Jinja: https://palletsprojects.com/p/jinja/
|
||||
.. _Click: https://palletsprojects.com/p/click/
|
||||
.. _Werkzeug: https://palletsprojects.com/p/werkzeug/
|
||||
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
|
||||
@ -0,0 +1,72 @@
|
||||
Static Files
|
||||
============
|
||||
|
||||
The authentication views and templates work, but they look very plain
|
||||
right now. Some `CSS`_ can be added to add style to the HTML layout you
|
||||
constructed. The style won't change, so it's a *static* file rather than
|
||||
a template.
|
||||
|
||||
Flask automatically adds a ``static`` view that takes a path relative
|
||||
to the ``flaskr/static`` directory and serves it. The ``base.html``
|
||||
template already has a link to the ``style.css`` file:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{{ url_for('static', filename='style.css') }}
|
||||
|
||||
Besides CSS, other types of static files might be files with JavaScript
|
||||
functions, or a logo image. They are all placed under the
|
||||
``flaskr/static`` directory and referenced with
|
||||
``url_for('static', filename='...')``.
|
||||
|
||||
This tutorial isn't focused on how to write CSS, so you can just copy
|
||||
the following into the ``flaskr/static/style.css`` file:
|
||||
|
||||
.. code-block:: css
|
||||
:caption: ``flaskr/static/style.css``
|
||||
|
||||
html { font-family: sans-serif; background: #eee; padding: 1rem; }
|
||||
body { max-width: 960px; margin: 0 auto; background: white; }
|
||||
h1 { font-family: serif; color: #377ba8; margin: 1rem 0; }
|
||||
a { color: #377ba8; }
|
||||
hr { border: none; border-top: 1px solid lightgray; }
|
||||
nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; }
|
||||
nav h1 { flex: auto; margin: 0; }
|
||||
nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; }
|
||||
nav ul { display: flex; list-style: none; margin: 0; padding: 0; }
|
||||
nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; }
|
||||
.content { padding: 0 1rem 1rem; }
|
||||
.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; }
|
||||
.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; }
|
||||
.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; }
|
||||
.post > header { display: flex; align-items: flex-end; font-size: 0.85em; }
|
||||
.post > header > div:first-of-type { flex: auto; }
|
||||
.post > header h1 { font-size: 1.5em; margin-bottom: 0; }
|
||||
.post .about { color: slategray; font-style: italic; }
|
||||
.post .body { white-space: pre-line; }
|
||||
.content:last-child { margin-bottom: 0; }
|
||||
.content form { margin: 1em 0; display: flex; flex-direction: column; }
|
||||
.content label { font-weight: bold; margin-bottom: 0.5em; }
|
||||
.content input, .content textarea { margin-bottom: 1em; }
|
||||
.content textarea { min-height: 12em; resize: vertical; }
|
||||
input.danger { color: #cc2f2e; }
|
||||
input[type=submit] { align-self: start; min-width: 10em; }
|
||||
|
||||
You can find a less compact version of ``style.css`` in the
|
||||
:gh:`example code <examples/tutorial/flaskr/static/style.css>`.
|
||||
|
||||
Go to http://127.0.0.1:5000/auth/login and the page should look like the
|
||||
screenshot below.
|
||||
|
||||
.. image:: flaskr_login.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of login page
|
||||
|
||||
You can read more about CSS from `Mozilla's documentation <CSS_>`_. If
|
||||
you change a static file, refresh the browser page. If the change
|
||||
doesn't show up, try clearing your browser's cache.
|
||||
|
||||
.. _CSS: https://developer.mozilla.org/docs/Web/CSS
|
||||
|
||||
Continue to :doc:`blog`.
|
||||
@ -0,0 +1,187 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Templates
|
||||
=========
|
||||
|
||||
You've written the authentication views for your application, but if
|
||||
you're running the server and try to go to any of the URLs, you'll see a
|
||||
``TemplateNotFound`` error. That's because the views are calling
|
||||
:func:`render_template`, but you haven't written the templates yet.
|
||||
The template files will be stored in the ``templates`` directory inside
|
||||
the ``flaskr`` package.
|
||||
|
||||
Templates are files that contain static data as well as placeholders
|
||||
for dynamic data. A template is rendered with specific data to produce a
|
||||
final document. Flask uses the `Jinja`_ template library to render
|
||||
templates.
|
||||
|
||||
In your application, you will use templates to render `HTML`_ which
|
||||
will display in the user's browser. In Flask, Jinja is configured to
|
||||
*autoescape* any data that is rendered in HTML templates. This means
|
||||
that it's safe to render user input; any characters they've entered that
|
||||
could mess with the HTML, such as ``<`` and ``>`` will be *escaped* with
|
||||
*safe* values that look the same in the browser but don't cause unwanted
|
||||
effects.
|
||||
|
||||
Jinja looks and behaves mostly like Python. Special delimiters are used
|
||||
to distinguish Jinja syntax from the static data in the template.
|
||||
Anything between ``{{`` and ``}}`` is an expression that will be output
|
||||
to the final document. ``{%`` and ``%}`` denotes a control flow
|
||||
statement like ``if`` and ``for``. Unlike Python, blocks are denoted
|
||||
by start and end tags rather than indentation since static text within
|
||||
a block could change indentation.
|
||||
|
||||
.. _Jinja: https://jinja.palletsprojects.com/templates/
|
||||
.. _HTML: https://developer.mozilla.org/docs/Web/HTML
|
||||
|
||||
|
||||
The Base Layout
|
||||
---------------
|
||||
|
||||
Each page in the application will have the same basic layout around a
|
||||
different body. Instead of writing the entire HTML structure in each
|
||||
template, each template will *extend* a base template and override
|
||||
specific sections.
|
||||
|
||||
.. code-block:: html+jinja
|
||||
:caption: ``flaskr/templates/base.html``
|
||||
|
||||
<!doctype html>
|
||||
<title>{% block title %}{% endblock %} - Flaskr</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<nav>
|
||||
<h1>Flaskr</h1>
|
||||
<ul>
|
||||
{% if g.user %}
|
||||
<li><span>{{ g.user['username'] }}</span>
|
||||
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('auth.register') }}">Register</a>
|
||||
<li><a href="{{ url_for('auth.login') }}">Log In</a>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
<section class="content">
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% block content %}{% endblock %}
|
||||
</section>
|
||||
|
||||
:data:`.g` is automatically available in templates. Based on if
|
||||
``g.user`` is set (from ``load_logged_in_user``), either the username
|
||||
and a log out link are displayed, or links to register and log in
|
||||
are displayed. :func:`url_for` is also automatically available, and is
|
||||
used to generate URLs to views instead of writing them out manually.
|
||||
|
||||
After the page title, and before the content, the template loops over
|
||||
each message returned by :func:`get_flashed_messages`. You used
|
||||
:func:`flash` in the views to show error messages, and this is the code
|
||||
that will display them.
|
||||
|
||||
There are three blocks defined here that will be overridden in the other
|
||||
templates:
|
||||
|
||||
#. ``{% block title %}`` will change the title displayed in the
|
||||
browser's tab and window title.
|
||||
|
||||
#. ``{% block header %}`` is similar to ``title`` but will change the
|
||||
title displayed on the page.
|
||||
|
||||
#. ``{% block content %}`` is where the content of each page goes, such
|
||||
as the login form or a blog post.
|
||||
|
||||
The base template is directly in the ``templates`` directory. To keep
|
||||
the others organized, the templates for a blueprint will be placed in a
|
||||
directory with the same name as the blueprint.
|
||||
|
||||
|
||||
Register
|
||||
--------
|
||||
|
||||
.. code-block:: html+jinja
|
||||
:caption: ``flaskr/templates/auth/register.html``
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Register{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<label for="username">Username</label>
|
||||
<input name="username" id="username" required>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<input type="submit" value="Register">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
``{% extends 'base.html' %}`` tells Jinja that this template should
|
||||
replace the blocks from the base template. All the rendered content must
|
||||
appear inside ``{% block %}`` tags that override blocks from the base
|
||||
template.
|
||||
|
||||
A useful pattern used here is to place ``{% block title %}`` inside
|
||||
``{% block header %}``. This will set the title block and then output
|
||||
the value of it into the header block, so that both the window and page
|
||||
share the same title without writing it twice.
|
||||
|
||||
The ``input`` tags are using the ``required`` attribute here. This tells
|
||||
the browser not to submit the form until those fields are filled in. If
|
||||
the user is using an older browser that doesn't support that attribute,
|
||||
or if they are using something besides a browser to make requests, you
|
||||
still want to validate the data in the Flask view. It's important to
|
||||
always fully validate the data on the server, even if the client does
|
||||
some validation as well.
|
||||
|
||||
|
||||
Log In
|
||||
------
|
||||
|
||||
This is identical to the register template except for the title and
|
||||
submit button.
|
||||
|
||||
.. code-block:: html+jinja
|
||||
:caption: ``flaskr/templates/auth/login.html``
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Log In{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<label for="username">Username</label>
|
||||
<input name="username" id="username" required>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<input type="submit" value="Log In">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Register A User
|
||||
---------------
|
||||
|
||||
Now that the authentication templates are written, you can register a
|
||||
user. Make sure the server is still running (``flask run`` if it's not),
|
||||
then go to http://127.0.0.1:5000/auth/register.
|
||||
|
||||
Try clicking the "Register" button without filling out the form and see
|
||||
that the browser shows an error message. Try removing the ``required``
|
||||
attributes from the ``register.html`` template and click "Register"
|
||||
again. Instead of the browser showing an error, the page will reload and
|
||||
the error from :func:`flash` in the view will be shown.
|
||||
|
||||
Fill out a username and password and you'll be redirected to the login
|
||||
page. Try entering an incorrect username, or the correct username and
|
||||
incorrect password. If you log in you'll get an error because there's
|
||||
no ``index`` view to redirect to yet.
|
||||
|
||||
Continue to :doc:`static`.
|
||||
@ -0,0 +1,559 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Test Coverage
|
||||
=============
|
||||
|
||||
Writing unit tests for your application lets you check that the code
|
||||
you wrote works the way you expect. Flask provides a test client that
|
||||
simulates requests to the application and returns the response data.
|
||||
|
||||
You should test as much of your code as possible. Code in functions only
|
||||
runs when the function is called, and code in branches, such as ``if``
|
||||
blocks, only runs when the condition is met. You want to make sure that
|
||||
each function is tested with data that covers each branch.
|
||||
|
||||
The closer you get to 100% coverage, the more comfortable you can be
|
||||
that making a change won't unexpectedly change other behavior. However,
|
||||
100% coverage doesn't guarantee that your application doesn't have bugs.
|
||||
In particular, it doesn't test how the user interacts with the
|
||||
application in the browser. Despite this, test coverage is an important
|
||||
tool to use during development.
|
||||
|
||||
.. note::
|
||||
This is being introduced late in the tutorial, but in your future
|
||||
projects you should test as you develop.
|
||||
|
||||
You'll use `pytest`_ and `coverage`_ to test and measure your code.
|
||||
Install them both:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ pip install pytest coverage
|
||||
|
||||
.. _pytest: https://pytest.readthedocs.io/
|
||||
.. _coverage: https://coverage.readthedocs.io/
|
||||
|
||||
|
||||
Setup and Fixtures
|
||||
------------------
|
||||
|
||||
The test code is located in the ``tests`` directory. This directory is
|
||||
*next to* the ``flaskr`` package, not inside it. The
|
||||
``tests/conftest.py`` file contains setup functions called *fixtures*
|
||||
that each test will use. Tests are in Python modules that start with
|
||||
``test_``, and each test function in those modules also starts with
|
||||
``test_``.
|
||||
|
||||
Each test will create a new temporary database file and populate some
|
||||
data that will be used in the tests. Write a SQL file to insert that
|
||||
data.
|
||||
|
||||
.. code-block:: sql
|
||||
:caption: ``tests/data.sql``
|
||||
|
||||
INSERT INTO user (username, password)
|
||||
VALUES
|
||||
('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'),
|
||||
('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79');
|
||||
|
||||
INSERT INTO post (title, body, author_id, created)
|
||||
VALUES
|
||||
('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00');
|
||||
|
||||
The ``app`` fixture will call the factory and pass ``test_config`` to
|
||||
configure the application and database for testing instead of using your
|
||||
local development configuration.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/conftest.py``
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from flaskr import create_app
|
||||
from flaskr.db import get_db, init_db
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f:
|
||||
_data_sql = f.read().decode('utf8')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
db_fd, db_path = tempfile.mkstemp()
|
||||
|
||||
app = create_app({
|
||||
'TESTING': True,
|
||||
'DATABASE': db_path,
|
||||
})
|
||||
|
||||
with app.app_context():
|
||||
init_db()
|
||||
get_db().executescript(_data_sql)
|
||||
|
||||
yield app
|
||||
|
||||
os.close(db_fd)
|
||||
os.unlink(db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
return app.test_client()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner(app):
|
||||
return app.test_cli_runner()
|
||||
|
||||
:func:`tempfile.mkstemp` creates and opens a temporary file, returning
|
||||
the file descriptor and the path to it. The ``DATABASE`` path is
|
||||
overridden so it points to this temporary path instead of the instance
|
||||
folder. After setting the path, the database tables are created and the
|
||||
test data is inserted. After the test is over, the temporary file is
|
||||
closed and removed.
|
||||
|
||||
:data:`TESTING` tells Flask that the app is in test mode. Flask changes
|
||||
some internal behavior so it's easier to test, and other extensions can
|
||||
also use the flag to make testing them easier.
|
||||
|
||||
The ``client`` fixture calls
|
||||
:meth:`app.test_client() <Flask.test_client>` with the application
|
||||
object created by the ``app`` fixture. Tests will use the client to make
|
||||
requests to the application without running the server.
|
||||
|
||||
The ``runner`` fixture is similar to ``client``.
|
||||
:meth:`app.test_cli_runner() <Flask.test_cli_runner>` creates a runner
|
||||
that can call the Click commands registered with the application.
|
||||
|
||||
Pytest uses fixtures by matching their function names with the names
|
||||
of arguments in the test functions. For example, the ``test_hello``
|
||||
function you'll write next takes a ``client`` argument. Pytest matches
|
||||
that with the ``client`` fixture function, calls it, and passes the
|
||||
returned value to the test function.
|
||||
|
||||
|
||||
Factory
|
||||
-------
|
||||
|
||||
There's not much to test about the factory itself. Most of the code will
|
||||
be executed for each test already, so if something fails the other tests
|
||||
will notice.
|
||||
|
||||
The only behavior that can change is passing test config. If config is
|
||||
not passed, there should be some default configuration, otherwise the
|
||||
configuration should be overridden.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_factory.py``
|
||||
|
||||
from flaskr import create_app
|
||||
|
||||
|
||||
def test_config():
|
||||
assert not create_app().testing
|
||||
assert create_app({'TESTING': True}).testing
|
||||
|
||||
|
||||
def test_hello(client):
|
||||
response = client.get('/hello')
|
||||
assert response.data == b'Hello, World!'
|
||||
|
||||
You added the ``hello`` route as an example when writing the factory at
|
||||
the beginning of the tutorial. It returns "Hello, World!", so the test
|
||||
checks that the response data matches.
|
||||
|
||||
|
||||
Database
|
||||
--------
|
||||
|
||||
Within an application context, ``get_db`` should return the same
|
||||
connection each time it's called. After the context, the connection
|
||||
should be closed.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_db.py``
|
||||
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
from flaskr.db import get_db
|
||||
|
||||
|
||||
def test_get_close_db(app):
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
assert db is get_db()
|
||||
|
||||
with pytest.raises(sqlite3.ProgrammingError) as e:
|
||||
db.execute('SELECT 1')
|
||||
|
||||
assert 'closed' in str(e.value)
|
||||
|
||||
The ``init-db`` command should call the ``init_db`` function and output
|
||||
a message.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_db.py``
|
||||
|
||||
def test_init_db_command(runner, monkeypatch):
|
||||
class Recorder(object):
|
||||
called = False
|
||||
|
||||
def fake_init_db():
|
||||
Recorder.called = True
|
||||
|
||||
monkeypatch.setattr('flaskr.db.init_db', fake_init_db)
|
||||
result = runner.invoke(args=['init-db'])
|
||||
assert 'Initialized' in result.output
|
||||
assert Recorder.called
|
||||
|
||||
This test uses Pytest's ``monkeypatch`` fixture to replace the
|
||||
``init_db`` function with one that records that it's been called. The
|
||||
``runner`` fixture you wrote above is used to call the ``init-db``
|
||||
command by name.
|
||||
|
||||
|
||||
Authentication
|
||||
--------------
|
||||
|
||||
For most of the views, a user needs to be logged in. The easiest way to
|
||||
do this in tests is to make a ``POST`` request to the ``login`` view
|
||||
with the client. Rather than writing that out every time, you can write
|
||||
a class with methods to do that, and use a fixture to pass it the client
|
||||
for each test.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/conftest.py``
|
||||
|
||||
class AuthActions(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def login(self, username='test', password='test'):
|
||||
return self._client.post(
|
||||
'/auth/login',
|
||||
data={'username': username, 'password': password}
|
||||
)
|
||||
|
||||
def logout(self):
|
||||
return self._client.get('/auth/logout')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auth(client):
|
||||
return AuthActions(client)
|
||||
|
||||
With the ``auth`` fixture, you can call ``auth.login()`` in a test to
|
||||
log in as the ``test`` user, which was inserted as part of the test
|
||||
data in the ``app`` fixture.
|
||||
|
||||
The ``register`` view should render successfully on ``GET``. On ``POST``
|
||||
with valid form data, it should redirect to the login URL and the user's
|
||||
data should be in the database. Invalid data should display error
|
||||
messages.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_auth.py``
|
||||
|
||||
import pytest
|
||||
from flask import g, session
|
||||
from flaskr.db import get_db
|
||||
|
||||
|
||||
def test_register(client, app):
|
||||
assert client.get('/auth/register').status_code == 200
|
||||
response = client.post(
|
||||
'/auth/register', data={'username': 'a', 'password': 'a'}
|
||||
)
|
||||
assert response.headers["Location"] == "/auth/login"
|
||||
|
||||
with app.app_context():
|
||||
assert get_db().execute(
|
||||
"SELECT * FROM user WHERE username = 'a'",
|
||||
).fetchone() is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('username', 'password', 'message'), (
|
||||
('', '', b'Username is required.'),
|
||||
('a', '', b'Password is required.'),
|
||||
('test', 'test', b'already registered'),
|
||||
))
|
||||
def test_register_validate_input(client, username, password, message):
|
||||
response = client.post(
|
||||
'/auth/register',
|
||||
data={'username': username, 'password': password}
|
||||
)
|
||||
assert message in response.data
|
||||
|
||||
:meth:`client.get() <werkzeug.test.Client.get>` makes a ``GET`` request
|
||||
and returns the :class:`Response` object returned by Flask. Similarly,
|
||||
:meth:`client.post() <werkzeug.test.Client.post>` makes a ``POST``
|
||||
request, converting the ``data`` dict into form data.
|
||||
|
||||
To test that the page renders successfully, a simple request is made and
|
||||
checked for a ``200 OK`` :attr:`~Response.status_code`. If
|
||||
rendering failed, Flask would return a ``500 Internal Server Error``
|
||||
code.
|
||||
|
||||
:attr:`~Response.headers` will have a ``Location`` header with the login
|
||||
URL when the register view redirects to the login view.
|
||||
|
||||
:attr:`~Response.data` contains the body of the response as bytes. If
|
||||
you expect a certain value to render on the page, check that it's in
|
||||
``data``. Bytes must be compared to bytes. If you want to compare text,
|
||||
use :meth:`get_data(as_text=True) <werkzeug.wrappers.Response.get_data>`
|
||||
instead.
|
||||
|
||||
``pytest.mark.parametrize`` tells Pytest to run the same test function
|
||||
with different arguments. You use it here to test different invalid
|
||||
input and error messages without writing the same code three times.
|
||||
|
||||
The tests for the ``login`` view are very similar to those for
|
||||
``register``. Rather than testing the data in the database,
|
||||
:data:`.session` should have ``user_id`` set after logging in.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_auth.py``
|
||||
|
||||
def test_login(client, auth):
|
||||
assert client.get('/auth/login').status_code == 200
|
||||
response = auth.login()
|
||||
assert response.headers["Location"] == "/"
|
||||
|
||||
with client:
|
||||
client.get('/')
|
||||
assert session['user_id'] == 1
|
||||
assert g.user['username'] == 'test'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('username', 'password', 'message'), (
|
||||
('a', 'test', b'Incorrect username.'),
|
||||
('test', 'a', b'Incorrect password.'),
|
||||
))
|
||||
def test_login_validate_input(auth, username, password, message):
|
||||
response = auth.login(username, password)
|
||||
assert message in response.data
|
||||
|
||||
Using ``client`` in a ``with`` block allows accessing context variables
|
||||
such as :data:`.session` after the response is returned. Normally,
|
||||
accessing ``session`` outside of a request would raise an error.
|
||||
|
||||
Testing ``logout`` is the opposite of ``login``. :data:`.session` should
|
||||
not contain ``user_id`` after logging out.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_auth.py``
|
||||
|
||||
def test_logout(client, auth):
|
||||
auth.login()
|
||||
|
||||
with client:
|
||||
auth.logout()
|
||||
assert 'user_id' not in session
|
||||
|
||||
|
||||
Blog
|
||||
----
|
||||
|
||||
All the blog views use the ``auth`` fixture you wrote earlier. Call
|
||||
``auth.login()`` and subsequent requests from the client will be logged
|
||||
in as the ``test`` user.
|
||||
|
||||
The ``index`` view should display information about the post that was
|
||||
added with the test data. When logged in as the author, there should be
|
||||
a link to edit the post.
|
||||
|
||||
You can also test some more authentication behavior while testing the
|
||||
``index`` view. When not logged in, each page shows links to log in or
|
||||
register. When logged in, there's a link to log out.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_blog.py``
|
||||
|
||||
import pytest
|
||||
from flaskr.db import get_db
|
||||
|
||||
|
||||
def test_index(client, auth):
|
||||
response = client.get('/')
|
||||
assert b"Log In" in response.data
|
||||
assert b"Register" in response.data
|
||||
|
||||
auth.login()
|
||||
response = client.get('/')
|
||||
assert b'Log Out' in response.data
|
||||
assert b'test title' in response.data
|
||||
assert b'by test on 2018-01-01' in response.data
|
||||
assert b'test\nbody' in response.data
|
||||
assert b'href="/1/update"' in response.data
|
||||
|
||||
A user must be logged in to access the ``create``, ``update``, and
|
||||
``delete`` views. The logged in user must be the author of the post to
|
||||
access ``update`` and ``delete``, otherwise a ``403 Forbidden`` status
|
||||
is returned. If a ``post`` with the given ``id`` doesn't exist,
|
||||
``update`` and ``delete`` should return ``404 Not Found``.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_blog.py``
|
||||
|
||||
@pytest.mark.parametrize('path', (
|
||||
'/create',
|
||||
'/1/update',
|
||||
'/1/delete',
|
||||
))
|
||||
def test_login_required(client, path):
|
||||
response = client.post(path)
|
||||
assert response.headers["Location"] == "/auth/login"
|
||||
|
||||
|
||||
def test_author_required(app, client, auth):
|
||||
# change the post author to another user
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
db.execute('UPDATE post SET author_id = 2 WHERE id = 1')
|
||||
db.commit()
|
||||
|
||||
auth.login()
|
||||
# current user can't modify other user's post
|
||||
assert client.post('/1/update').status_code == 403
|
||||
assert client.post('/1/delete').status_code == 403
|
||||
# current user doesn't see edit link
|
||||
assert b'href="/1/update"' not in client.get('/').data
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path', (
|
||||
'/2/update',
|
||||
'/2/delete',
|
||||
))
|
||||
def test_exists_required(client, auth, path):
|
||||
auth.login()
|
||||
assert client.post(path).status_code == 404
|
||||
|
||||
The ``create`` and ``update`` views should render and return a
|
||||
``200 OK`` status for a ``GET`` request. When valid data is sent in a
|
||||
``POST`` request, ``create`` should insert the new post data into the
|
||||
database, and ``update`` should modify the existing data. Both pages
|
||||
should show an error message on invalid data.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_blog.py``
|
||||
|
||||
def test_create(client, auth, app):
|
||||
auth.login()
|
||||
assert client.get('/create').status_code == 200
|
||||
client.post('/create', data={'title': 'created', 'body': ''})
|
||||
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0]
|
||||
assert count == 2
|
||||
|
||||
|
||||
def test_update(client, auth, app):
|
||||
auth.login()
|
||||
assert client.get('/1/update').status_code == 200
|
||||
client.post('/1/update', data={'title': 'updated', 'body': ''})
|
||||
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
post = db.execute('SELECT * FROM post WHERE id = 1').fetchone()
|
||||
assert post['title'] == 'updated'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path', (
|
||||
'/create',
|
||||
'/1/update',
|
||||
))
|
||||
def test_create_update_validate(client, auth, path):
|
||||
auth.login()
|
||||
response = client.post(path, data={'title': '', 'body': ''})
|
||||
assert b'Title is required.' in response.data
|
||||
|
||||
The ``delete`` view should redirect to the index URL and the post should
|
||||
no longer exist in the database.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``tests/test_blog.py``
|
||||
|
||||
def test_delete(client, auth, app):
|
||||
auth.login()
|
||||
response = client.post('/1/delete')
|
||||
assert response.headers["Location"] == "/"
|
||||
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
post = db.execute('SELECT * FROM post WHERE id = 1').fetchone()
|
||||
assert post is None
|
||||
|
||||
|
||||
Running the Tests
|
||||
-----------------
|
||||
|
||||
Some extra configuration, which is not required but makes running tests with coverage
|
||||
less verbose, can be added to the project's ``pyproject.toml`` file.
|
||||
|
||||
.. code-block:: toml
|
||||
:caption: ``pyproject.toml``
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
source = ["flaskr"]
|
||||
|
||||
To run the tests, use the ``pytest`` command. It will find and run all
|
||||
the test functions you've written.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ pytest
|
||||
|
||||
========================= test session starts ==========================
|
||||
platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
|
||||
rootdir: /home/user/Projects/flask-tutorial
|
||||
collected 23 items
|
||||
|
||||
tests/test_auth.py ........ [ 34%]
|
||||
tests/test_blog.py ............ [ 86%]
|
||||
tests/test_db.py .. [ 95%]
|
||||
tests/test_factory.py .. [100%]
|
||||
|
||||
====================== 24 passed in 0.64 seconds =======================
|
||||
|
||||
If any tests fail, pytest will show the error that was raised. You can
|
||||
run ``pytest -v`` to get a list of each test function rather than dots.
|
||||
|
||||
To measure the code coverage of your tests, use the ``coverage`` command
|
||||
to run pytest instead of running it directly.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ coverage run -m pytest
|
||||
|
||||
You can either view a simple coverage report in the terminal:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ coverage report
|
||||
|
||||
Name Stmts Miss Branch BrPart Cover
|
||||
------------------------------------------------------
|
||||
flaskr/__init__.py 21 0 2 0 100%
|
||||
flaskr/auth.py 54 0 22 0 100%
|
||||
flaskr/blog.py 54 0 16 0 100%
|
||||
flaskr/db.py 24 0 4 0 100%
|
||||
------------------------------------------------------
|
||||
TOTAL 153 0 44 0 100%
|
||||
|
||||
An HTML report allows you to see which lines were covered in each file:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ coverage html
|
||||
|
||||
This generates files in the ``htmlcov`` directory. Open
|
||||
``htmlcov/index.html`` in your browser to see the report.
|
||||
|
||||
Continue to :doc:`deploy`.
|
||||
@ -0,0 +1,305 @@
|
||||
.. currentmodule:: flask
|
||||
|
||||
Blueprints and Views
|
||||
====================
|
||||
|
||||
A view function is the code you write to respond to requests to your
|
||||
application. Flask uses patterns to match the incoming request URL to
|
||||
the view that should handle it. The view returns data that Flask turns
|
||||
into an outgoing response. Flask can also go the other direction and
|
||||
generate a URL to a view based on its name and arguments.
|
||||
|
||||
|
||||
Create a Blueprint
|
||||
------------------
|
||||
|
||||
A :class:`Blueprint` is a way to organize a group of related views and
|
||||
other code. Rather than registering views and other code directly with
|
||||
an application, they are registered with a blueprint. Then the blueprint
|
||||
is registered with the application when it is available in the factory
|
||||
function.
|
||||
|
||||
Flaskr will have two blueprints, one for authentication functions and
|
||||
one for the blog posts functions. The code for each blueprint will go
|
||||
in a separate module. Since the blog needs to know about authentication,
|
||||
you'll write the authentication one first.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/auth.py``
|
||||
|
||||
import functools
|
||||
|
||||
from flask import (
|
||||
Blueprint, flash, g, redirect, render_template, request, session, url_for
|
||||
)
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
from flaskr.db import get_db
|
||||
|
||||
bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
|
||||
This creates a :class:`Blueprint` named ``'auth'``. Like the application
|
||||
object, the blueprint needs to know where it's defined, so ``__name__``
|
||||
is passed as the second argument. The ``url_prefix`` will be prepended
|
||||
to all the URLs associated with the blueprint.
|
||||
|
||||
Import and register the blueprint from the factory using
|
||||
:meth:`app.register_blueprint() <Flask.register_blueprint>`. Place the
|
||||
new code at the end of the factory function before returning the app.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/__init__.py``
|
||||
|
||||
def create_app():
|
||||
app = ...
|
||||
# existing code omitted
|
||||
|
||||
from . import auth
|
||||
app.register_blueprint(auth.bp)
|
||||
|
||||
return app
|
||||
|
||||
The authentication blueprint will have views to register new users and
|
||||
to log in and log out.
|
||||
|
||||
|
||||
The First View: Register
|
||||
------------------------
|
||||
|
||||
When the user visits the ``/auth/register`` URL, the ``register`` view
|
||||
will return `HTML`_ with a form for them to fill out. When they submit
|
||||
the form, it will validate their input and either show the form again
|
||||
with an error message or create the new user and go to the login page.
|
||||
|
||||
.. _HTML: https://developer.mozilla.org/docs/Web/HTML
|
||||
|
||||
For now you will just write the view code. On the next page, you'll
|
||||
write templates to generate the HTML form.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/auth.py``
|
||||
|
||||
@bp.route('/register', methods=('GET', 'POST'))
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
db = get_db()
|
||||
error = None
|
||||
|
||||
if not username:
|
||||
error = 'Username is required.'
|
||||
elif not password:
|
||||
error = 'Password is required.'
|
||||
|
||||
if error is None:
|
||||
try:
|
||||
db.execute(
|
||||
"INSERT INTO user (username, password) VALUES (?, ?)",
|
||||
(username, generate_password_hash(password)),
|
||||
)
|
||||
db.commit()
|
||||
except db.IntegrityError:
|
||||
error = f"User {username} is already registered."
|
||||
else:
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
flash(error)
|
||||
|
||||
return render_template('auth/register.html')
|
||||
|
||||
Here's what the ``register`` view function is doing:
|
||||
|
||||
#. :meth:`@bp.route <Blueprint.route>` associates the URL ``/register``
|
||||
with the ``register`` view function. When Flask receives a request
|
||||
to ``/auth/register``, it will call the ``register`` view and use
|
||||
the return value as the response.
|
||||
|
||||
#. If the user submitted the form,
|
||||
:attr:`request.method <Request.method>` will be ``'POST'``. In this
|
||||
case, start validating the input.
|
||||
|
||||
#. :attr:`request.form <Request.form>` is a special type of
|
||||
:class:`dict` mapping submitted form keys and values. The user will
|
||||
input their ``username`` and ``password``.
|
||||
|
||||
#. Validate that ``username`` and ``password`` are not empty.
|
||||
|
||||
#. If validation succeeds, insert the new user data into the database.
|
||||
|
||||
- :meth:`db.execute <sqlite3.Connection.execute>` takes a SQL
|
||||
query with ``?`` placeholders for any user input, and a tuple of
|
||||
values to replace the placeholders with. The database library
|
||||
will take care of escaping the values so you are not vulnerable
|
||||
to a *SQL injection attack*.
|
||||
|
||||
- For security, passwords should never be stored in the database
|
||||
directly. Instead,
|
||||
:func:`~werkzeug.security.generate_password_hash` is used to
|
||||
securely hash the password, and that hash is stored. Since this
|
||||
query modifies data,
|
||||
:meth:`db.commit() <sqlite3.Connection.commit>` needs to be
|
||||
called afterwards to save the changes.
|
||||
|
||||
- An :exc:`sqlite3.IntegrityError` will occur if the username
|
||||
already exists, which should be shown to the user as another
|
||||
validation error.
|
||||
|
||||
#. After storing the user, they are redirected to the login page.
|
||||
:func:`url_for` generates the URL for the login view based on its
|
||||
name. This is preferable to writing the URL directly as it allows
|
||||
you to change the URL later without changing all code that links to
|
||||
it. :func:`redirect` generates a redirect response to the generated
|
||||
URL.
|
||||
|
||||
#. If validation fails, the error is shown to the user. :func:`flash`
|
||||
stores messages that can be retrieved when rendering the template.
|
||||
|
||||
#. When the user initially navigates to ``auth/register``, or
|
||||
there was a validation error, an HTML page with the registration
|
||||
form should be shown. :func:`render_template` will render a template
|
||||
containing the HTML, which you'll write in the next step of the
|
||||
tutorial.
|
||||
|
||||
|
||||
Login
|
||||
-----
|
||||
|
||||
This view follows the same pattern as the ``register`` view above.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/auth.py``
|
||||
|
||||
@bp.route('/login', methods=('GET', 'POST'))
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
db = get_db()
|
||||
error = None
|
||||
user = db.execute(
|
||||
'SELECT * FROM user WHERE username = ?', (username,)
|
||||
).fetchone()
|
||||
|
||||
if user is None:
|
||||
error = 'Incorrect username.'
|
||||
elif not check_password_hash(user['password'], password):
|
||||
error = 'Incorrect password.'
|
||||
|
||||
if error is None:
|
||||
session.clear()
|
||||
session['user_id'] = user['id']
|
||||
return redirect(url_for('index'))
|
||||
|
||||
flash(error)
|
||||
|
||||
return render_template('auth/login.html')
|
||||
|
||||
There are a few differences from the ``register`` view:
|
||||
|
||||
#. The user is queried first and stored in a variable for later use.
|
||||
|
||||
:meth:`~sqlite3.Cursor.fetchone` returns one row from the query.
|
||||
If the query returned no results, it returns ``None``. Later,
|
||||
:meth:`~sqlite3.Cursor.fetchall` will be used, which returns a list
|
||||
of all results.
|
||||
|
||||
#. :func:`~werkzeug.security.check_password_hash` hashes the submitted
|
||||
password in the same way as the stored hash and securely compares
|
||||
them. If they match, the password is valid.
|
||||
|
||||
#. :data:`.session` is a :class:`dict` that stores data across requests.
|
||||
When validation succeeds, the user's ``id`` is stored in a new
|
||||
session. The data is stored in a *cookie* that is sent to the
|
||||
browser, and the browser then sends it back with subsequent requests.
|
||||
Flask securely *signs* the data so that it can't be tampered with.
|
||||
|
||||
Now that the user's ``id`` is stored in the :data:`.session`, it will be
|
||||
available on subsequent requests. At the beginning of each request, if
|
||||
a user is logged in their information should be loaded and made
|
||||
available to other views.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/auth.py``
|
||||
|
||||
@bp.before_app_request
|
||||
def load_logged_in_user():
|
||||
user_id = session.get('user_id')
|
||||
|
||||
if user_id is None:
|
||||
g.user = None
|
||||
else:
|
||||
g.user = get_db().execute(
|
||||
'SELECT * FROM user WHERE id = ?', (user_id,)
|
||||
).fetchone()
|
||||
|
||||
:meth:`bp.before_app_request() <Blueprint.before_app_request>` registers
|
||||
a function that runs before the view function, no matter what URL is
|
||||
requested. ``load_logged_in_user`` checks if a user id is stored in the
|
||||
:data:`.session` and gets that user's data from the database, storing it
|
||||
on :data:`g.user <g>`, which lasts for the length of the request. If
|
||||
there is no user id, or if the id doesn't exist, ``g.user`` will be
|
||||
``None``.
|
||||
|
||||
|
||||
Logout
|
||||
------
|
||||
|
||||
To log out, you need to remove the user id from the :data:`.session`.
|
||||
Then ``load_logged_in_user`` won't load a user on subsequent requests.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/auth.py``
|
||||
|
||||
@bp.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
Require Authentication in Other Views
|
||||
-------------------------------------
|
||||
|
||||
Creating, editing, and deleting blog posts will require a user to be
|
||||
logged in. A *decorator* can be used to check this for each view it's
|
||||
applied to.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``flaskr/auth.py``
|
||||
|
||||
def login_required(view):
|
||||
@functools.wraps(view)
|
||||
def wrapped_view(**kwargs):
|
||||
if g.user is None:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return view(**kwargs)
|
||||
|
||||
return wrapped_view
|
||||
|
||||
This decorator returns a new view function that wraps the original view
|
||||
it's applied to. The new function checks if a user is loaded and
|
||||
redirects to the login page otherwise. If a user is loaded the original
|
||||
view is called and continues normally. You'll use this decorator when
|
||||
writing the blog views.
|
||||
|
||||
Endpoints and URLs
|
||||
------------------
|
||||
|
||||
The :func:`url_for` function generates the URL to a view based on a name
|
||||
and arguments. The name associated with a view is also called the
|
||||
*endpoint*, and by default it's the same as the name of the view
|
||||
function.
|
||||
|
||||
For example, the ``hello()`` view that was added to the app
|
||||
factory earlier in the tutorial has the name ``'hello'`` and can be
|
||||
linked to with ``url_for('hello')``. If it took an argument, which
|
||||
you'll see later, it would be linked to using
|
||||
``url_for('hello', who='World')``.
|
||||
|
||||
When using a blueprint, the name of the blueprint is prepended to the
|
||||
name of the function, so the endpoint for the ``login`` function you
|
||||
wrote above is ``'auth.login'`` because you added it to the ``'auth'``
|
||||
blueprint.
|
||||
|
||||
Continue to :doc:`templates`.
|
||||
@ -0,0 +1,324 @@
|
||||
Class-based Views
|
||||
=================
|
||||
|
||||
.. currentmodule:: flask.views
|
||||
|
||||
This page introduces using the :class:`View` and :class:`MethodView`
|
||||
classes to write class-based views.
|
||||
|
||||
A class-based view is a class that acts as a view function. Because it
|
||||
is a class, different instances of the class can be created with
|
||||
different arguments, to change the behavior of the view. This is also
|
||||
known as generic, reusable, or pluggable views.
|
||||
|
||||
An example of where this is useful is defining a class that creates an
|
||||
API based on the database model it is initialized with.
|
||||
|
||||
For more complex API behavior and customization, look into the various
|
||||
API extensions for Flask.
|
||||
|
||||
|
||||
Basic Reusable View
|
||||
-------------------
|
||||
|
||||
Let's walk through an example converting a view function to a view
|
||||
class. We start with a view function that queries a list of users then
|
||||
renders a template to show the list.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route("/users/")
|
||||
def user_list():
|
||||
users = User.query.all()
|
||||
return render_template("users.html", users=users)
|
||||
|
||||
This works for the user model, but let's say you also had more models
|
||||
that needed list pages. You'd need to write another view function for
|
||||
each model, even though the only thing that would change is the model
|
||||
and template name.
|
||||
|
||||
Instead, you can write a :class:`View` subclass that will query a model
|
||||
and render a template. As the first step, we'll convert the view to a
|
||||
class without any customization.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask.views import View
|
||||
|
||||
class UserList(View):
|
||||
def dispatch_request(self):
|
||||
users = User.query.all()
|
||||
return render_template("users.html", objects=users)
|
||||
|
||||
app.add_url_rule("/users/", view_func=UserList.as_view("user_list"))
|
||||
|
||||
The :meth:`View.dispatch_request` method is the equivalent of the view
|
||||
function. Calling :meth:`View.as_view` method will create a view
|
||||
function that can be registered on the app with its
|
||||
:meth:`~flask.Flask.add_url_rule` method. The first argument to
|
||||
``as_view`` is the name to use to refer to the view with
|
||||
:func:`~flask.url_for`.
|
||||
|
||||
.. note::
|
||||
|
||||
You can't decorate the class with ``@app.route()`` the way you'd
|
||||
do with a basic view function.
|
||||
|
||||
Next, we need to be able to register the same view class for different
|
||||
models and templates, to make it more useful than the original function.
|
||||
The class will take two arguments, the model and template, and store
|
||||
them on ``self``. Then ``dispatch_request`` can reference these instead
|
||||
of hard-coded values.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ListView(View):
|
||||
def __init__(self, model, template):
|
||||
self.model = model
|
||||
self.template = template
|
||||
|
||||
def dispatch_request(self):
|
||||
items = self.model.query.all()
|
||||
return render_template(self.template, items=items)
|
||||
|
||||
Remember, we create the view function with ``View.as_view()`` instead of
|
||||
creating the class directly. Any extra arguments passed to ``as_view``
|
||||
are then passed when creating the class. Now we can register the same
|
||||
view to handle multiple models.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.add_url_rule(
|
||||
"/users/",
|
||||
view_func=ListView.as_view("user_list", User, "users.html"),
|
||||
)
|
||||
app.add_url_rule(
|
||||
"/stories/",
|
||||
view_func=ListView.as_view("story_list", Story, "stories.html"),
|
||||
)
|
||||
|
||||
|
||||
URL Variables
|
||||
-------------
|
||||
|
||||
Any variables captured by the URL are passed as keyword arguments to the
|
||||
``dispatch_request`` method, as they would be for a regular view
|
||||
function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class DetailView(View):
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.template = f"{model.__name__.lower()}/detail.html"
|
||||
|
||||
def dispatch_request(self, id)
|
||||
item = self.model.query.get_or_404(id)
|
||||
return render_template(self.template, item=item)
|
||||
|
||||
app.add_url_rule(
|
||||
"/users/<int:id>",
|
||||
view_func=DetailView.as_view("user_detail", User)
|
||||
)
|
||||
|
||||
|
||||
View Lifetime and ``self``
|
||||
--------------------------
|
||||
|
||||
By default, a new instance of the view class is created every time a
|
||||
request is handled. This means that it is safe to write other data to
|
||||
``self`` during the request, since the next request will not see it,
|
||||
unlike other forms of global state.
|
||||
|
||||
However, if your view class needs to do a lot of complex initialization,
|
||||
doing it for every request is unnecessary and can be inefficient. To
|
||||
avoid this, set :attr:`View.init_every_request` to ``False``, which will
|
||||
only create one instance of the class and use it for every request. In
|
||||
this case, writing to ``self`` is not safe. If you need to store data
|
||||
during the request, use :data:`~flask.g` instead.
|
||||
|
||||
In the ``ListView`` example, nothing writes to ``self`` during the
|
||||
request, so it is more efficient to create a single instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ListView(View):
|
||||
init_every_request = False
|
||||
|
||||
def __init__(self, model, template):
|
||||
self.model = model
|
||||
self.template = template
|
||||
|
||||
def dispatch_request(self):
|
||||
items = self.model.query.all()
|
||||
return render_template(self.template, items=items)
|
||||
|
||||
Different instances will still be created each for each ``as_view``
|
||||
call, but not for each request to those views.
|
||||
|
||||
|
||||
View Decorators
|
||||
---------------
|
||||
|
||||
The view class itself is not the view function. View decorators need to
|
||||
be applied to the view function returned by ``as_view``, not the class
|
||||
itself. Set :attr:`View.decorators` to a list of decorators to apply.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class UserList(View):
|
||||
decorators = [cache(minutes=2), login_required]
|
||||
|
||||
app.add_url_rule('/users/', view_func=UserList.as_view())
|
||||
|
||||
If you didn't set ``decorators``, you could apply them manually instead.
|
||||
This is equivalent to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
view = UserList.as_view("users_list")
|
||||
view = cache(minutes=2)(view)
|
||||
view = login_required(view)
|
||||
app.add_url_rule('/users/', view_func=view)
|
||||
|
||||
Keep in mind that order matters. If you're used to ``@decorator`` style,
|
||||
this is equivalent to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route("/users/")
|
||||
@login_required
|
||||
@cache(minutes=2)
|
||||
def user_list():
|
||||
...
|
||||
|
||||
|
||||
Method Hints
|
||||
------------
|
||||
|
||||
A common pattern is to register a view with ``methods=["GET", "POST"]``,
|
||||
then check ``request.method == "POST"`` to decide what to do. Setting
|
||||
:attr:`View.methods` is equivalent to passing the list of methods to
|
||||
``add_url_rule`` or ``route``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyView(View):
|
||||
methods = ["GET", "POST"]
|
||||
|
||||
def dispatch_request(self):
|
||||
if request.method == "POST":
|
||||
...
|
||||
...
|
||||
|
||||
app.add_url_rule('/my-view', view_func=MyView.as_view('my-view'))
|
||||
|
||||
This is equivalent to the following, except further subclasses can
|
||||
inherit or change the methods.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.add_url_rule(
|
||||
"/my-view",
|
||||
view_func=MyView.as_view("my-view"),
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
|
||||
|
||||
Method Dispatching and APIs
|
||||
---------------------------
|
||||
|
||||
For APIs it can be helpful to use a different function for each HTTP
|
||||
method. :class:`MethodView` extends the basic :class:`View` to dispatch
|
||||
to different methods of the class based on the request method. Each HTTP
|
||||
method maps to a method of the class with the same (lowercase) name.
|
||||
|
||||
:class:`MethodView` automatically sets :attr:`View.methods` based on the
|
||||
methods defined by the class. It even knows how to handle subclasses
|
||||
that override or define other methods.
|
||||
|
||||
We can make a generic ``ItemAPI`` class that provides get (detail),
|
||||
patch (edit), and delete methods for a given model. A ``GroupAPI`` can
|
||||
provide get (list) and post (create) methods.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask.views import MethodView
|
||||
|
||||
class ItemAPI(MethodView):
|
||||
init_every_request = False
|
||||
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.validator = generate_validator(model)
|
||||
|
||||
def _get_item(self, id):
|
||||
return self.model.query.get_or_404(id)
|
||||
|
||||
def get(self, id):
|
||||
item = self._get_item(id)
|
||||
return jsonify(item.to_json())
|
||||
|
||||
def patch(self, id):
|
||||
item = self._get_item(id)
|
||||
errors = self.validator.validate(item, request.json)
|
||||
|
||||
if errors:
|
||||
return jsonify(errors), 400
|
||||
|
||||
item.update_from_json(request.json)
|
||||
db.session.commit()
|
||||
return jsonify(item.to_json())
|
||||
|
||||
def delete(self, id):
|
||||
item = self._get_item(id)
|
||||
db.session.delete(item)
|
||||
db.session.commit()
|
||||
return "", 204
|
||||
|
||||
class GroupAPI(MethodView):
|
||||
init_every_request = False
|
||||
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.validator = generate_validator(model, create=True)
|
||||
|
||||
def get(self):
|
||||
items = self.model.query.all()
|
||||
return jsonify([item.to_json() for item in items])
|
||||
|
||||
def post(self):
|
||||
errors = self.validator.validate(request.json)
|
||||
|
||||
if errors:
|
||||
return jsonify(errors), 400
|
||||
|
||||
db.session.add(self.model.from_json(request.json))
|
||||
db.session.commit()
|
||||
return jsonify(item.to_json())
|
||||
|
||||
def register_api(app, model, name):
|
||||
item = ItemAPI.as_view(f"{name}-item", model)
|
||||
group = GroupAPI.as_view(f"{name}-group", model)
|
||||
app.add_url_rule(f"/{name}/<int:id>", view_func=item)
|
||||
app.add_url_rule(f"/{name}/", view_func=group)
|
||||
|
||||
register_api(app, User, "users")
|
||||
register_api(app, Story, "stories")
|
||||
|
||||
This produces the following views, a standard REST API!
|
||||
|
||||
================= ========== ===================
|
||||
URL Method Description
|
||||
----------------- ---------- -------------------
|
||||
``/users/`` ``GET`` List all users
|
||||
``/users/`` ``POST`` Create a new user
|
||||
``/users/<id>`` ``GET`` Show a single user
|
||||
``/users/<id>`` ``PATCH`` Update a user
|
||||
``/users/<id>`` ``DELETE`` Delete a user
|
||||
``/stories/`` ``GET`` List all stories
|
||||
``/stories/`` ``POST`` Create a new story
|
||||
``/stories/<id>`` ``GET`` Show a single story
|
||||
``/stories/<id>`` ``PATCH`` Update a story
|
||||
``/stories/<id>`` ``DELETE`` Delete a story
|
||||
================= ========== ===================
|
||||
@ -0,0 +1,316 @@
|
||||
Security Considerations
|
||||
=======================
|
||||
|
||||
Web applications face many types of potential security problems, and it can be
|
||||
hard to get everything right, or even to know what "right" is in general. Flask
|
||||
tries to solve a few of these things by default, but there are other parts you
|
||||
may have to take care of yourself. Many of these solutions are tradeoffs, and
|
||||
will depend on each application's specific needs and threat model. Many hosting
|
||||
platforms may take care of certain types of problems without the need for the
|
||||
Flask application to handle them.
|
||||
|
||||
Resource Use
|
||||
------------
|
||||
|
||||
A common category of attacks is "Denial of Service" (DoS or DDoS). This is a
|
||||
very broad category, and different variants target different layers in a
|
||||
deployed application. In general, something is done to increase how much
|
||||
processing time or memory is used to handle each request, to the point where
|
||||
there are not enough resources to handle legitimate requests.
|
||||
|
||||
Flask provides a few configuration options to handle resource use. They can
|
||||
also be set on individual requests to customize only that request. The
|
||||
documentation for each goes into more detail.
|
||||
|
||||
- :data:`MAX_CONTENT_LENGTH` or :attr:`.Request.max_content_length` controls
|
||||
how much data will be read from a request. It is not set by default,
|
||||
although it will still block truly unlimited streams unless the WSGI server
|
||||
indicates support.
|
||||
- :data:`MAX_FORM_MEMORY_SIZE` or :attr:`.Request.max_form_memory_size`
|
||||
controls how large any non-file ``multipart/form-data`` field can be. It is
|
||||
set to 500kB by default.
|
||||
- :data:`MAX_FORM_PARTS` or :attr:`.Request.max_form_parts` controls how many
|
||||
``multipart/form-data`` fields can be parsed. It is set to 1000 by default.
|
||||
Combined with the default `max_form_memory_size`, this means that a form
|
||||
will occupy at most 500MB of memory.
|
||||
|
||||
Regardless of these settings, you should also review what settings are available
|
||||
from your operating system, container deployment (Docker etc), WSGI server, HTTP
|
||||
server, and hosting platform. They typically have ways to set process resource
|
||||
limits, timeouts, and other checks regardless of how Flask is configured.
|
||||
|
||||
.. _security-xss:
|
||||
|
||||
Cross-Site Scripting (XSS)
|
||||
--------------------------
|
||||
|
||||
Cross site scripting is the concept of injecting arbitrary HTML (and with
|
||||
it JavaScript) into the context of a website. To remedy this, developers
|
||||
have to properly escape text so that it cannot include arbitrary HTML
|
||||
tags. For more information on that have a look at the Wikipedia article
|
||||
on `Cross-Site Scripting
|
||||
<https://en.wikipedia.org/wiki/Cross-site_scripting>`_.
|
||||
|
||||
Flask configures Jinja to automatically escape all values unless
|
||||
explicitly told otherwise. This should rule out all XSS problems caused
|
||||
in templates, but there are still other places where you have to be
|
||||
careful:
|
||||
|
||||
- generating HTML without the help of Jinja
|
||||
- calling :class:`~markupsafe.Markup` on data submitted by users
|
||||
- sending out HTML from uploaded files, never do that, use the
|
||||
``Content-Disposition: attachment`` header to prevent that problem.
|
||||
- sending out textfiles from uploaded files. Some browsers are using
|
||||
content-type guessing based on the first few bytes so users could
|
||||
trick a browser to execute HTML.
|
||||
|
||||
Another thing that is very important are unquoted attributes. While
|
||||
Jinja can protect you from XSS issues by escaping HTML, there is one
|
||||
thing it cannot protect you from: XSS by attribute injection. To counter
|
||||
this possible attack vector, be sure to always quote your attributes with
|
||||
either double or single quotes when using Jinja expressions in them:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<input value="{{ value }}">
|
||||
|
||||
Why is this necessary? Because if you would not be doing that, an
|
||||
attacker could easily inject custom JavaScript handlers. For example an
|
||||
attacker could inject this piece of HTML+JavaScript:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
onmouseover=alert(document.cookie)
|
||||
|
||||
When the user would then move with the mouse over the input, the cookie
|
||||
would be presented to the user in an alert window. But instead of showing
|
||||
the cookie to the user, a good attacker might also execute any other
|
||||
JavaScript code. In combination with CSS injections the attacker might
|
||||
even make the element fill out the entire page so that the user would
|
||||
just have to have the mouse anywhere on the page to trigger the attack.
|
||||
|
||||
There is one class of XSS issues that Jinja's escaping does not protect
|
||||
against. The ``a`` tag's ``href`` attribute can contain a `javascript:` URI,
|
||||
which the browser will execute when clicked if not secured properly.
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<a href="{{ value }}">click here</a>
|
||||
<a href="javascript:alert('unsafe');">click here</a>
|
||||
|
||||
To prevent this, you'll need to set the :ref:`security-csp` response header.
|
||||
|
||||
Cross-Site Request Forgery (CSRF)
|
||||
---------------------------------
|
||||
|
||||
Another big problem is CSRF. This is a very complex topic and I won't
|
||||
outline it here in detail just mention what it is and how to theoretically
|
||||
prevent it.
|
||||
|
||||
If your authentication information is stored in cookies, you have implicit
|
||||
state management. The state of "being logged in" is controlled by a
|
||||
cookie, and that cookie is sent with each request to a page.
|
||||
Unfortunately that includes requests triggered by 3rd party sites. If you
|
||||
don't keep that in mind, some people might be able to trick your
|
||||
application's users with social engineering to do stupid things without
|
||||
them knowing.
|
||||
|
||||
Say you have a specific URL that, when you sent ``POST`` requests to will
|
||||
delete a user's profile (say ``http://example.com/user/delete``). If an
|
||||
attacker now creates a page that sends a post request to that page with
|
||||
some JavaScript they just have to trick some users to load that page and
|
||||
their profiles will end up being deleted.
|
||||
|
||||
Imagine you were to run Facebook with millions of concurrent users and
|
||||
someone would send out links to images of little kittens. When users
|
||||
would go to that page, their profiles would get deleted while they are
|
||||
looking at images of fluffy cats.
|
||||
|
||||
How can you prevent that? Basically for each request that modifies
|
||||
content on the server you would have to either use a one-time token and
|
||||
store that in the cookie **and** also transmit it with the form data.
|
||||
After receiving the data on the server again, you would then have to
|
||||
compare the two tokens and ensure they are equal.
|
||||
|
||||
Why does Flask not do that for you? The ideal place for this to happen is
|
||||
the form validation framework, which does not exist in Flask.
|
||||
|
||||
.. _security-json:
|
||||
|
||||
JSON Security
|
||||
-------------
|
||||
|
||||
In Flask 0.10 and lower, :func:`~flask.jsonify` did not serialize top-level
|
||||
arrays to JSON. This was because of a security vulnerability in ECMAScript 4.
|
||||
|
||||
ECMAScript 5 closed this vulnerability, so only extremely old browsers are
|
||||
still vulnerable. All of these browsers have `other more serious
|
||||
vulnerabilities
|
||||
<https://github.com/pallets/flask/issues/248#issuecomment-59934857>`_, so
|
||||
this behavior was changed and :func:`~flask.jsonify` now supports serializing
|
||||
arrays.
|
||||
|
||||
Security Headers
|
||||
----------------
|
||||
|
||||
Browsers recognize various response headers in order to control security. We
|
||||
recommend reviewing each of the headers below for use in your application.
|
||||
The `Flask-Talisman`_ extension can be used to manage HTTPS and the security
|
||||
headers for you.
|
||||
|
||||
.. _Flask-Talisman: https://github.com/wntrblm/flask-talisman
|
||||
|
||||
HTTP Strict Transport Security (HSTS)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tells the browser to convert all HTTP requests to HTTPS, preventing
|
||||
man-in-the-middle (MITM) attacks. ::
|
||||
|
||||
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
|
||||
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
||||
|
||||
.. _security-csp:
|
||||
|
||||
Content Security Policy (CSP)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tell the browser where it can load various types of resource from. This header
|
||||
should be used whenever possible, but requires some work to define the correct
|
||||
policy for your site. A very strict policy would be::
|
||||
|
||||
response.headers['Content-Security-Policy'] = "default-src 'self'"
|
||||
|
||||
- https://csp.withgoogle.com/docs/index.html
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||
|
||||
X-Content-Type-Options
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forces the browser to honor the response content type instead of trying to
|
||||
detect it, which can be abused to generate a cross-site scripting (XSS)
|
||||
attack. ::
|
||||
|
||||
response.headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||
|
||||
X-Frame-Options
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Prevents external sites from embedding your site in an ``iframe``. This
|
||||
prevents a class of attacks where clicks in the outer frame can be translated
|
||||
invisibly to clicks on your page's elements. This is also known as
|
||||
"clickjacking". ::
|
||||
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||
|
||||
.. _security-cookie:
|
||||
|
||||
Set-Cookie options
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These options can be added to a ``Set-Cookie`` header to improve their
|
||||
security. Flask has configuration options to set these on the session cookie.
|
||||
They can be set on other cookies too.
|
||||
|
||||
- ``Secure`` limits cookies to HTTPS traffic only.
|
||||
- ``HttpOnly`` protects the contents of cookies from being read with
|
||||
JavaScript.
|
||||
- ``SameSite`` restricts how cookies are sent with requests from
|
||||
external sites. Can be set to ``'Lax'`` (recommended) or ``'Strict'``.
|
||||
``Lax`` prevents sending cookies with CSRF-prone requests from
|
||||
external sites, such as submitting a form. ``Strict`` prevents sending
|
||||
cookies with all external requests, including following regular links.
|
||||
|
||||
::
|
||||
|
||||
app.config.update(
|
||||
SESSION_COOKIE_SECURE=True,
|
||||
SESSION_COOKIE_HTTPONLY=True,
|
||||
SESSION_COOKIE_SAMESITE='Lax',
|
||||
)
|
||||
|
||||
response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax')
|
||||
|
||||
Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after
|
||||
the given time, or the current time plus the age, respectively. If neither
|
||||
option is set, the cookie will be removed when the browser is closed. ::
|
||||
|
||||
# cookie expires after 10 minutes
|
||||
response.set_cookie('snakes', '3', max_age=600)
|
||||
|
||||
For the session cookie, if :attr:`session.permanent <flask.session.permanent>`
|
||||
is set, then :data:`PERMANENT_SESSION_LIFETIME` is used to set the expiration.
|
||||
Flask's default cookie implementation validates that the cryptographic
|
||||
signature is not older than this value. Lowering this value may help mitigate
|
||||
replay attacks, where intercepted cookies can be sent at a later time. ::
|
||||
|
||||
app.config.update(
|
||||
PERMANENT_SESSION_LIFETIME=600
|
||||
)
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
...
|
||||
session.clear()
|
||||
session['user_id'] = user.id
|
||||
session.permanent = True
|
||||
...
|
||||
|
||||
Use :class:`itsdangerous.TimedSerializer` to sign and validate other cookie
|
||||
values (or any values that need secure signatures).
|
||||
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
||||
|
||||
.. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute
|
||||
|
||||
|
||||
Host Header Validation
|
||||
----------------------
|
||||
|
||||
The ``Host`` header is used by the client to indicate what host name the request
|
||||
was made to. This is used, for example, by ``url_for(..., _external=True)`` to
|
||||
generate full URLs, for use in email or other messages outside the browser
|
||||
window.
|
||||
|
||||
By default the app doesn't know what host(s) it is allowed to be accessed
|
||||
through, and assumes any host is valid. Although browsers do not allow setting
|
||||
the ``Host`` header, requests made by attackers in other scenarios could set
|
||||
the ``Host`` header to a value they want.
|
||||
|
||||
When deploying your application, set :data:`TRUSTED_HOSTS` to restrict what
|
||||
values the ``Host`` header may be.
|
||||
|
||||
The ``Host`` header may be modified by proxies in between the client and your
|
||||
application. See :doc:`deploying/proxy_fix` to tell your app which proxy values
|
||||
to trust.
|
||||
|
||||
|
||||
Copy/Paste to Terminal
|
||||
----------------------
|
||||
|
||||
Hidden characters such as the backspace character (``\b``, ``^H``) can
|
||||
cause text to render differently in HTML than how it is interpreted if
|
||||
`pasted into a terminal <https://security.stackexchange.com/q/39118>`__.
|
||||
|
||||
For example, ``import y\bose\bm\bi\bt\be\b`` renders as
|
||||
``import yosemite`` in HTML, but the backspaces are applied when pasted
|
||||
into a terminal, and it becomes ``import os``.
|
||||
|
||||
If you expect users to copy and paste untrusted code from your site,
|
||||
such as from comments posted by users on a technical blog, consider
|
||||
applying extra filtering, such as replacing all ``\b`` characters.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
body = body.replace("\b", "")
|
||||
|
||||
Most modern terminals will warn about and remove hidden characters when
|
||||
pasting, so this isn't strictly necessary. It's also possible to craft
|
||||
dangerous commands in other ways that aren't possible to filter.
|
||||
Depending on your site's use case, it may be good to show a warning
|
||||
about copying code in general.
|
||||
@ -0,0 +1,27 @@
|
||||
Background Tasks with Celery
|
||||
============================
|
||||
|
||||
This example shows how to configure Celery with Flask, how to set up an API for
|
||||
submitting tasks and polling results, and how to use that API with JavaScript. See
|
||||
[Flask's documentation about Celery](https://flask.palletsprojects.com/patterns/celery/).
|
||||
|
||||
From this directory, create a virtualenv and install the application into it. Then run a
|
||||
Celery worker.
|
||||
|
||||
```shell
|
||||
$ python3 -m venv .venv
|
||||
$ . ./.venv/bin/activate
|
||||
$ pip install -r requirements.txt && pip install -e .
|
||||
$ celery -A make_celery worker --loglevel INFO
|
||||
```
|
||||
|
||||
In a separate terminal, activate the virtualenv and run the Flask development server.
|
||||
|
||||
```shell
|
||||
$ . ./.venv/bin/activate
|
||||
$ flask -A task_app run --debug
|
||||
```
|
||||
|
||||
Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling
|
||||
requests in the browser dev tools and the Flask logs. You can see the tasks submitting
|
||||
and completing in the Celery logs.
|
||||
@ -0,0 +1,4 @@
|
||||
from task_app import create_app
|
||||
|
||||
flask_app = create_app()
|
||||
celery_app = flask_app.extensions["celery"]
|
||||