Compare commits
No commits in common. "main" and "0.12.3" have entirely different histories.
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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
|
|
||||||
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
CHANGES merge=union
|
||||||
2
.github/ISSUE_TEMPLATE.rst
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
The issue tracker is a tool to address bugs.
|
||||||
|
Please use the #pocoo IRC channel on freenode or Stack Overflow for questions.
|
||||||
27
.github/ISSUE_TEMPLATE/bug-report.md
vendored
|
|
@ -1,27 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Report a bug in Flask (not other projects which depend on Flask)
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
This issue tracker is a tool to address bugs in Flask itself. Please use
|
|
||||||
GitHub Discussions or the Pallets Discord for questions about your own code.
|
|
||||||
|
|
||||||
Replace this comment with a clear outline of what the bug is.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe how to replicate the bug.
|
|
||||||
|
|
||||||
Include a minimal reproducible example that demonstrates the bug.
|
|
||||||
Include the full traceback if there was an exception.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe the expected behavior that should have happened but didn't.
|
|
||||||
-->
|
|
||||||
|
|
||||||
Environment:
|
|
||||||
|
|
||||||
- Python version:
|
|
||||||
- Flask version:
|
|
||||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,11 +0,0 @@
|
||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Security issue
|
|
||||||
url: https://github.com/pallets/flask/security/advisories/new
|
|
||||||
about: Do not report security issues publicly. Create a private advisory.
|
|
||||||
- name: Questions on GitHub Discussions
|
|
||||||
url: https://github.com/pallets/flask/discussions/
|
|
||||||
about: Ask questions about your own code on the Discussions tab.
|
|
||||||
- name: Questions on Discord
|
|
||||||
url: https://discord.gg/pallets
|
|
||||||
about: Ask questions about your own code on our Discord chat.
|
|
||||||
15
.github/ISSUE_TEMPLATE/feature-request.md
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest a new feature for Flask
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Replace this comment with a description of what the feature should do.
|
|
||||||
Include details such as links to relevant specs or previous discussions.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Replace this comment with an example of the problem which this feature
|
|
||||||
would resolve. Is this problem solvable without changes to Flask, such
|
|
||||||
as by subclassing or using an extension?
|
|
||||||
-->
|
|
||||||
25
.github/pull_request_template.md
vendored
|
|
@ -1,25 +0,0 @@
|
||||||
<!--
|
|
||||||
Before opening a PR, open a ticket describing the issue or feature the
|
|
||||||
PR will address. An issue is not required for fixing typos in
|
|
||||||
documentation, or other simple non-code changes.
|
|
||||||
|
|
||||||
Replace this comment with a description of the change. Describe how it
|
|
||||||
addresses the linked ticket.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Link to relevant issues or previous PRs, one per line. Use "fixes" to
|
|
||||||
automatically close an issue.
|
|
||||||
|
|
||||||
fixes #<issue number>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Ensure each step in CONTRIBUTING.rst is complete, especially the following:
|
|
||||||
|
|
||||||
- Add tests that demonstrate the correct behavior of the change. Tests
|
|
||||||
should fail without the change.
|
|
||||||
- Add or update relevant docs, in the docs folder and in code.
|
|
||||||
- Add an entry in CHANGES.rst summarizing the change and linking to the issue.
|
|
||||||
- Add `.. versionchanged::` entries in any relevant code docs.
|
|
||||||
-->
|
|
||||||
26
.github/workflows/lock.yaml
vendored
|
|
@ -1,26 +0,0 @@
|
||||||
name: Lock inactive closed issues
|
|
||||||
# Lock closed issues that have not received any further activity for two weeks.
|
|
||||||
# This does not close open issues, only humans may do that. It is easier to
|
|
||||||
# respond to new issues with fresh examples rather than continuing discussions
|
|
||||||
# on old issues.
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
permissions: {}
|
|
||||||
concurrency:
|
|
||||||
group: lock
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
lock:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
discussions: write
|
|
||||||
steps:
|
|
||||||
- uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
|
|
||||||
with:
|
|
||||||
issue-inactive-days: 14
|
|
||||||
pr-inactive-days: 14
|
|
||||||
discussion-inactive-days: 14
|
|
||||||
29
.github/workflows/pre-commit.yaml
vendored
|
|
@ -1,29 +0,0 @@
|
||||||
name: pre-commit
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches: [main, stable]
|
|
||||||
permissions: {}
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
prune-cache: false
|
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
||||||
id: setup-python
|
|
||||||
with:
|
|
||||||
python-version-file: pyproject.toml
|
|
||||||
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
|
||||||
with:
|
|
||||||
path: ~/.cache/pre-commit
|
|
||||||
key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }}
|
|
||||||
- run: uv run --locked --no-default-groups --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files
|
|
||||||
62
.github/workflows/publish.yaml
vendored
|
|
@ -1,62 +0,0 @@
|
||||||
name: Publish
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags: ['*']
|
|
||||||
permissions: {}
|
|
||||||
concurrency:
|
|
||||||
group: publish-${{ github.event.push.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
|
||||||
with:
|
|
||||||
enable-cache: false
|
|
||||||
prune-cache: false
|
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
||||||
with:
|
|
||||||
python-version-file: pyproject.toml
|
|
||||||
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
|
|
||||||
- run: uv build
|
|
||||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
id: upload-artifact
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: dist/
|
|
||||||
if-no-files-found: error
|
|
||||||
create-release:
|
|
||||||
needs: [build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
||||||
with:
|
|
||||||
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
||||||
path: dist/
|
|
||||||
- name: create release
|
|
||||||
run: gh release create --draft --repo ${GITHUB_REPOSITORY} ${GITHUB_REF_NAME} dist/*
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
publish-pypi:
|
|
||||||
needs: [build]
|
|
||||||
environment:
|
|
||||||
name: publish
|
|
||||||
url: https://pypi.org/project/Flask/${{ github.ref_name }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
||||||
with:
|
|
||||||
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
||||||
path: dist/
|
|
||||||
- uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
|
||||||
with:
|
|
||||||
packages-dir: "dist/"
|
|
||||||
63
.github/workflows/tests.yaml
vendored
|
|
@ -1,63 +0,0 @@
|
||||||
name: Tests
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths-ignore: ['docs/**', 'README.md']
|
|
||||||
push:
|
|
||||||
branches: [main, stable]
|
|
||||||
paths-ignore: ['docs/**', 'README.md']
|
|
||||||
permissions: {}
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
name: ${{ matrix.name || matrix.python }}
|
|
||||||
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- {python: '3.14'}
|
|
||||||
- {python: '3.14t'}
|
|
||||||
- {name: Windows, python: '3.14', os: windows-latest}
|
|
||||||
- {name: Mac, python: '3.14', os: macos-latest}
|
|
||||||
- {python: '3.13'}
|
|
||||||
- {python: '3.12'}
|
|
||||||
- {python: '3.11'}
|
|
||||||
- {python: '3.10'}
|
|
||||||
- {name: PyPy, python: 'pypy-3.11', tox: pypy3.11}
|
|
||||||
- {name: Minimum Versions, python: '3.14', tox: tests-min}
|
|
||||||
- {name: Development Versions, python: '3.10', tox: tests-dev}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
prune-cache: false
|
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python }}
|
|
||||||
- run: uv run --locked --no-default-groups --group dev tox run
|
|
||||||
env:
|
|
||||||
TOX_ENV: ${{ matrix.tox || format('py{0}', matrix.python) }}
|
|
||||||
typing:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
prune-cache: false
|
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
||||||
with:
|
|
||||||
python-version-file: pyproject.toml
|
|
||||||
- name: cache mypy
|
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
|
||||||
with:
|
|
||||||
path: ./.mypy_cache
|
|
||||||
key: mypy|${{ hashFiles('pyproject.toml') }}
|
|
||||||
- run: uv run --locked --no-default-groups --group dev tox run -e typing
|
|
||||||
22
.github/workflows/zizmor.yaml
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
name: GitHub Actions security analysis with zizmor
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths: ["**/*.yaml?"]
|
|
||||||
push:
|
|
||||||
branches: [main, stable]
|
|
||||||
paths: ["**/*.yaml?"]
|
|
||||||
permissions: {}
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
zizmor:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
|
||||||
with:
|
|
||||||
advanced-security: false
|
|
||||||
annotations: true
|
|
||||||
24
.gitignore
vendored
|
|
@ -1,8 +1,20 @@
|
||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
env
|
||||||
|
env*
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
*.egg
|
||||||
|
*.egg-info
|
||||||
|
_mailinglist
|
||||||
|
.tox
|
||||||
|
.cache/
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
|
||||||
__pycache__/
|
|
||||||
dist/
|
|
||||||
.coverage*
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
|
htmlcov
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
*,cover
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
repos:
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
||||||
rev: 5e2fb545eba1ea9dc051f6f962d52fe8f76a9794 # frozen: v0.15.13
|
|
||||||
hooks:
|
|
||||||
- id: ruff-check
|
|
||||||
- id: ruff-format
|
|
||||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
||||||
rev: fa60a193803535a9e2accdb3ca4b1b584b1150cb # frozen: 0.11.15
|
|
||||||
hooks:
|
|
||||||
- id: uv-lock
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
|
||||||
rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2
|
|
||||||
hooks:
|
|
||||||
- id: codespell
|
|
||||||
args: ['--write-changes']
|
|
||||||
- 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
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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
|
|
||||||
55
.travis.yml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
sudo: false
|
||||||
|
language: python
|
||||||
|
|
||||||
|
python:
|
||||||
|
- "2.6"
|
||||||
|
- "2.7"
|
||||||
|
- "pypy"
|
||||||
|
- "3.3"
|
||||||
|
- "3.4"
|
||||||
|
- "3.5"
|
||||||
|
|
||||||
|
env:
|
||||||
|
- REQUIREMENTS=lowest
|
||||||
|
- REQUIREMENTS=lowest-simplejson
|
||||||
|
- REQUIREMENTS=release
|
||||||
|
- REQUIREMENTS=release-simplejson
|
||||||
|
- REQUIREMENTS=devel
|
||||||
|
- REQUIREMENTS=devel-simplejson
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
exclude:
|
||||||
|
# Python 3 support currently does not work with lowest requirements
|
||||||
|
- python: "3.3"
|
||||||
|
env: REQUIREMENTS=lowest
|
||||||
|
- python: "3.3"
|
||||||
|
env: REQUIREMENTS=lowest-simplejson
|
||||||
|
- python: "3.4"
|
||||||
|
env: REQUIREMENTS=lowest
|
||||||
|
- python: "3.4"
|
||||||
|
env: REQUIREMENTS=lowest-simplejson
|
||||||
|
- python: "3.5"
|
||||||
|
env: REQUIREMENTS=lowest
|
||||||
|
- python: "3.5"
|
||||||
|
env: REQUIREMENTS=lowest-simplejson
|
||||||
|
|
||||||
|
|
||||||
|
install:
|
||||||
|
- pip install tox
|
||||||
|
|
||||||
|
script:
|
||||||
|
- tox -e py-$REQUIREMENTS
|
||||||
|
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- website
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
irc:
|
||||||
|
channels:
|
||||||
|
- "chat.freenode.net#pocoo"
|
||||||
|
on_success: change
|
||||||
|
on_failure: always
|
||||||
|
use_notice: true
|
||||||
|
skip_join: true
|
||||||
37
AUTHORS
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
Flask is written and maintained by Armin Ronacher and
|
||||||
|
various contributors:
|
||||||
|
|
||||||
|
Development Lead
|
||||||
|
````````````````
|
||||||
|
|
||||||
|
- Armin Ronacher <armin.ronacher@active-4.com>
|
||||||
|
|
||||||
|
Patches and Suggestions
|
||||||
|
```````````````````````
|
||||||
|
|
||||||
|
- Adam Zapletal
|
||||||
|
- Ali Afshar
|
||||||
|
- Chris Edgemon
|
||||||
|
- Chris Grindstaff
|
||||||
|
- Christopher Grebs
|
||||||
|
- Daniel Neuhäuser
|
||||||
|
- Dan Sully
|
||||||
|
- David Lord @davidism
|
||||||
|
- Edmond Burnett
|
||||||
|
- Florent Xicluna
|
||||||
|
- Georg Brandl
|
||||||
|
- Jeff Widman @jeffwidman
|
||||||
|
- Justin Quick
|
||||||
|
- Kenneth Reitz
|
||||||
|
- Keyan Pishdadian
|
||||||
|
- Marian Sigler
|
||||||
|
- Martijn Pieters
|
||||||
|
- Matt Campell
|
||||||
|
- Matthew Frazier
|
||||||
|
- Michael van Tellingen
|
||||||
|
- Ron DuPlain
|
||||||
|
- Sebastien Estienne
|
||||||
|
- Simon Sapin
|
||||||
|
- Stephane Wirtel
|
||||||
|
- Thomas Schranz
|
||||||
|
- Zhao Xiaohong
|
||||||
637
CHANGES
Normal file
|
|
@ -0,0 +1,637 @@
|
||||||
|
Flask Changelog
|
||||||
|
===============
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.12.3
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Released on April 26th 2018
|
||||||
|
|
||||||
|
- :func:`Request.get_json` no longer accepts arbitrary encodings.
|
||||||
|
Incoming JSON should be encoded using UTF-8 per :rfc:`8259`, but
|
||||||
|
Flask will autodetect UTF-8, -16, or -32. (`#2692`_)
|
||||||
|
- Fix a Python warning about imports when using ``python -m flask``.
|
||||||
|
(`#2666`_)
|
||||||
|
- Fix a ``ValueError`` caused by invalid ``Range`` requests in some
|
||||||
|
cases.
|
||||||
|
|
||||||
|
.. _#2666: https://github.com/pallets/flask/issues/2666
|
||||||
|
.. _#2692: https://github.com/pallets/flask/issues/2692
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.12.2
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Released on May 16 2017
|
||||||
|
|
||||||
|
- Fix a bug in `safe_join` on Windows.
|
||||||
|
|
||||||
|
Version 0.12.1
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Bugfix release, released on March 31st 2017
|
||||||
|
|
||||||
|
- Prevent `flask run` from showing a NoAppException when an ImportError occurs
|
||||||
|
within the imported application module.
|
||||||
|
- Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix
|
||||||
|
``#2118``.
|
||||||
|
- Call `ctx.auto_pop` with the exception object instead of `None`, in the
|
||||||
|
event that a `BaseException` such as `KeyboardInterrupt` is raised in a
|
||||||
|
request handler.
|
||||||
|
|
||||||
|
Version 0.12
|
||||||
|
------------
|
||||||
|
|
||||||
|
Released on December 21st 2016, codename Punsch.
|
||||||
|
|
||||||
|
- the cli command now responds to `--version`.
|
||||||
|
- Mimetype guessing and ETag generation for file-like objects in ``send_file``
|
||||||
|
has been removed, as per issue ``#104``. See pull request ``#1849``.
|
||||||
|
- Mimetype guessing in ``send_file`` now fails loudly and doesn't fall back to
|
||||||
|
``application/octet-stream``. See pull request ``#1988``.
|
||||||
|
- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join``
|
||||||
|
(pull request ``#1730``).
|
||||||
|
- Revert a behavior change that made the dev server crash instead of returning
|
||||||
|
a Internal Server Error (pull request ``#2006``).
|
||||||
|
- Correctly invoke response handlers for both regular request dispatching as
|
||||||
|
well as error handlers.
|
||||||
|
- Disable logger propagation by default for the app logger.
|
||||||
|
- Add support for range requests in ``send_file``.
|
||||||
|
- ``app.test_client`` includes preset default environment, which can now be
|
||||||
|
directly set, instead of per ``client.get``.
|
||||||
|
|
||||||
|
Version 0.11.2
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Bugfix release, unreleased
|
||||||
|
|
||||||
|
- Fix crash when running under PyPy3, see pull request ``#1814``.
|
||||||
|
|
||||||
|
Version 0.11.1
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Bugfix release, released on June 7th 2016.
|
||||||
|
|
||||||
|
- Fixed a bug that prevented ``FLASK_APP=foobar/__init__.py`` from working. See
|
||||||
|
pull request ``#1872``.
|
||||||
|
|
||||||
|
Version 0.11
|
||||||
|
------------
|
||||||
|
|
||||||
|
Released on May 29th 2016, codename Absinthe.
|
||||||
|
|
||||||
|
- Added support to serializing top-level arrays to :func:`flask.jsonify`. This
|
||||||
|
introduces a security risk in ancient browsers. See
|
||||||
|
:ref:`json-security` for details.
|
||||||
|
- Added before_render_template signal.
|
||||||
|
- Added `**kwargs` to :meth:`flask.Test.test_client` to support passing
|
||||||
|
additional keyword arguments to the constructor of
|
||||||
|
:attr:`flask.Flask.test_client_class`.
|
||||||
|
- Added ``SESSION_REFRESH_EACH_REQUEST`` config key that controls the
|
||||||
|
set-cookie behavior. If set to ``True`` a permanent session will be
|
||||||
|
refreshed each request and get their lifetime extended, if set to
|
||||||
|
``False`` it will only be modified if the session actually modifies.
|
||||||
|
Non permanent sessions are not affected by this and will always
|
||||||
|
expire if the browser window closes.
|
||||||
|
- Made Flask support custom JSON mimetypes for incoming data.
|
||||||
|
- Added support for returning tuples in the form ``(response, headers)``
|
||||||
|
from a view function.
|
||||||
|
- Added :meth:`flask.Config.from_json`.
|
||||||
|
- Added :attr:`flask.Flask.config_class`.
|
||||||
|
- Added :meth:`flask.Config.get_namespace`.
|
||||||
|
- Templates are no longer automatically reloaded outside of debug mode. This
|
||||||
|
can be configured with the new ``TEMPLATES_AUTO_RELOAD`` config key.
|
||||||
|
- Added a workaround for a limitation in Python 3.3's namespace loader.
|
||||||
|
- Added support for explicit root paths when using Python 3.3's namespace
|
||||||
|
packages.
|
||||||
|
- Added :command:`flask` and the ``flask.cli`` module to start the local
|
||||||
|
debug server through the click CLI system. This is recommended over the old
|
||||||
|
``flask.run()`` method as it works faster and more reliable due to a
|
||||||
|
different design and also replaces ``Flask-Script``.
|
||||||
|
- Error handlers that match specific classes are now checked first,
|
||||||
|
thereby allowing catching exceptions that are subclasses of HTTP
|
||||||
|
exceptions (in ``werkzeug.exceptions``). This makes it possible
|
||||||
|
for an extension author to create exceptions that will by default
|
||||||
|
result in the HTTP error of their choosing, but may be caught with
|
||||||
|
a custom error handler if desired.
|
||||||
|
- Added :meth:`flask.Config.from_mapping`.
|
||||||
|
- Flask will now log by default even if debug is disabled. The log format is
|
||||||
|
now hardcoded but the default log handling can be disabled through the
|
||||||
|
``LOGGER_HANDLER_POLICY`` configuration key.
|
||||||
|
- Removed deprecated module functionality.
|
||||||
|
- Added the ``EXPLAIN_TEMPLATE_LOADING`` config flag which when enabled will
|
||||||
|
instruct Flask to explain how it locates templates. This should help
|
||||||
|
users debug when the wrong templates are loaded.
|
||||||
|
- Enforce blueprint handling in the order they were registered for template
|
||||||
|
loading.
|
||||||
|
- Ported test suite to py.test.
|
||||||
|
- Deprecated ``request.json`` in favour of ``request.get_json()``.
|
||||||
|
- Add "pretty" and "compressed" separators definitions in jsonify() method.
|
||||||
|
Reduces JSON response size when JSONIFY_PRETTYPRINT_REGULAR=False by removing
|
||||||
|
unnecessary white space included by default after separators.
|
||||||
|
- JSON responses are now terminated with a newline character, because it is a
|
||||||
|
convention that UNIX text files end with a newline and some clients don't
|
||||||
|
deal well when this newline is missing. See
|
||||||
|
https://github.com/pallets/flask/pull/1262 -- this came up originally as a
|
||||||
|
part of https://github.com/kennethreitz/httpbin/issues/168
|
||||||
|
- The automatically provided ``OPTIONS`` method is now correctly disabled if
|
||||||
|
the user registered an overriding rule with the lowercase-version
|
||||||
|
``options`` (issue ``#1288``).
|
||||||
|
- ``flask.json.jsonify`` now supports the ``datetime.date`` type (pull request
|
||||||
|
``#1326``).
|
||||||
|
- Don't leak exception info of already catched exceptions to context teardown
|
||||||
|
handlers (pull request ``#1393``).
|
||||||
|
- Allow custom Jinja environment subclasses (pull request ``#1422``).
|
||||||
|
- ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
|
||||||
|
- Turn on autoescape for ``flask.templating.render_template_string`` by default
|
||||||
|
(pull request ``#1515``).
|
||||||
|
- ``flask.ext`` is now deprecated (pull request ``#1484``).
|
||||||
|
- ``send_from_directory`` now raises BadRequest if the filename is invalid on
|
||||||
|
the server OS (pull request ``#1763``).
|
||||||
|
- Added the ``JSONIFY_MIMETYPE`` configuration variable (pull request ``#1728``).
|
||||||
|
- Exceptions during teardown handling will no longer leave bad application
|
||||||
|
contexts lingering around.
|
||||||
|
|
||||||
|
Version 0.10.2
|
||||||
|
--------------
|
||||||
|
|
||||||
|
(bugfix release, release date to be announced)
|
||||||
|
|
||||||
|
- Fixed broken `test_appcontext_signals()` test case.
|
||||||
|
- Raise an :exc:`AttributeError` in :func:`flask.helpers.find_package` with a
|
||||||
|
useful message explaining why it is raised when a PEP 302 import hook is used
|
||||||
|
without an `is_package()` method.
|
||||||
|
- Fixed an issue causing exceptions raised before entering a request or app
|
||||||
|
context to be passed to teardown handlers.
|
||||||
|
- Fixed an issue with query parameters getting removed from requests in
|
||||||
|
the test client when absolute URLs were requested.
|
||||||
|
- Made `@before_first_request` into a decorator as intended.
|
||||||
|
- Fixed an etags bug when sending a file streams with a name.
|
||||||
|
- Fixed `send_from_directory` not expanding to the application root path
|
||||||
|
correctly.
|
||||||
|
- Changed logic of before first request handlers to flip the flag after
|
||||||
|
invoking. This will allow some uses that are potentially dangerous but
|
||||||
|
should probably be permitted.
|
||||||
|
- Fixed Python 3 bug when a handler from `app.url_build_error_handlers`
|
||||||
|
reraises the `BuildError`.
|
||||||
|
|
||||||
|
Version 0.10.1
|
||||||
|
--------------
|
||||||
|
|
||||||
|
(bugfix release, released on June 14th 2013)
|
||||||
|
|
||||||
|
- Fixed an issue where ``|tojson`` was not quoting single quotes which
|
||||||
|
made the filter not work properly in HTML attributes. Now it's
|
||||||
|
possible to use that filter in single quoted attributes. This should
|
||||||
|
make using that filter with angular.js easier.
|
||||||
|
- Added support for byte strings back to the session system. This broke
|
||||||
|
compatibility with the common case of people putting binary data for
|
||||||
|
token verification into the session.
|
||||||
|
- Fixed an issue where registering the same method twice for the same endpoint
|
||||||
|
would trigger an exception incorrectly.
|
||||||
|
|
||||||
|
Version 0.10
|
||||||
|
------------
|
||||||
|
|
||||||
|
Released on June 13th 2013, codename Limoncello.
|
||||||
|
|
||||||
|
- Changed default cookie serialization format from pickle to JSON to
|
||||||
|
limit the impact an attacker can do if the secret key leaks. See
|
||||||
|
:ref:`upgrading-to-010` for more information.
|
||||||
|
- Added ``template_test`` methods in addition to the already existing
|
||||||
|
``template_filter`` method family.
|
||||||
|
- Added ``template_global`` methods in addition to the already existing
|
||||||
|
``template_filter`` method family.
|
||||||
|
- Set the content-length header for x-sendfile.
|
||||||
|
- ``tojson`` filter now does not escape script blocks in HTML5 parsers.
|
||||||
|
- ``tojson`` used in templates is now safe by default due. This was
|
||||||
|
allowed due to the different escaping behavior.
|
||||||
|
- Flask will now raise an error if you attempt to register a new function
|
||||||
|
on an already used endpoint.
|
||||||
|
- Added wrapper module around simplejson and added default serialization
|
||||||
|
of datetime objects. This allows much easier customization of how
|
||||||
|
JSON is handled by Flask or any Flask extension.
|
||||||
|
- Removed deprecated internal ``flask.session`` module alias. Use
|
||||||
|
``flask.sessions`` instead to get the session module. This is not to
|
||||||
|
be confused with ``flask.session`` the session proxy.
|
||||||
|
- Templates can now be rendered without request context. The behavior is
|
||||||
|
slightly different as the ``request``, ``session`` and ``g`` objects
|
||||||
|
will not be available and blueprint's context processors are not
|
||||||
|
called.
|
||||||
|
- The config object is now available to the template as a real global and
|
||||||
|
not through a context processor which makes it available even in imported
|
||||||
|
templates by default.
|
||||||
|
- Added an option to generate non-ascii encoded JSON which should result
|
||||||
|
in less bytes being transmitted over the network. It's disabled by
|
||||||
|
default to not cause confusion with existing libraries that might expect
|
||||||
|
``flask.json.dumps`` to return bytestrings by default.
|
||||||
|
- ``flask.g`` is now stored on the app context instead of the request
|
||||||
|
context.
|
||||||
|
- ``flask.g`` now gained a ``get()`` method for not erroring out on non
|
||||||
|
existing items.
|
||||||
|
- ``flask.g`` now can be used with the ``in`` operator to see what's defined
|
||||||
|
and it now is iterable and will yield all attributes stored.
|
||||||
|
- ``flask.Flask.request_globals_class`` got renamed to
|
||||||
|
``flask.Flask.app_ctx_globals_class`` which is a better name to what it
|
||||||
|
does since 0.10.
|
||||||
|
- `request`, `session` and `g` are now also added as proxies to the template
|
||||||
|
context which makes them available in imported templates. One has to be
|
||||||
|
very careful with those though because usage outside of macros might
|
||||||
|
cause caching.
|
||||||
|
- Flask will no longer invoke the wrong error handlers if a proxy
|
||||||
|
exception is passed through.
|
||||||
|
- Added a workaround for chrome's cookies in localhost not working
|
||||||
|
as intended with domain names.
|
||||||
|
- Changed logic for picking defaults for cookie values from sessions
|
||||||
|
to work better with Google Chrome.
|
||||||
|
- Added `message_flashed` signal that simplifies flashing testing.
|
||||||
|
- Added support for copying of request contexts for better working with
|
||||||
|
greenlets.
|
||||||
|
- Removed custom JSON HTTP exception subclasses. If you were relying on them
|
||||||
|
you can reintroduce them again yourself trivially. Using them however is
|
||||||
|
strongly discouraged as the interface was flawed.
|
||||||
|
- Python requirements changed: requiring Python 2.6 or 2.7 now to prepare
|
||||||
|
for Python 3.3 port.
|
||||||
|
- Changed how the teardown system is informed about exceptions. This is now
|
||||||
|
more reliable in case something handles an exception halfway through
|
||||||
|
the error handling process.
|
||||||
|
- Request context preservation in debug mode now keeps the exception
|
||||||
|
information around which means that teardown handlers are able to
|
||||||
|
distinguish error from success cases.
|
||||||
|
- Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable.
|
||||||
|
- Flask now orders JSON keys by default to not trash HTTP caches due to
|
||||||
|
different hash seeds between different workers.
|
||||||
|
- Added `appcontext_pushed` and `appcontext_popped` signals.
|
||||||
|
- The builtin run method now takes the ``SERVER_NAME`` into account when
|
||||||
|
picking the default port to run on.
|
||||||
|
- Added `flask.request.get_json()` as a replacement for the old
|
||||||
|
`flask.request.json` property.
|
||||||
|
|
||||||
|
Version 0.9
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on July 1st 2012, codename Campari.
|
||||||
|
|
||||||
|
- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted
|
||||||
|
response by default.
|
||||||
|
- The :func:`flask.url_for` function now can generate anchors to the
|
||||||
|
generated links.
|
||||||
|
- The :func:`flask.url_for` function now can also explicitly generate
|
||||||
|
URL rules specific to a given HTTP method.
|
||||||
|
- Logger now only returns the debug log setting if it was not set
|
||||||
|
explicitly.
|
||||||
|
- Unregister a circular dependency between the WSGI environment and
|
||||||
|
the request object when shutting down the request. This means that
|
||||||
|
environ ``werkzeug.request`` will be ``None`` after the response was
|
||||||
|
returned to the WSGI server but has the advantage that the garbage
|
||||||
|
collector is not needed on CPython to tear down the request unless
|
||||||
|
the user created circular dependencies themselves.
|
||||||
|
- Session is now stored after callbacks so that if the session payload
|
||||||
|
is stored in the session you can still modify it in an after
|
||||||
|
request callback.
|
||||||
|
- The :class:`flask.Flask` class will avoid importing the provided import name
|
||||||
|
if it can (the required first parameter), to benefit tools which build Flask
|
||||||
|
instances programmatically. The Flask class will fall back to using import
|
||||||
|
on systems with custom module hooks, e.g. Google App Engine, or when the
|
||||||
|
import name is inside a zip archive (usually a .egg) prior to Python 2.7.
|
||||||
|
- Blueprints now have a decorator to add custom template filters application
|
||||||
|
wide, :meth:`flask.Blueprint.app_template_filter`.
|
||||||
|
- The Flask and Blueprint classes now have a non-decorator method for adding
|
||||||
|
custom template filters application wide,
|
||||||
|
:meth:`flask.Flask.add_template_filter` and
|
||||||
|
:meth:`flask.Blueprint.add_app_template_filter`.
|
||||||
|
- The :func:`flask.get_flashed_messages` function now allows rendering flashed
|
||||||
|
message categories in separate blocks, through a ``category_filter``
|
||||||
|
argument.
|
||||||
|
- The :meth:`flask.Flask.run` method now accepts ``None`` for `host` and `port`
|
||||||
|
arguments, using default values when ``None``. This allows for calling run
|
||||||
|
using configuration values, e.g. ``app.run(app.config.get('MYHOST'),
|
||||||
|
app.config.get('MYPORT'))``, with proper behavior whether or not a config
|
||||||
|
file is provided.
|
||||||
|
- The :meth:`flask.render_template` method now accepts a either an iterable of
|
||||||
|
template names or a single template name. Previously, it only accepted a
|
||||||
|
single template name. On an iterable, the first template found is rendered.
|
||||||
|
- Added :meth:`flask.Flask.app_context` which works very similar to the
|
||||||
|
request context but only provides access to the current application. This
|
||||||
|
also adds support for URL generation without an active request context.
|
||||||
|
- View functions can now return a tuple with the first instance being an
|
||||||
|
instance of :class:`flask.Response`. This allows for returning
|
||||||
|
``jsonify(error="error msg"), 400`` from a view function.
|
||||||
|
- :class:`~flask.Flask` and :class:`~flask.Blueprint` now provide a
|
||||||
|
:meth:`~flask.Flask.get_send_file_max_age` hook for subclasses to override
|
||||||
|
behavior of serving static files from Flask when using
|
||||||
|
:meth:`flask.Flask.send_static_file` (used for the default static file
|
||||||
|
handler) and :func:`~flask.helpers.send_file`. This hook is provided a
|
||||||
|
filename, which for example allows changing cache controls by file extension.
|
||||||
|
The default max-age for `send_file` and static files can be configured
|
||||||
|
through a new ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, which is
|
||||||
|
used in the default `get_send_file_max_age` implementation.
|
||||||
|
- Fixed an assumption in sessions implementation which could break message
|
||||||
|
flashing on sessions implementations which use external storage.
|
||||||
|
- Changed the behavior of tuple return values from functions. They are no
|
||||||
|
longer arguments to the response object, they now have a defined meaning.
|
||||||
|
- Added :attr:`flask.Flask.request_globals_class` to allow a specific class to
|
||||||
|
be used on creation of the :data:`~flask.g` instance of each request.
|
||||||
|
- Added `required_methods` attribute to view functions to force-add methods
|
||||||
|
on registration.
|
||||||
|
- Added :func:`flask.after_this_request`.
|
||||||
|
- Added :func:`flask.stream_with_context` and the ability to push contexts
|
||||||
|
multiple times without producing unexpected behavior.
|
||||||
|
|
||||||
|
Version 0.8.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix release, released on July 1st 2012
|
||||||
|
|
||||||
|
- Fixed an issue with the undocumented `flask.session` module to not
|
||||||
|
work properly on Python 2.5. It should not be used but did cause
|
||||||
|
some problems for package managers.
|
||||||
|
|
||||||
|
Version 0.8
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on September 29th 2011, codename Rakija
|
||||||
|
|
||||||
|
- Refactored session support into a session interface so that
|
||||||
|
the implementation of the sessions can be changed without
|
||||||
|
having to override the Flask class.
|
||||||
|
- Empty session cookies are now deleted properly automatically.
|
||||||
|
- View functions can now opt out of getting the automatic
|
||||||
|
OPTIONS implementation.
|
||||||
|
- HTTP exceptions and Bad Request errors can now be trapped so that they
|
||||||
|
show up normally in the traceback.
|
||||||
|
- Flask in debug mode is now detecting some common problems and tries to
|
||||||
|
warn you about them.
|
||||||
|
- Flask in debug mode will now complain with an assertion error if a view
|
||||||
|
was attached after the first request was handled. This gives earlier
|
||||||
|
feedback when users forget to import view code ahead of time.
|
||||||
|
- Added the ability to register callbacks that are only triggered once at
|
||||||
|
the beginning of the first request. (:meth:`Flask.before_first_request`)
|
||||||
|
- Malformed JSON data will now trigger a bad request HTTP exception instead
|
||||||
|
of a value error which usually would result in a 500 internal server
|
||||||
|
error if not handled. This is a backwards incompatible change.
|
||||||
|
- Applications now not only have a root path where the resources and modules
|
||||||
|
are located but also an instance path which is the designated place to
|
||||||
|
drop files that are modified at runtime (uploads etc.). Also this is
|
||||||
|
conceptually only instance depending and outside version control so it's
|
||||||
|
the perfect place to put configuration files etc. For more information
|
||||||
|
see :ref:`instance-folders`.
|
||||||
|
- Added the ``APPLICATION_ROOT`` configuration variable.
|
||||||
|
- Implemented :meth:`~flask.testing.TestClient.session_transaction` to
|
||||||
|
easily modify sessions from the test environment.
|
||||||
|
- Refactored test client internally. The ``APPLICATION_ROOT`` configuration
|
||||||
|
variable as well as ``SERVER_NAME`` are now properly used by the test client
|
||||||
|
as defaults.
|
||||||
|
- Added :attr:`flask.views.View.decorators` to support simpler decorating of
|
||||||
|
pluggable (class-based) views.
|
||||||
|
- Fixed an issue where the test client if used with the "with" statement did not
|
||||||
|
trigger the execution of the teardown handlers.
|
||||||
|
- Added finer control over the session cookie parameters.
|
||||||
|
- HEAD requests to a method view now automatically dispatch to the `get`
|
||||||
|
method if no handler was implemented.
|
||||||
|
- Implemented the virtual :mod:`flask.ext` package to import extensions from.
|
||||||
|
- The context preservation on exceptions is now an integral component of
|
||||||
|
Flask itself and no longer of the test client. This cleaned up some
|
||||||
|
internal logic and lowers the odds of runaway request contexts in unittests.
|
||||||
|
|
||||||
|
Version 0.7.3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix release, release date to be decided
|
||||||
|
|
||||||
|
- Fixed the Jinja2 environment's list_templates method not returning the
|
||||||
|
correct names when blueprints or modules were involved.
|
||||||
|
|
||||||
|
Version 0.7.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix release, released on July 6th 2011
|
||||||
|
|
||||||
|
- Fixed an issue with URL processors not properly working on
|
||||||
|
blueprints.
|
||||||
|
|
||||||
|
Version 0.7.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix release, released on June 29th 2011
|
||||||
|
|
||||||
|
- Added missing future import that broke 2.5 compatibility.
|
||||||
|
- Fixed an infinite redirect issue with blueprints.
|
||||||
|
|
||||||
|
Version 0.7
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on June 28th 2011, codename Grappa
|
||||||
|
|
||||||
|
- Added :meth:`~flask.Flask.make_default_options_response`
|
||||||
|
which can be used by subclasses to alter the default
|
||||||
|
behavior for ``OPTIONS`` responses.
|
||||||
|
- Unbound locals now raise a proper :exc:`RuntimeError` instead
|
||||||
|
of an :exc:`AttributeError`.
|
||||||
|
- Mimetype guessing and etag support based on file objects is now
|
||||||
|
deprecated for :func:`flask.send_file` because it was unreliable.
|
||||||
|
Pass filenames instead or attach your own etags and provide a
|
||||||
|
proper mimetype by hand.
|
||||||
|
- Static file handling for modules now requires the name of the
|
||||||
|
static folder to be supplied explicitly. The previous autodetection
|
||||||
|
was not reliable and caused issues on Google's App Engine. Until
|
||||||
|
1.0 the old behavior will continue to work but issue dependency
|
||||||
|
warnings.
|
||||||
|
- fixed a problem for Flask to run on jython.
|
||||||
|
- added a ``PROPAGATE_EXCEPTIONS`` configuration variable that can be
|
||||||
|
used to flip the setting of exception propagation which previously
|
||||||
|
was linked to ``DEBUG`` alone and is now linked to either ``DEBUG`` or
|
||||||
|
``TESTING``.
|
||||||
|
- Flask no longer internally depends on rules being added through the
|
||||||
|
`add_url_rule` function and can now also accept regular werkzeug
|
||||||
|
rules added to the url map.
|
||||||
|
- Added an `endpoint` method to the flask application object which
|
||||||
|
allows one to register a callback to an arbitrary endpoint with
|
||||||
|
a decorator.
|
||||||
|
- Use Last-Modified for static file sending instead of Date which
|
||||||
|
was incorrectly introduced in 0.6.
|
||||||
|
- Added `create_jinja_loader` to override the loader creation process.
|
||||||
|
- Implemented a silent flag for `config.from_pyfile`.
|
||||||
|
- Added `teardown_request` decorator, for functions that should run at the end
|
||||||
|
of a request regardless of whether an exception occurred. Also the behavior
|
||||||
|
for `after_request` was changed. It's now no longer executed when an exception
|
||||||
|
is raised. See :ref:`upgrading-to-new-teardown-handling`
|
||||||
|
- Implemented :func:`flask.has_request_context`
|
||||||
|
- Deprecated `init_jinja_globals`. Override the
|
||||||
|
:meth:`~flask.Flask.create_jinja_environment` method instead to
|
||||||
|
achieve the same functionality.
|
||||||
|
- Added :func:`flask.safe_join`
|
||||||
|
- The automatic JSON request data unpacking now looks at the charset
|
||||||
|
mimetype parameter.
|
||||||
|
- Don't modify the session on :func:`flask.get_flashed_messages` if there
|
||||||
|
are no messages in the session.
|
||||||
|
- `before_request` handlers are now able to abort requests with errors.
|
||||||
|
- it is not possible to define user exception handlers. That way you can
|
||||||
|
provide custom error messages from a central hub for certain errors that
|
||||||
|
might occur during request processing (for instance database connection
|
||||||
|
errors, timeouts from remote resources etc.).
|
||||||
|
- Blueprints can provide blueprint specific error handlers.
|
||||||
|
- Implemented generic :ref:`views` (class-based views).
|
||||||
|
|
||||||
|
Version 0.6.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix release, released on December 31st 2010
|
||||||
|
|
||||||
|
- Fixed an issue where the default ``OPTIONS`` response was
|
||||||
|
not exposing all valid methods in the ``Allow`` header.
|
||||||
|
- Jinja2 template loading syntax now allows "./" in front of
|
||||||
|
a template load path. Previously this caused issues with
|
||||||
|
module setups.
|
||||||
|
- Fixed an issue where the subdomain setting for modules was
|
||||||
|
ignored for the static folder.
|
||||||
|
- Fixed a security problem that allowed clients to download arbitrary files
|
||||||
|
if the host server was a windows based operating system and the client
|
||||||
|
uses backslashes to escape the directory the files where exposed from.
|
||||||
|
|
||||||
|
Version 0.6
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on July 27th 2010, codename Whisky
|
||||||
|
|
||||||
|
- after request functions are now called in reverse order of
|
||||||
|
registration.
|
||||||
|
- OPTIONS is now automatically implemented by Flask unless the
|
||||||
|
application explicitly adds 'OPTIONS' as method to the URL rule.
|
||||||
|
In this case no automatic OPTIONS handling kicks in.
|
||||||
|
- static rules are now even in place if there is no static folder
|
||||||
|
for the module. This was implemented to aid GAE which will
|
||||||
|
remove the static folder if it's part of a mapping in the .yml
|
||||||
|
file.
|
||||||
|
- the :attr:`~flask.Flask.config` is now available in the templates
|
||||||
|
as `config`.
|
||||||
|
- context processors will no longer override values passed directly
|
||||||
|
to the render function.
|
||||||
|
- added the ability to limit the incoming request data with the
|
||||||
|
new ``MAX_CONTENT_LENGTH`` configuration value.
|
||||||
|
- the endpoint for the :meth:`flask.Module.add_url_rule` method
|
||||||
|
is now optional to be consistent with the function of the
|
||||||
|
same name on the application object.
|
||||||
|
- added a :func:`flask.make_response` function that simplifies
|
||||||
|
creating response object instances in views.
|
||||||
|
- added signalling support based on blinker. This feature is currently
|
||||||
|
optional and supposed to be used by extensions and applications. If
|
||||||
|
you want to use it, make sure to have `blinker`_ installed.
|
||||||
|
- refactored the way URL adapters are created. This process is now
|
||||||
|
fully customizable with the :meth:`~flask.Flask.create_url_adapter`
|
||||||
|
method.
|
||||||
|
- modules can now register for a subdomain instead of just an URL
|
||||||
|
prefix. This makes it possible to bind a whole module to a
|
||||||
|
configurable subdomain.
|
||||||
|
|
||||||
|
.. _blinker: https://pypi.org/project/blinker/
|
||||||
|
|
||||||
|
Version 0.5.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix Release, released on July 15th 2010
|
||||||
|
|
||||||
|
- fixed another issue with loading templates from directories when
|
||||||
|
modules were used.
|
||||||
|
|
||||||
|
Version 0.5.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix Release, released on July 6th 2010
|
||||||
|
|
||||||
|
- fixes an issue with template loading from directories when modules
|
||||||
|
where used.
|
||||||
|
|
||||||
|
Version 0.5
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on July 6th 2010, codename Calvados
|
||||||
|
|
||||||
|
- fixed a bug with subdomains that was caused by the inability to
|
||||||
|
specify the server name. The server name can now be set with
|
||||||
|
the ``SERVER_NAME`` config key. This key is now also used to set
|
||||||
|
the session cookie cross-subdomain wide.
|
||||||
|
- autoescaping is no longer active for all templates. Instead it
|
||||||
|
is only active for ``.html``, ``.htm``, ``.xml`` and ``.xhtml``.
|
||||||
|
Inside templates this behavior can be changed with the
|
||||||
|
``autoescape`` tag.
|
||||||
|
- refactored Flask internally. It now consists of more than a
|
||||||
|
single file.
|
||||||
|
- :func:`flask.send_file` now emits etags and has the ability to
|
||||||
|
do conditional responses builtin.
|
||||||
|
- (temporarily) dropped support for zipped applications. This was a
|
||||||
|
rarely used feature and led to some confusing behavior.
|
||||||
|
- added support for per-package template and static-file directories.
|
||||||
|
- removed support for `create_jinja_loader` which is no longer used
|
||||||
|
in 0.5 due to the improved module support.
|
||||||
|
- added a helper function to expose files from any directory.
|
||||||
|
|
||||||
|
Version 0.4
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on June 18th 2010, codename Rakia
|
||||||
|
|
||||||
|
- added the ability to register application wide error handlers
|
||||||
|
from modules.
|
||||||
|
- :meth:`~flask.Flask.after_request` handlers are now also invoked
|
||||||
|
if the request dies with an exception and an error handling page
|
||||||
|
kicks in.
|
||||||
|
- test client has not the ability to preserve the request context
|
||||||
|
for a little longer. This can also be used to trigger custom
|
||||||
|
requests that do not pop the request stack for testing.
|
||||||
|
- because the Python standard library caches loggers, the name of
|
||||||
|
the logger is configurable now to better support unittests.
|
||||||
|
- added ``TESTING`` switch that can activate unittesting helpers.
|
||||||
|
- the logger switches to ``DEBUG`` mode now if debug is enabled.
|
||||||
|
|
||||||
|
Version 0.3.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix release, released on May 28th 2010
|
||||||
|
|
||||||
|
- fixed a error reporting bug with :meth:`flask.Config.from_envvar`
|
||||||
|
- removed some unused code from flask
|
||||||
|
- release does no longer include development leftover files (.git
|
||||||
|
folder for themes, built documentation in zip and pdf file and
|
||||||
|
some .pyc files)
|
||||||
|
|
||||||
|
Version 0.3
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on May 28th 2010, codename Schnaps
|
||||||
|
|
||||||
|
- added support for categories for flashed messages.
|
||||||
|
- the application now configures a :class:`logging.Handler` and will
|
||||||
|
log request handling exceptions to that logger when not in debug
|
||||||
|
mode. This makes it possible to receive mails on server errors
|
||||||
|
for example.
|
||||||
|
- added support for context binding that does not require the use of
|
||||||
|
the with statement for playing in the console.
|
||||||
|
- the request context is now available within the with statement making
|
||||||
|
it possible to further push the request context or pop it.
|
||||||
|
- added support for configurations.
|
||||||
|
|
||||||
|
Version 0.2
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on May 12th 2010, codename Jägermeister
|
||||||
|
|
||||||
|
- various bugfixes
|
||||||
|
- integrated JSON support
|
||||||
|
- added :func:`~flask.get_template_attribute` helper function.
|
||||||
|
- :meth:`~flask.Flask.add_url_rule` can now also register a
|
||||||
|
view function.
|
||||||
|
- refactored internal request dispatching.
|
||||||
|
- server listens on 127.0.0.1 by default now to fix issues with chrome.
|
||||||
|
- added external URL support.
|
||||||
|
- added support for :func:`~flask.send_file`
|
||||||
|
- module support and internal request handling refactoring
|
||||||
|
to better support pluggable applications.
|
||||||
|
- sessions can be set to be permanent now on a per-session basis.
|
||||||
|
- better error reporting on missing secret keys.
|
||||||
|
- added support for Google Appengine.
|
||||||
|
|
||||||
|
Version 0.1
|
||||||
|
-----------
|
||||||
|
|
||||||
|
First public preview release.
|
||||||
1665
CHANGES.rst
114
CONTRIBUTING.rst
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
==========================
|
||||||
|
How to contribute to Flask
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Thanks for considering contributing to Flask.
|
||||||
|
|
||||||
|
Support questions
|
||||||
|
=================
|
||||||
|
|
||||||
|
Please, don't use the issue tracker for this. Check whether the ``#pocoo`` IRC
|
||||||
|
channel on Freenode can help with your issue. If your problem is not strictly
|
||||||
|
Werkzeug or Flask specific, ``#python`` is generally more active.
|
||||||
|
`Stack Overflow <https://stackoverflow.com/>`_ is also worth considering.
|
||||||
|
|
||||||
|
Reporting issues
|
||||||
|
================
|
||||||
|
|
||||||
|
- Under which versions of Python does this happen? This is even more important
|
||||||
|
if your issue is encoding related.
|
||||||
|
|
||||||
|
- Under which versions of Werkzeug does this happen? Check if this issue is
|
||||||
|
fixed in the repository.
|
||||||
|
|
||||||
|
Submitting patches
|
||||||
|
==================
|
||||||
|
|
||||||
|
- Include tests if your patch is supposed to solve a bug, and explain
|
||||||
|
clearly under which circumstances the bug happens. Make sure the test fails
|
||||||
|
without your patch.
|
||||||
|
|
||||||
|
- Try to follow `PEP8 <http://legacy.python.org/dev/peps/pep-0008/>`_, but you
|
||||||
|
may ignore the line-length-limit if following it would make the code uglier.
|
||||||
|
|
||||||
|
|
||||||
|
Running the testsuite
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
You probably want to set up a `virtualenv
|
||||||
|
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
|
||||||
|
|
||||||
|
The minimal requirement for running the testsuite is ``py.test``. You can
|
||||||
|
install it with::
|
||||||
|
|
||||||
|
pip install pytest
|
||||||
|
|
||||||
|
Clone this repository::
|
||||||
|
|
||||||
|
git clone https://github.com/pallets/flask.git
|
||||||
|
|
||||||
|
Install Flask as an editable package using the current source::
|
||||||
|
|
||||||
|
cd flask
|
||||||
|
pip install --editable .
|
||||||
|
|
||||||
|
Then you can run the testsuite with::
|
||||||
|
|
||||||
|
py.test
|
||||||
|
|
||||||
|
With only py.test installed, a large part of the testsuite will get skipped
|
||||||
|
though. Whether this is relevant depends on which part of Flask you're working
|
||||||
|
on. Travis is set up to run the full testsuite when you submit your pull
|
||||||
|
request anyways.
|
||||||
|
|
||||||
|
If you really want to test everything, you will have to install ``tox`` instead
|
||||||
|
of ``pytest``. You can install it with::
|
||||||
|
|
||||||
|
pip install tox
|
||||||
|
|
||||||
|
The ``tox`` command will then run all tests against multiple combinations
|
||||||
|
Python versions and dependency versions.
|
||||||
|
|
||||||
|
Running test coverage
|
||||||
|
---------------------
|
||||||
|
Generating a report of lines that do not have unit test coverage can indicate where
|
||||||
|
to start contributing. ``pytest`` integrates with ``coverage.py``, using the ``pytest-cov``
|
||||||
|
plugin. This assumes you have already run the testsuite (see previous section)::
|
||||||
|
|
||||||
|
pip install pytest-cov
|
||||||
|
|
||||||
|
After this has been installed, you can output a report to the command line using this command::
|
||||||
|
|
||||||
|
py.test --cov=flask tests/
|
||||||
|
|
||||||
|
Generate a HTML report can be done using this command::
|
||||||
|
|
||||||
|
py.test --cov-report html --cov=flask tests/
|
||||||
|
|
||||||
|
Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io
|
||||||
|
|
||||||
|
Caution
|
||||||
|
=======
|
||||||
|
pushing
|
||||||
|
-------
|
||||||
|
This repository contains several zero-padded file modes that may cause issues when pushing this repository to git hosts other than github. Fixing this is destructive to the commit history, so we suggest ignoring these warnings. If it fails to push and you're using a self-hosted git service like Gitlab, you can turn off repository checks in the admin panel.
|
||||||
|
|
||||||
|
|
||||||
|
cloning
|
||||||
|
-------
|
||||||
|
The zero-padded file modes files above can cause issues while cloning, too. If you have
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[fetch]
|
||||||
|
fsckobjects = true
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[receive]
|
||||||
|
fsckObjects = true
|
||||||
|
|
||||||
|
|
||||||
|
set in your git configuration file, cloning this repository will fail. The only solution is to set both of the above settings to false while cloning, and then setting them back to true after the cloning is finished.
|
||||||
33
LICENSE
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
Copyright (c) 2015 by Armin Ronacher and contributors. See AUTHORS
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
Some rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms of the software as well
|
||||||
|
as documentation, with or without modification, are permitted provided
|
||||||
|
that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
* The names of the contributors may not be used to endorse or
|
||||||
|
promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE AND DOCUMENTATION 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 OWNER
|
||||||
|
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 AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGE.
|
||||||
28
LICENSE.txt
|
|
@ -1,28 +0,0 @@
|
||||||
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.
|
|
||||||
11
MANIFEST.in
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
include Makefile CHANGES LICENSE AUTHORS
|
||||||
|
|
||||||
|
graft artwork
|
||||||
|
graft tests
|
||||||
|
graft examples
|
||||||
|
graft docs
|
||||||
|
|
||||||
|
global-exclude *.py[co]
|
||||||
|
|
||||||
|
prune docs/_build
|
||||||
|
prune docs/_themes
|
||||||
41
Makefile
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
.PHONY: clean-pyc ext-test test tox-test test-with-mem upload-docs docs audit
|
||||||
|
|
||||||
|
all: clean-pyc test
|
||||||
|
|
||||||
|
test:
|
||||||
|
pip install -r test-requirements.txt
|
||||||
|
tox -e py-release
|
||||||
|
|
||||||
|
audit:
|
||||||
|
python setup.py audit
|
||||||
|
|
||||||
|
release:
|
||||||
|
python scripts/make-release.py
|
||||||
|
|
||||||
|
ext-test:
|
||||||
|
python tests/flaskext_test.py --browse
|
||||||
|
|
||||||
|
clean-pyc:
|
||||||
|
find . -name '*.pyc' -exec rm -f {} +
|
||||||
|
find . -name '*.pyo' -exec rm -f {} +
|
||||||
|
find . -name '*~' -exec rm -f {} +
|
||||||
|
|
||||||
|
upload-docs:
|
||||||
|
$(MAKE) -C docs html dirhtml latex epub
|
||||||
|
$(MAKE) -C docs/_build/latex all-pdf
|
||||||
|
cd docs/_build/; mv html flask-docs; zip -r flask-docs.zip flask-docs; mv flask-docs html
|
||||||
|
rsync -a docs/_build/dirhtml/ flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/
|
||||||
|
rsync -a docs/_build/latex/Flask.pdf flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/flask-docs.pdf
|
||||||
|
rsync -a docs/_build/flask-docs.zip flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/flask-docs.zip
|
||||||
|
rsync -a docs/_build/epub/Flask.epub flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/flask-docs.epub
|
||||||
|
|
||||||
|
# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html
|
||||||
|
ebook:
|
||||||
|
@echo 'Using .epub from `make upload-docs` to create .mobi.'
|
||||||
|
@echo 'Command `ebook-covert` is provided by calibre package.'
|
||||||
|
@echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).'
|
||||||
|
@echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.'
|
||||||
|
ssh -X pocoo.org ebook-convert /var/www/flask.pocoo.org/docs/flask-docs.epub /var/www/flask.pocoo.org/docs/flask-docs.mobi --cover http://flask.pocoo.org/docs/_images/logo-full.png --authors 'Armin Ronacher'
|
||||||
|
|
||||||
|
docs:
|
||||||
|
$(MAKE) -C docs html
|
||||||
49
README
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
|
||||||
|
|
||||||
|
// Flask //
|
||||||
|
|
||||||
|
web development, one drop at a time
|
||||||
|
|
||||||
|
|
||||||
|
~ What is Flask?
|
||||||
|
|
||||||
|
Flask is a microframework for Python based on Werkzeug
|
||||||
|
and Jinja2. It's intended for getting started very quickly
|
||||||
|
and was developed with best intentions in mind.
|
||||||
|
|
||||||
|
~ Is it ready?
|
||||||
|
|
||||||
|
It's still not 1.0 but it's shaping up nicely and is
|
||||||
|
already widely used. Consider the API to slightly
|
||||||
|
improve over time but we don't plan on breaking it.
|
||||||
|
|
||||||
|
~ What do I need?
|
||||||
|
|
||||||
|
All dependencies are installed by using `pip install Flask`.
|
||||||
|
We encourage you to use a virtualenv. Check the docs for
|
||||||
|
complete installation and usage instructions.
|
||||||
|
|
||||||
|
~ Where are the docs?
|
||||||
|
|
||||||
|
Go to http://flask.pocoo.org/docs/ for a prebuilt version
|
||||||
|
of the current documentation. Otherwise build them yourself
|
||||||
|
from the sphinx sources in the docs folder.
|
||||||
|
|
||||||
|
~ Where are the tests?
|
||||||
|
|
||||||
|
Good that you're asking. The tests are in the
|
||||||
|
tests/ folder. To run the tests use the
|
||||||
|
`py.test` testing tool:
|
||||||
|
|
||||||
|
$ py.test
|
||||||
|
|
||||||
|
Details on contributing can be found in CONTRIBUTING.rst
|
||||||
|
|
||||||
|
~ Where can I get help?
|
||||||
|
|
||||||
|
Either use the #pocoo IRC channel on irc.freenode.net or
|
||||||
|
ask on the mailinglist: http://flask.pocoo.org/mailinglist/
|
||||||
|
|
||||||
|
See http://flask.pocoo.org/community/ for more resources.
|
||||||
|
|
||||||
|
|
||||||
53
README.md
|
|
@ -1,53 +0,0 @@
|
||||||
<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/
|
|
||||||
20
artwork/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2010 by Armin Ronacher.
|
||||||
|
|
||||||
|
Some rights reserved.
|
||||||
|
|
||||||
|
This logo or a modified version may be used by anyone to refer to the
|
||||||
|
Flask project, but does not indicate endorsement by the project.
|
||||||
|
|
||||||
|
Redistribution and use in source (the SVG file) and binary forms (rendered
|
||||||
|
PNG files etc.) of the image, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice and this list of conditions.
|
||||||
|
|
||||||
|
* The names of the contributors to the Flask software (see AUTHORS) may
|
||||||
|
not be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
Note: we would appreciate that you make the image a link to
|
||||||
|
http://flask.pocoo.org/ if you use it on a web page.
|
||||||
290
artwork/logo-full.svg
Normal file
|
After Width: | Height: | Size: 77 KiB |
165
artwork/logo-lineart.svg
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="211.15901"
|
||||||
|
height="190.52811"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.5 r10040"
|
||||||
|
sodipodi:docname="logo-lineart.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<inkscape:perspective
|
||||||
|
sodipodi:type="inkscape:persp3d"
|
||||||
|
inkscape:vp_x="0 : 526.18109 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||||
|
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||||
|
id="perspective10" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective2824"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective2840"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective2878"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective2894"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective2910"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective2926"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective2976"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective3020"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective3036"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective3052"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
<inkscape:perspective
|
||||||
|
id="perspective3866"
|
||||||
|
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||||
|
inkscape:vp_z="1 : 0.5 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_x="0 : 0.5 : 1"
|
||||||
|
sodipodi:type="inkscape:persp3d" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="2.1723341"
|
||||||
|
inkscape:cx="242.05817"
|
||||||
|
inkscape:cy="92.686293"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1676"
|
||||||
|
inkscape:window-height="1005"
|
||||||
|
inkscape:window-x="4"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
fit-margin-top="10"
|
||||||
|
fit-margin-left="10"
|
||||||
|
fit-margin-right="10"
|
||||||
|
fit-margin-bottom="10" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-29.820801,-20.186869)">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="M 9.7525867,55.788422 C 40.421293,45.982204 33.821969,46.567748 69.327984,40.346648 c 8.493721,2.411576 22.910914,5.687215 22.240236,12.296506 -6.241933,2.320572 -15.351869,-6.455434 -20.254712,-1.539882 0.01014,18.421641 5.965221,38.200493 13.480678,55.747588 7.515457,17.5471 18.880714,32.86245 34.290034,42.35708 20.42595,12.66826 41.92048,14.9356 63.64846,15.65546 6.66858,0.23786 17.30912,-1.47838 20.01846,0 -4.9124,8.703 -19.28006,12.8118 -34.21844,14.71154 -14.93837,1.89974 -30.44747,1.59043 -37.64272,1.45723 -15.88921,-0.50065 -29.5942,-2.65111 -42.06658,-7.29048 C 56.640409,160.78176 38.428746,134.71246 24.668078,106.25832 16.765019,89.693325 11.290118,72.259923 9.7525867,55.788422 z"
|
||||||
|
id="path3826"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
transform="translate(29.820801,20.186869)"
|
||||||
|
sodipodi:nodetypes="ccccscccscccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="m 54.066806,32.351647 c -0.427165,0.87404 -0.384822,1.998232 -0.02834,2.90275 0.781834,1.983761 2.799883,3.252081 4.397491,4.681241 0.728446,0.651642 2.26934,0.803097 2.364296,1.769134 0.215279,2.190161 -2.700769,3.566537 -4.456242,4.921486 -1.316317,1.015991 -3.845581,0.776849 -4.451985,2.314219 -0.417515,1.058499 0.837317,2.10047 1.1679,3.188615 0.465799,1.533243 1.642442,3.150334 1.145997,4.674061 -0.597449,1.833868 -2.700081,2.84663 -4.420626,3.754433 -1.893115,0.998854 -4.450538,0.497797 -6.207667,1.715064 -1.674125,1.159765 -3.485979,2.907099 -3.554321,4.925579 -0.03097,0.915115 -0.384582,2.676814 -0.233936,3.114037 12.863193,-4.155671 20.195138,-6.507915 28.694286,-8.598094 8.499136,-2.090222 16.108852,-3.399531 29.579722,-5.689662 -0.06867,-0.457321 -1.197061,-1.855664 -1.647827,-2.652661 -0.994254,-1.75795 -3.408869,-2.469029 -5.429591,-2.722885 -2.120906,-0.26644 -4.15652,1.360749 -6.296964,1.350851 -1.945327,-0.009 -4.277958,0.06569 -5.655921,-1.283841 -1.144955,-1.121286 -0.849755,-3.099246 -1.145997,-4.674061 -0.210243,-1.117649 0.420309,-2.621884 -0.439473,-3.367212 -1.248754,-1.082519 -3.380554,0.299434 -5.017542,0.0075 -2.183125,-0.389274 -5.405114,-0.260713 -6.227327,-2.302063 -0.362663,-0.900401 0.93342,-1.747432 1.277831,-2.662119 0.75535,-2.006065 1.957858,-4.064009 1.733419,-6.184432 -0.102333,-0.966833 -0.5848,-1.983113 -1.367813,-2.56044 -1.68203,-1.191313 -4.366912,-1.323763 -7.531636,-0.525881 -3.164723,0.797885 -5.342193,2.137743 -6.247739,3.90434 z"
|
||||||
|
id="path3832"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="csssssscssscccssscssssssccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="m 71.836704,50.617573 c 0,0 -24.55635,5.277975 -35.918352,8.551988 C 27.8621,61.491007 12.143824,67.37947 12.143824,67.37947"
|
||||||
|
id="path3847"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
transform="translate(29.820801,20.186869)"
|
||||||
|
sodipodi:nodetypes="csc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.9 KiB |
|
|
@ -1,10 +1,10 @@
|
||||||
# Minimal makefile for Sphinx documentation
|
# Minimal makefile for Sphinx documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line, and also
|
# You can set these variables from the command line.
|
||||||
# from the environment for the first two.
|
SPHINXOPTS =
|
||||||
SPHINXOPTS ?=
|
SPHINXBUILD = sphinx-build
|
||||||
SPHINXBUILD ?= sphinx-build
|
SPHINXPROJ = Flask
|
||||||
SOURCEDIR = .
|
SOURCEDIR = .
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
|
@ -17,4 +17,4 @@ help:
|
||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
%: Makefile
|
%: Makefile
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
BIN
docs/_static/debugger.png
vendored
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 121 KiB |
BIN
docs/_static/flask-favicon.ico
vendored
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
15
docs/_static/flask-icon.svg
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
||||||
<rect id="Icon" x="0" y="0" width="500" height="500" style="fill:none;"/>
|
|
||||||
<clipPath id="_clip1">
|
|
||||||
<rect x="0" y="0" width="500" height="500"/>
|
|
||||||
</clipPath>
|
|
||||||
<g clip-path="url(#_clip1)">
|
|
||||||
<g>
|
|
||||||
<path d="M224.446,59.975c-0.056,-4.151 -0.483,-5.543 -2.7,-6.823c-2.104,-1.393 -5.288,-1.421 -8.329,-0.085l-204.674,87.64c-3.042,1.336 -5.913,4.008 -7.448,6.908c-1.535,2.899 -1.705,5.97 -0.511,8.158l17.084,31.384l0.228,0.369c1.847,2.928 6.026,3.696 10.29,1.82l1.251,-0.54c5.344,22.4 14.1,50.429 25.783,70.413l178.294,-79.794c-2.559,-23.14 -9.552,-89.602 -9.268,-119.479l0,0.029Z" style="fill:#3babc3;fill-rule:nonzero;"/>
|
|
||||||
<path d="M238.603,205.776l-171.698,76.838c10.091,19.132 22.542,39.428 37.722,58.986c50.429,-25.698 100.887,-51.396 151.316,-77.094c-3.269,-8.471 -6.452,-17.653 -17.34,-58.73Z" style="fill:#3babc3;fill-rule:nonzero;"/>
|
|
||||||
<path d="M497.601,388.846l-12.139,-18.535c-1.819,-2.018 -4.633,-2.786 -7.106,-1.791l-15.578,5.999c-1.848,-2.047 -4.52,-2.815 -7.135,-1.791c-5.089,1.99 -10.206,4.008 -15.294,5.998c-1.649,0.625 -2.104,1.847 -1.791,3.439l0.995,4.861c-28.711,3.099 -77.236,1.564 -120.701,-32.577c-19.216,-15.066 -37.239,-36.386 -52.277,-66.206l-144.75,73.768c26.466,29.08 59.697,54.864 100.973,70.385c57.422,21.633 130.593,23.679 222.838,-13.475l0.512,2.616c0.455,2.928 3.98,6.026 8.755,4.15l15.323,-5.97c5.258,-1.99 5.287,-6.026 4.519,-8.641l19.729,-7.704c2.217,-0.853 9.096,-6.169 3.183,-14.526l-0.056,-0Z" style="fill:#3babc3;fill-rule:nonzero;"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2 KiB |
17
docs/_static/flask-logo.svg
vendored
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
||||||
<rect id="Logo" x="0" y="0" width="500" height="500" style="fill:none;"/>
|
|
||||||
<g>
|
|
||||||
<path id="Box" d="M500,50l0,400c0,27.596 -22.404,50 -50,50l-400,0c-27.596,0 -50,-22.404 -50,-50l0,-400c0,-27.596 22.404,-50 50,-50l400,0c27.596,0 50,22.404 50,50Z" style="fill:url(#_Linear1);"/>
|
|
||||||
<path id="Shadow" d="M500,398.646l0,51.354c0,27.596 -22.404,50 -50,50l-94.111,0l-170.452,-170.451c13.541,12.279 29.511,22.845 48.242,29.888c34.453,12.98 78.356,14.208 133.703,-8.084l0.307,1.569c0.273,1.757 2.388,3.616 5.253,2.49l9.193,-3.582c3.156,-1.194 3.173,-3.616 2.712,-5.185l11.837,-4.622c1.331,-0.512 5.458,-3.701 1.91,-8.716l-0.034,0l-7.283,-11.12c-1.091,-1.211 -2.78,-1.672 -4.264,-1.075l-9.346,3.599c-1.109,-1.228 -2.712,-1.688 -4.281,-1.074c-3.054,1.194 -6.124,2.404 -9.177,3.598c-0.989,0.376 -1.262,1.109 -1.074,2.064l0.597,2.917c-17.227,1.859 -46.342,0.938 -72.421,-19.547c-11.53,-9.039 -22.343,-21.831 -31.366,-39.723l-83.923,42.769l-13.246,-10.755c30.258,-15.419 60.532,-30.837 90.79,-46.256c-1.961,-5.083 -3.872,-10.592 -10.404,-35.238l-98.082,43.893l-11.828,-11.828l106.976,-47.876c-1.534,-13.88 -5.728,-53.736 -5.56,-71.67l-0,-0.017c-0.027,-1.894 -0.184,-2.827 -0.85,-3.504l266.182,266.182Zm-388.562,-185.436c1.272,1.164 3.414,1.356 5.594,0.397l0.75,-0.324c0.645,2.703 1.373,5.543 2.181,8.452l-8.525,-8.525Z" style="fill:#3b808b;"/>
|
|
||||||
<g id="Icon">
|
|
||||||
<path d="M234.668,135.985c-0.034,-2.49 -0.29,-3.326 -1.62,-4.094c-1.263,-0.835 -3.173,-0.852 -4.998,-0.051l-122.804,52.584c-1.825,0.802 -3.548,2.405 -4.469,4.145c-0.921,1.74 -1.023,3.582 -0.307,4.895l10.251,18.83l0.136,0.222c1.109,1.757 3.616,2.217 6.175,1.091l0.75,-0.324c3.207,13.441 8.46,30.258 15.47,42.248l106.976,-47.876c-1.535,-13.884 -5.731,-53.761 -5.56,-71.687l-0,0.017Z" style="fill:#fff;fill-rule:nonzero;"/>
|
|
||||||
<path d="M243.162,223.466l-103.019,46.103c6.055,11.478 13.525,23.656 22.633,35.391c30.258,-15.419 60.532,-30.837 90.79,-46.256c-1.961,-5.083 -3.872,-10.592 -10.404,-35.238Z" style="fill:#fff;fill-rule:nonzero;"/>
|
|
||||||
<path d="M398.56,333.307l-7.283,-11.12c-1.091,-1.211 -2.78,-1.672 -4.264,-1.075l-9.346,3.599c-1.109,-1.228 -2.712,-1.688 -4.281,-1.074c-3.054,1.194 -6.124,2.404 -9.177,3.598c-0.989,0.376 -1.262,1.109 -1.074,2.064l0.597,2.917c-17.227,1.859 -46.342,0.938 -72.421,-19.547c-11.53,-9.039 -22.343,-21.831 -31.366,-39.723l-86.85,44.26c15.879,17.449 35.818,32.919 60.584,42.231c34.453,12.98 78.356,14.208 133.703,-8.084l0.307,1.569c0.273,1.757 2.388,3.616 5.253,2.49l9.193,-3.582c3.156,-1.194 3.173,-3.616 2.712,-5.185l11.837,-4.622c1.331,-0.512 5.458,-3.701 1.91,-8.716l-0.034,0Z" style="fill:#fff;fill-rule:nonzero;"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.06162e-14,500,-500,3.06162e-14,267.59,0)"><stop offset="0" style="stop-color:#bdddeb;stop-opacity:1"/><stop offset="1" style="stop-color:#53a9d1;stop-opacity:1"/></linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
23
docs/_static/flask-name.svg
vendored
|
|
@ -1,23 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 706 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
||||||
<g>
|
|
||||||
<path id="Name" d="M420.35,117.6l-37.65,-0c-4.4,-0 -7.475,0.85 -9.225,2.55c-1.75,1.7 -2.625,4.65 -2.625,8.85l-0,14.85l39.15,-0l-1.5,18.6l-37.65,-0l-0,43.35l-20.85,-0l-0,-85.05c-0,-7.9 1.725,-13.5 5.175,-16.8c3.45,-3.3 9.375,-4.95 17.775,-4.95l47.4,-0l0,18.6Z" style="fill-rule:nonzero;"/>
|
|
||||||
<path d="M455.75,205.8l-19.5,0.15l-0,-112.95l19.5,-0l-0,112.8Z" style="fill-rule:nonzero;"/>
|
|
||||||
<path d="M535.7,205.8l-13.05,-0l-6,-10.35l-20.85,11.25c-11.6,-0 -19.4,-4.2 -23.4,-12.6c-2,-4.1 -3.375,-8.525 -4.125,-13.275c-0.75,-4.75 -1.125,-9.7 -1.125,-14.85c-0,-5.15 0.05,-8.95 0.15,-11.4c0.1,-2.45 0.35,-5.3 0.75,-8.55c0.4,-3.25 0.975,-5.975 1.725,-8.175c0.75,-2.2 1.825,-4.475 3.225,-6.825c1.4,-2.35 3.1,-4.225 5.1,-5.625c4.5,-3.1 10.35,-4.65 17.55,-4.65l20.55,-0l19.5,-1.2l-0,86.25Zm-19.5,-27.3l-0,-40.2l-14.85,-0c-5.5,-0 -9.325,2.1 -11.475,6.3c-2.15,4.2 -3.225,10.475 -3.225,18.825c-0,8.35 1.025,14.225 3.075,17.625c2.05,3.4 5.925,5.1 11.625,5.1l14.85,-7.65Z" style="fill-rule:nonzero;"/>
|
|
||||||
<path d="M615.65,182.1l0,2.25c-0.6,7.5 -3.775,13.15 -9.525,16.95c-5.75,3.8 -12.925,5.7 -21.525,5.7c-12.7,-0 -21.6,-2.3 -26.7,-6.9c-4.7,-4.2 -7.05,-10.4 -7.05,-18.6l0,-1.8l17.7,0c0,4.6 1.2,7.75 3.6,9.45c2.4,1.7 6.55,2.55 12.45,2.55c8,0 12,-2.9 12,-8.7c0,-4.8 -1.4,-8 -4.2,-9.6c-1.3,-0.8 -2.95,-1.4 -4.95,-1.8l-15.15,-2.55c-13.2,-2.1 -19.8,-10.35 -19.8,-24.75c0,-8 2.925,-14.225 8.775,-18.675c5.85,-4.45 13.275,-6.675 22.275,-6.675c20.5,0 30.75,8.85 30.75,26.55l0,1.95l-16.95,0c-0.2,-4.7 -1.45,-7.9 -3.75,-9.6c-2.3,-1.7 -5.525,-2.55 -9.675,-2.55c-4.15,0 -7.275,0.825 -9.375,2.475c-2.1,1.65 -3.15,3.475 -3.15,5.475c0,5.7 2.3,8.95 6.9,9.75l18.15,3.3c12.8,2.4 19.2,11 19.2,25.8Z" style="fill-rule:nonzero;"/>
|
|
||||||
<path d="M705.65,205.8l-23.4,-0l-22.5,-30.3l-8.55,12.15l0,18.15l-19.5,-0l0,-112.8l19.5,-0l0,71.25l27.3,-40.65l22.05,-0l-28.05,38.4l33.15,43.8Z" style="fill-rule:nonzero;"/>
|
|
||||||
<g id="Logo">
|
|
||||||
<path id="Box" d="M300,30l0,240c0,16.557 -13.443,30 -30,30l-240,-0c-16.557,-0 -30,-13.443 -30,-30l0,-240c0,-16.557 13.443,-30 30,-30l240,0c16.557,0 30,13.443 30,30Z" style="fill:url(#_Linear1);"/>
|
|
||||||
<path id="Shadow" d="M300,239.188l0,30.812c0,16.557 -13.443,30 -30,30l-56.467,-0l-102.271,-102.271c8.125,7.368 17.707,13.707 28.945,17.933c20.672,7.788 47.014,8.525 80.222,-4.85l0.184,0.941c0.164,1.054 1.433,2.17 3.152,1.494l5.516,-2.149c1.893,-0.716 1.904,-2.169 1.627,-3.111l7.103,-2.773c0.798,-0.307 3.274,-2.221 1.146,-5.23l-0.021,0l-4.37,-6.672c-0.655,-0.727 -1.668,-1.003 -2.558,-0.645l-5.608,2.16c-0.665,-0.737 -1.627,-1.013 -2.569,-0.645c-1.832,0.716 -3.674,1.443 -5.505,2.159c-0.594,0.225 -0.758,0.665 -0.645,1.239l0.358,1.749c-10.336,1.116 -27.805,0.563 -43.452,-11.727c-6.918,-5.424 -13.406,-13.099 -18.82,-23.835l-50.354,25.662l-7.947,-6.453c18.154,-9.251 36.319,-18.502 54.474,-27.754c-1.177,-3.049 -2.323,-6.355 -6.243,-21.143l-58.849,26.336l-7.097,-7.096l64.186,-28.726c-0.921,-8.328 -3.437,-32.242 -3.336,-43.002l-0,-0.01c-0.016,-1.137 -0.111,-1.697 -0.51,-2.103l159.709,159.71Zm-233.137,-111.262c0.763,0.699 2.048,0.814 3.356,0.238l0.45,-0.194c0.387,1.622 0.824,3.325 1.309,5.071l-5.115,-5.115Z" style="fill:#3b808b;"/>
|
|
||||||
<g id="Icon">
|
|
||||||
<path d="M140.801,81.591c-0.021,-1.494 -0.174,-1.996 -0.972,-2.456c-0.758,-0.502 -1.904,-0.512 -2.999,-0.031l-73.683,31.551c-1.095,0.481 -2.128,1.443 -2.681,2.486c-0.552,1.044 -0.614,2.149 -0.184,2.937l6.151,11.298l0.081,0.133c0.666,1.055 2.17,1.331 3.705,0.655l0.45,-0.194c1.924,8.064 5.076,18.155 9.282,25.349l64.186,-28.726c-0.921,-8.33 -3.439,-32.257 -3.336,-43.012l-0,0.01Z" style="fill:#fff;fill-rule:nonzero;"/>
|
|
||||||
<path d="M145.897,134.079l-61.811,27.662c3.633,6.887 8.115,14.194 13.58,21.235c18.154,-9.251 36.319,-18.502 54.474,-27.754c-1.177,-3.049 -2.323,-6.355 -6.243,-21.143Z" style="fill:#fff;fill-rule:nonzero;"/>
|
|
||||||
<path d="M239.136,199.984l-4.37,-6.672c-0.655,-0.727 -1.668,-1.003 -2.558,-0.645l-5.608,2.16c-0.665,-0.737 -1.627,-1.013 -2.569,-0.645c-1.832,0.716 -3.674,1.443 -5.505,2.159c-0.594,0.225 -0.758,0.665 -0.645,1.239l0.358,1.749c-10.336,1.116 -27.805,0.563 -43.452,-11.727c-6.918,-5.424 -13.406,-13.099 -18.82,-23.835l-52.11,26.557c9.528,10.469 21.491,19.751 36.35,25.338c20.672,7.788 47.014,8.525 80.222,-4.85l0.184,0.941c0.164,1.054 1.433,2.17 3.152,1.494l5.516,-2.149c1.893,-0.716 1.904,-2.169 1.627,-3.111l7.103,-2.773c0.798,-0.307 3.274,-2.221 1.146,-5.23l-0.021,0Z" style="fill:#fff;fill-rule:nonzero;"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.83697e-14,300,-300,1.83697e-14,160.554,0)"><stop offset="0" style="stop-color:#bdddeb;stop-opacity:1"/><stop offset="1" style="stop-color:#53a9d1;stop-opacity:1"/></linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB |
BIN
docs/_static/flask.png
vendored
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
docs/_static/flaskr.png
vendored
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/_static/logo-full.png
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/_static/no.png
vendored
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
docs/_static/pycharm-run-config.png
vendored
|
Before Width: | Height: | Size: 97 KiB |
BIN
docs/_static/touch-icon.png
vendored
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
docs/_static/yes.png
vendored
Normal file
|
After Width: | Height: | Size: 277 B |
55
docs/advanced_foreword.rst
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
.. _advanced_foreword:
|
||||||
|
|
||||||
|
Foreword for Experienced Programmers
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Thread-Locals in Flask
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
One of the design decisions in Flask was that simple tasks should be simple;
|
||||||
|
they should not take a lot of code and yet they should not limit you. Because
|
||||||
|
of that, Flask has a few design choices that some people might find surprising or
|
||||||
|
unorthodox. For example, Flask uses thread-local objects internally so that you
|
||||||
|
don’t have to pass objects around from function to function within a request in
|
||||||
|
order to stay threadsafe. This approach is convenient, but requires a valid
|
||||||
|
request context for dependency injection or when attempting to reuse code which
|
||||||
|
uses a value pegged to the request. The Flask project is honest about
|
||||||
|
thread-locals, does not hide them, and calls out in the code and documentation
|
||||||
|
where they are used.
|
||||||
|
|
||||||
|
Develop for the Web with Caution
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Always keep security in mind when building web applications.
|
||||||
|
|
||||||
|
If you write a web application, you are probably allowing users to register
|
||||||
|
and leave their data on your server. The users are entrusting you with data.
|
||||||
|
And even if you are the only user that might leave data in your application,
|
||||||
|
you still want that data to be stored securely.
|
||||||
|
|
||||||
|
Unfortunately, there are many ways the security of a web application can be
|
||||||
|
compromised. Flask protects you against one of the most common security
|
||||||
|
problems of modern web applications: cross-site scripting (XSS). Unless you
|
||||||
|
deliberately mark insecure HTML as secure, Flask and the underlying Jinja2
|
||||||
|
template engine have you covered. But there are many more ways to cause
|
||||||
|
security problems.
|
||||||
|
|
||||||
|
The documentation will warn you about aspects of web development that require
|
||||||
|
attention to security. Some of these security concerns are far more complex
|
||||||
|
than one might think, and we all sometimes underestimate the likelihood that a
|
||||||
|
vulnerability will be exploited - until a clever attacker figures out a way to
|
||||||
|
exploit our applications. And don't think that your application is not
|
||||||
|
important enough to attract an attacker. Depending on the kind of attack,
|
||||||
|
chances are that automated bots are probing for ways to fill your database with
|
||||||
|
spam, links to malicious software, and the like.
|
||||||
|
|
||||||
|
Flask is no different from any other framework in that you the developer must
|
||||||
|
build with caution, watching for exploits when building to your requirements.
|
||||||
|
|
||||||
|
Python 3 Support in Flask
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Flask, its dependencies, and most Flask extensions all support Python 3.
|
||||||
|
If you want to use Flask with Python 3 have a look at the :ref:`python3-support` page.
|
||||||
|
|
||||||
|
Continue to :ref:`installation` or the :ref:`quickstart`.
|
||||||
557
docs/api.rst
|
|
@ -1,9 +1,11 @@
|
||||||
|
.. _api:
|
||||||
|
|
||||||
API
|
API
|
||||||
===
|
===
|
||||||
|
|
||||||
.. module:: flask
|
.. module:: flask
|
||||||
|
|
||||||
This part of the documentation covers all the interfaces of Flask. For
|
This part of the documentation covers all the interfaces of Flask. For
|
||||||
parts where Flask depends on external libraries, we document the most
|
parts where Flask depends on external libraries, we document the most
|
||||||
important right here and provide links to the canonical documentation.
|
important right here and provide links to the canonical documentation.
|
||||||
|
|
||||||
|
|
@ -27,66 +29,177 @@ Incoming Request Data
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
.. autoclass:: Request
|
.. autoclass:: Request
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
|
||||||
:exclude-members: json_module
|
|
||||||
|
|
||||||
.. data:: request
|
.. attribute:: form
|
||||||
|
|
||||||
A proxy to the request data for the current request, an instance of
|
A :class:`~werkzeug.datastructures.MultiDict` with the parsed form data from ``POST``
|
||||||
:class:`.Request`.
|
or ``PUT`` requests. Please keep in mind that file uploads will not
|
||||||
|
end up here, but instead in the :attr:`files` attribute.
|
||||||
|
|
||||||
This is only available when a :doc:`request context </appcontext>` is
|
.. attribute:: args
|
||||||
active.
|
|
||||||
|
|
||||||
This is a proxy. See :ref:`context-visibility` for more information.
|
A :class:`~werkzeug.datastructures.MultiDict` with the parsed contents of the query
|
||||||
|
string. (The part in the URL after the question mark).
|
||||||
|
|
||||||
|
.. attribute:: values
|
||||||
|
|
||||||
|
A :class:`~werkzeug.datastructures.CombinedMultiDict` with the contents of both
|
||||||
|
:attr:`form` and :attr:`args`.
|
||||||
|
|
||||||
|
.. attribute:: cookies
|
||||||
|
|
||||||
|
A :class:`dict` with the contents of all cookies transmitted with
|
||||||
|
the request.
|
||||||
|
|
||||||
|
.. attribute:: stream
|
||||||
|
|
||||||
|
If the incoming form data was not encoded with a known mimetype
|
||||||
|
the data is stored unmodified in this stream for consumption. Most
|
||||||
|
of the time it is a better idea to use :attr:`data` which will give
|
||||||
|
you that data as a string. The stream only returns the data once.
|
||||||
|
|
||||||
|
.. attribute:: headers
|
||||||
|
|
||||||
|
The incoming request headers as a dictionary like object.
|
||||||
|
|
||||||
|
.. attribute:: data
|
||||||
|
|
||||||
|
Contains the incoming request data as string in case it came with
|
||||||
|
a mimetype Flask does not handle.
|
||||||
|
|
||||||
|
.. attribute:: files
|
||||||
|
|
||||||
|
A :class:`~werkzeug.datastructures.MultiDict` with files uploaded as part of a
|
||||||
|
``POST`` or ``PUT`` request. Each file is stored as
|
||||||
|
:class:`~werkzeug.datastructures.FileStorage` object. It basically behaves like a
|
||||||
|
standard file object you know from Python, with the difference that
|
||||||
|
it also has a :meth:`~werkzeug.datastructures.FileStorage.save` function that can
|
||||||
|
store the file on the filesystem.
|
||||||
|
|
||||||
|
.. attribute:: environ
|
||||||
|
|
||||||
|
The underlying WSGI environment.
|
||||||
|
|
||||||
|
.. attribute:: method
|
||||||
|
|
||||||
|
The current request method (``POST``, ``GET`` etc.)
|
||||||
|
|
||||||
|
.. attribute:: path
|
||||||
|
.. attribute:: full_path
|
||||||
|
.. attribute:: script_root
|
||||||
|
.. attribute:: url
|
||||||
|
.. attribute:: base_url
|
||||||
|
.. attribute:: url_root
|
||||||
|
|
||||||
|
Provides different ways to look at the current `IRI
|
||||||
|
<http://tools.ietf.org/html/rfc3987>`_. Imagine your application is
|
||||||
|
listening on the following application root::
|
||||||
|
|
||||||
|
http://www.example.com/myapplication
|
||||||
|
|
||||||
|
And a user requests the following URI::
|
||||||
|
|
||||||
|
http://www.example.com/myapplication/%CF%80/page.html?x=y
|
||||||
|
|
||||||
|
In this case the values of the above mentioned attributes would be
|
||||||
|
the following:
|
||||||
|
|
||||||
|
============= ======================================================
|
||||||
|
`path` ``u'/π/page.html'``
|
||||||
|
`full_path` ``u'/π/page.html?x=y'``
|
||||||
|
`script_root` ``u'/myapplication'``
|
||||||
|
`base_url` ``u'http://www.example.com/myapplication/π/page.html'``
|
||||||
|
`url` ``u'http://www.example.com/myapplication/π/page.html?x=y'``
|
||||||
|
`url_root` ``u'http://www.example.com/myapplication/'``
|
||||||
|
============= ======================================================
|
||||||
|
|
||||||
|
.. attribute:: is_xhr
|
||||||
|
|
||||||
|
``True`` if the request was triggered via a JavaScript
|
||||||
|
`XMLHttpRequest`. This only works with libraries that support the
|
||||||
|
``X-Requested-With`` header and set it to `XMLHttpRequest`.
|
||||||
|
Libraries that do that are prototype, jQuery and Mochikit and
|
||||||
|
probably some more.
|
||||||
|
|
||||||
|
.. class:: request
|
||||||
|
|
||||||
|
To access incoming request data, you can use the global `request`
|
||||||
|
object. Flask parses incoming request data for you and gives you
|
||||||
|
access to it through that global object. Internally Flask makes
|
||||||
|
sure that you always get the correct data for the active thread if you
|
||||||
|
are in a multithreaded environment.
|
||||||
|
|
||||||
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
|
|
||||||
|
The request object is an instance of a :class:`~werkzeug.wrappers.Request`
|
||||||
|
subclass and provides all of the attributes Werkzeug defines. This
|
||||||
|
just shows a quick overview of the most important ones.
|
||||||
|
|
||||||
|
|
||||||
Response Objects
|
Response Objects
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. autoclass:: flask.Response
|
.. autoclass:: flask.Response
|
||||||
:members:
|
:members: set_cookie, data, mimetype
|
||||||
:inherited-members:
|
|
||||||
:exclude-members: json_module
|
.. attribute:: headers
|
||||||
|
|
||||||
|
A :class:`~werkzeug.datastructures.Headers` object representing the response headers.
|
||||||
|
|
||||||
|
.. attribute:: status
|
||||||
|
|
||||||
|
A string with a response status.
|
||||||
|
|
||||||
|
.. attribute:: status_code
|
||||||
|
|
||||||
|
The response status as integer.
|
||||||
|
|
||||||
|
|
||||||
Sessions
|
Sessions
|
||||||
--------
|
--------
|
||||||
|
|
||||||
If you have set :attr:`Flask.secret_key` (or configured it from
|
If you have the :attr:`Flask.secret_key` set you can use sessions in Flask
|
||||||
:data:`SECRET_KEY`) you can use sessions in Flask applications. A session makes
|
applications. A session basically makes it possible to remember
|
||||||
it possible to remember information from one request to another. The way Flask
|
information from one request to another. The way Flask does this is by
|
||||||
does this is by using a signed cookie. The user can look at the session
|
using a signed cookie. So the user can look at the session contents, but
|
||||||
contents, but can't modify it unless they know the secret key, so make sure to
|
not modify it unless they know the secret key, so make sure to set that
|
||||||
set that to something complex and unguessable.
|
to something complex and unguessable.
|
||||||
|
|
||||||
To access the current session you can use the :data:`.session` proxy.
|
To access the current session you can use the :class:`session` object:
|
||||||
|
|
||||||
.. data:: session
|
.. class:: session
|
||||||
|
|
||||||
A proxy to the session data for the current request, an instance of
|
The session object works pretty much like an ordinary dict, with the
|
||||||
:class:`.SessionMixin`.
|
difference that it keeps track on modifications.
|
||||||
|
|
||||||
This is only available when a :doc:`request context </appcontext>` is
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
active.
|
|
||||||
|
|
||||||
This is a proxy. See :ref:`context-visibility` for more information.
|
The following attributes are interesting:
|
||||||
|
|
||||||
The session object works like a dict but tracks assignment and access to its
|
.. attribute:: new
|
||||||
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
|
``True`` if the session is new, ``False`` otherwise.
|
||||||
|
|
||||||
# appending to a list is not detected
|
.. attribute:: modified
|
||||||
session["numbers"].append(42)
|
|
||||||
|
``True`` if the session object detected a modification. Be advised
|
||||||
|
that modifications on mutable structures are not picked up
|
||||||
|
automatically, in that situation you have to explicitly set the
|
||||||
|
attribute to ``True`` yourself. Here an example::
|
||||||
|
|
||||||
|
# this change is not picked up because a mutable object (here
|
||||||
|
# a list) is changed.
|
||||||
|
session['objects'].append(42)
|
||||||
# so mark it as modified yourself
|
# so mark it as modified yourself
|
||||||
session.modified = True
|
session.modified = True
|
||||||
|
|
||||||
The session is persisted across requests using a cookie. By default the
|
.. attribute:: permanent
|
||||||
users's browser will clear the cookie when it is closed. Set
|
|
||||||
:attr:`~.SessionMixin.permanent` to ``True`` to persist the cookie for
|
If set to ``True`` the session lives for
|
||||||
:data:`PERMANENT_SESSION_LIFETIME`.
|
:attr:`~flask.Flask.permanent_session_lifetime` seconds. The
|
||||||
|
default is 31 days. If set to ``False`` (which is the default) the
|
||||||
|
session will be deleted when the user closes the browser.
|
||||||
|
|
||||||
|
|
||||||
Session Interface
|
Session Interface
|
||||||
|
|
@ -114,11 +227,24 @@ implementation that Flask is using.
|
||||||
.. autoclass:: SessionMixin
|
.. autoclass:: SessionMixin
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autodata:: session_json_serializer
|
||||||
|
|
||||||
|
This object provides dumping and loading methods similar to simplejson
|
||||||
|
but it also tags certain builtin Python objects that commonly appear in
|
||||||
|
sessions. Currently the following extended values are supported in
|
||||||
|
the JSON it dumps:
|
||||||
|
|
||||||
|
- :class:`~markupsafe.Markup` objects
|
||||||
|
- :class:`~uuid.UUID` objects
|
||||||
|
- :class:`~datetime.datetime` objects
|
||||||
|
- :class:`tuple`\s
|
||||||
|
|
||||||
.. admonition:: Notice
|
.. admonition:: Notice
|
||||||
|
|
||||||
The :data:`PERMANENT_SESSION_LIFETIME` config can be an integer or ``timedelta``.
|
The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer
|
||||||
The :attr:`~flask.Flask.permanent_session_lifetime` attribute is always a
|
starting with Flask 0.8. Either catch this down yourself or use
|
||||||
``timedelta``.
|
the :attr:`~flask.Flask.permanent_session_lifetime` attribute on the
|
||||||
|
app which converts the result to an integer automatically.
|
||||||
|
|
||||||
|
|
||||||
Test Client
|
Test Client
|
||||||
|
|
@ -130,15 +256,6 @@ Test Client
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Test CLI Runner
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. currentmodule:: flask.testing
|
|
||||||
|
|
||||||
.. autoclass:: FlaskCliRunner
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Application Globals
|
Application Globals
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
@ -146,30 +263,36 @@ Application Globals
|
||||||
|
|
||||||
To share data that is valid for one request only from one function to
|
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
|
another, a global variable is not good enough because it would break in
|
||||||
threaded environments. Flask provides you with a special object that
|
threaded environments. Flask provides you with a special object that
|
||||||
ensures it is only valid for the active request and that will return
|
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
|
different values for each request. In a nutshell: it does the right
|
||||||
thing, like it does for :data:`.request` and :data:`.session`.
|
thing, like it does for :class:`request` and :class:`session`.
|
||||||
|
|
||||||
.. data:: g
|
.. data:: g
|
||||||
|
|
||||||
A proxy to a namespace object used to store data during a single request or
|
Just store on this whatever you want. For example a database
|
||||||
app context. An instance of :attr:`.Flask.app_ctx_globals_class`, which
|
connection or the user that is currently logged in.
|
||||||
defaults to :class:`._AppCtxGlobals`.
|
|
||||||
|
|
||||||
This is a good place to store resources during a request. For example, a
|
Starting with Flask 0.10 this is stored on the application context and
|
||||||
:meth:`~.Flask.before_request` function could load a user object from a
|
no longer on the request context which means it becomes available if
|
||||||
session id, then set ``g.user`` to be used in the view function.
|
only the application context is bound and not yet a request. This
|
||||||
|
is especially useful when combined with the :ref:`faking-resources`
|
||||||
|
pattern for testing.
|
||||||
|
|
||||||
This is only available when an :doc:`app context </appcontext>` is active.
|
Additionally as of 0.10 you can use the :meth:`get` method to
|
||||||
|
get an attribute or ``None`` (or the second argument) if it's not set.
|
||||||
|
These two usages are now equivalent::
|
||||||
|
|
||||||
This is a proxy. See :ref:`context-visibility` for more information.
|
user = getattr(flask.g, 'user', None)
|
||||||
|
user = flask.g.get('user', None)
|
||||||
|
|
||||||
.. versionchanged:: 0.10
|
It's now also possible to use the ``in`` operator on it to see if an
|
||||||
Bound to the application context instead of the request context.
|
attribute is defined and it yields all keys on iteration.
|
||||||
|
|
||||||
.. autoclass:: flask.ctx._AppCtxGlobals
|
As of 0.11 you can use :meth:`pop` and :meth:`setdefault` in the same
|
||||||
:members:
|
way you would use them on a dictionary.
|
||||||
|
|
||||||
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
|
|
||||||
|
|
||||||
Useful Functions and Classes
|
Useful Functions and Classes
|
||||||
|
|
@ -177,16 +300,13 @@ Useful Functions and Classes
|
||||||
|
|
||||||
.. data:: current_app
|
.. data:: current_app
|
||||||
|
|
||||||
A proxy to the :class:`.Flask` application handling the current request or
|
Points to the application handling the request. This is useful for
|
||||||
other activity.
|
extensions that want to support multiple applications running side
|
||||||
|
by side. This is powered by the application context and not by the
|
||||||
|
request context, so you can change the value of this proxy by
|
||||||
|
using the :meth:`~flask.Flask.app_context` method.
|
||||||
|
|
||||||
This is useful to access the application without needing to import it, or if
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
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:: has_request_context
|
||||||
|
|
||||||
|
|
@ -208,6 +328,12 @@ Useful Functions and Classes
|
||||||
|
|
||||||
.. autofunction:: send_from_directory
|
.. autofunction:: send_from_directory
|
||||||
|
|
||||||
|
.. autofunction:: safe_join
|
||||||
|
|
||||||
|
.. autofunction:: escape
|
||||||
|
|
||||||
|
.. autoclass:: Markup
|
||||||
|
:members: escape, unescape, striptags
|
||||||
|
|
||||||
Message Flashing
|
Message Flashing
|
||||||
----------------
|
----------------
|
||||||
|
|
@ -216,29 +342,58 @@ Message Flashing
|
||||||
|
|
||||||
.. autofunction:: get_flashed_messages
|
.. autofunction:: get_flashed_messages
|
||||||
|
|
||||||
|
|
||||||
JSON Support
|
JSON Support
|
||||||
------------
|
------------
|
||||||
|
|
||||||
.. module:: flask.json
|
.. module:: flask.json
|
||||||
|
|
||||||
Flask uses Python's built-in :mod:`json` module for handling JSON by
|
Flask uses ``simplejson`` for the JSON implementation. Since simplejson
|
||||||
default. The JSON implementation can be changed by assigning a different
|
is provided by both the standard library as well as extension, Flask will
|
||||||
provider to :attr:`flask.Flask.json_provider_class` or
|
try simplejson first and then fall back to the stdlib json module. On top
|
||||||
:attr:`flask.Flask.json`. The functions provided by ``flask.json`` will
|
of that it will delegate access to the current application's JSON encoders
|
||||||
use methods on ``app.json`` if an app context is active.
|
and decoders for easier customization.
|
||||||
|
|
||||||
Jinja's ``|tojson`` filter is configured to use the app's JSON provider.
|
So for starters instead of doing::
|
||||||
The filter marks the output with ``|safe``. Use it to render data inside
|
|
||||||
HTML ``<script>`` tags.
|
try:
|
||||||
|
import simplejson as json
|
||||||
|
except ImportError:
|
||||||
|
import json
|
||||||
|
|
||||||
|
You can instead just do this::
|
||||||
|
|
||||||
|
from flask import json
|
||||||
|
|
||||||
|
For usage examples, read the :mod:`json` documentation in the standard
|
||||||
|
library. The following extensions are by default applied to the stdlib's
|
||||||
|
JSON module:
|
||||||
|
|
||||||
|
1. ``datetime`` objects are serialized as :rfc:`822` strings.
|
||||||
|
2. Any object with an ``__html__`` method (like :class:`~flask.Markup`)
|
||||||
|
will have that method called and then the return value is serialized
|
||||||
|
as string.
|
||||||
|
|
||||||
|
The :func:`~htmlsafe_dumps` function of this json module is also available
|
||||||
|
as filter called ``|tojson`` in Jinja2. Note that inside ``script``
|
||||||
|
tags no escaping must take place, so make sure to disable escaping
|
||||||
|
with ``|safe`` if you intend to use it inside ``script`` tags unless
|
||||||
|
you are using Flask 0.10 which implies that:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
<script>
|
<script type=text/javascript>
|
||||||
const names = {{ names|tojson }};
|
doSomethingWith({{ user.username|tojson|safe }});
|
||||||
renderChart(names, {{ axis_data|tojson }});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
.. admonition:: Auto-Sort JSON Keys
|
||||||
|
|
||||||
|
The configuration variable ``JSON_SORT_KEYS`` (:ref:`config`) can be
|
||||||
|
set to false to stop Flask from auto-sorting keys. By default sorting
|
||||||
|
is enabled and outside of the app context sorting is turned on.
|
||||||
|
|
||||||
|
Notice that disabling key sorting can cause issues when using content
|
||||||
|
based HTTP caches and Python's hash randomization feature.
|
||||||
|
|
||||||
.. autofunction:: jsonify
|
.. autofunction:: jsonify
|
||||||
|
|
||||||
.. autofunction:: dumps
|
.. autofunction:: dumps
|
||||||
|
|
@ -249,16 +404,11 @@ HTML ``<script>`` tags.
|
||||||
|
|
||||||
.. autofunction:: load
|
.. autofunction:: load
|
||||||
|
|
||||||
.. autoclass:: flask.json.provider.JSONProvider
|
.. autoclass:: JSONEncoder
|
||||||
:members:
|
:members:
|
||||||
:member-order: bysource
|
|
||||||
|
|
||||||
.. autoclass:: flask.json.provider.DefaultJSONProvider
|
|
||||||
:members:
|
|
||||||
:member-order: bysource
|
|
||||||
|
|
||||||
.. automodule:: flask.json.tag
|
|
||||||
|
|
||||||
|
.. autoclass:: JSONDecoder
|
||||||
|
:members:
|
||||||
|
|
||||||
Template Rendering
|
Template Rendering
|
||||||
------------------
|
------------------
|
||||||
|
|
@ -269,10 +419,6 @@ Template Rendering
|
||||||
|
|
||||||
.. autofunction:: render_template_string
|
.. autofunction:: render_template_string
|
||||||
|
|
||||||
.. autofunction:: stream_template
|
|
||||||
|
|
||||||
.. autofunction:: stream_template_string
|
|
||||||
|
|
||||||
.. autofunction:: get_template_attribute
|
.. autofunction:: get_template_attribute
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
|
|
@ -281,6 +427,22 @@ Configuration
|
||||||
.. autoclass:: Config
|
.. autoclass:: Config
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Extensions
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. data:: flask.ext
|
||||||
|
|
||||||
|
This module acts as redirect import module to Flask extensions. It was
|
||||||
|
added in 0.8 as the canonical way to import Flask extensions and makes
|
||||||
|
it possible for us to have more flexibility in how we distribute
|
||||||
|
extensions.
|
||||||
|
|
||||||
|
If you want to use an extension named “Flask-Foo” you would import it
|
||||||
|
from :data:`~flask.ext` as follows::
|
||||||
|
|
||||||
|
from flask.ext import foo
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
|
||||||
Stream Helpers
|
Stream Helpers
|
||||||
--------------
|
--------------
|
||||||
|
|
@ -290,31 +452,55 @@ Stream Helpers
|
||||||
Useful Internals
|
Useful Internals
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
.. autoclass:: flask.ctx.RequestContext
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. data:: _request_ctx_stack
|
||||||
|
|
||||||
|
The internal :class:`~werkzeug.local.LocalStack` that is used to implement
|
||||||
|
all the context local objects used in Flask. This is a documented
|
||||||
|
instance and can be used by extensions and application code but the
|
||||||
|
use is discouraged in general.
|
||||||
|
|
||||||
|
The following attributes are always present on each layer of the
|
||||||
|
stack:
|
||||||
|
|
||||||
|
`app`
|
||||||
|
the active Flask application.
|
||||||
|
|
||||||
|
`url_adapter`
|
||||||
|
the URL adapter that was used to match the request.
|
||||||
|
|
||||||
|
`request`
|
||||||
|
the current request object.
|
||||||
|
|
||||||
|
`session`
|
||||||
|
the active session object.
|
||||||
|
|
||||||
|
`g`
|
||||||
|
an object with all the attributes of the :data:`flask.g` object.
|
||||||
|
|
||||||
|
`flashes`
|
||||||
|
an internal cache for the flashed messages.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
from flask import _request_ctx_stack
|
||||||
|
|
||||||
|
def get_session():
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
if ctx is not None:
|
||||||
|
return ctx.session
|
||||||
|
|
||||||
.. autoclass:: flask.ctx.AppContext
|
.. autoclass:: flask.ctx.AppContext
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. data:: flask.globals.app_ctx
|
.. data:: _app_ctx_stack
|
||||||
|
|
||||||
A proxy to the active :class:`.AppContext`.
|
Works similar to the request context but only binds the application.
|
||||||
|
This is mainly there for extensions to store data.
|
||||||
|
|
||||||
This is an internal object that is essential to how Flask handles requests.
|
.. versionadded:: 0.9
|
||||||
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
|
.. autoclass:: flask.blueprints.BlueprintSetupState
|
||||||
:members:
|
:members:
|
||||||
|
|
@ -324,13 +510,18 @@ Useful Internals
|
||||||
Signals
|
Signals
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introduction.
|
.. versionadded:: 0.6
|
||||||
|
|
||||||
.. _blinker: https://blinker.readthedocs.io/
|
.. data:: signals.signals_available
|
||||||
|
|
||||||
|
``True`` if the signaling system is available. This is the case
|
||||||
|
when `blinker`_ is installed.
|
||||||
|
|
||||||
|
The following signals exist in Flask:
|
||||||
|
|
||||||
.. data:: template_rendered
|
.. data:: template_rendered
|
||||||
|
|
||||||
This signal is sent when a template was successfully rendered. The
|
This signal is sent when a template was successfully rendered. The
|
||||||
signal is invoked with the instance of the template as `template`
|
signal is invoked with the instance of the template as `template`
|
||||||
and the context as dictionary (named `context`).
|
and the context as dictionary (named `context`).
|
||||||
|
|
||||||
|
|
@ -364,7 +555,7 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
||||||
.. data:: request_started
|
.. data:: request_started
|
||||||
|
|
||||||
This signal is sent when the request context is set up, before
|
This signal is sent when the request context is set up, before
|
||||||
any request processing happens. Because the request context is already
|
any request processing happens. Because the request context is already
|
||||||
bound, the subscriber can access the request with the standard global
|
bound, the subscriber can access the request with the standard global
|
||||||
proxies such as :class:`~flask.request`.
|
proxies such as :class:`~flask.request`.
|
||||||
|
|
||||||
|
|
@ -384,7 +575,7 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
||||||
Example subscriber::
|
Example subscriber::
|
||||||
|
|
||||||
def log_response(sender, response, **extra):
|
def log_response(sender, response, **extra):
|
||||||
sender.logger.debug('Request context is about to close down. '
|
sender.logger.debug('Request context is about to close down. '
|
||||||
'Response: %s', response)
|
'Response: %s', response)
|
||||||
|
|
||||||
from flask import request_finished
|
from flask import request_finished
|
||||||
|
|
@ -392,37 +583,23 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
||||||
|
|
||||||
.. data:: got_request_exception
|
.. data:: got_request_exception
|
||||||
|
|
||||||
This signal is sent when an unhandled exception happens during
|
This signal is sent when an exception happens during request processing.
|
||||||
request processing, including when debugging. The exception is
|
It is sent *before* the standard exception handling kicks in and even
|
||||||
passed to the subscriber as ``exception``.
|
in debug mode, where no exception handling happens. The exception
|
||||||
|
itself is passed to the subscriber as `exception`.
|
||||||
|
|
||||||
This signal is not sent for
|
Example subscriber::
|
||||||
: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
|
def log_exception(sender, exception, **extra):
|
||||||
``SecurityException`` was raised:
|
sender.logger.debug('Got exception during processing: %s', exception)
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from flask import got_request_exception
|
from flask import got_request_exception
|
||||||
|
got_request_exception.connect(log_exception, app)
|
||||||
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
|
.. data:: request_tearing_down
|
||||||
|
|
||||||
This signal is sent when the request is tearing down. This is always
|
This signal is sent when the request is tearing down. This is always
|
||||||
called, even if an exception is caused. Currently functions listening
|
called, even if an exception is caused. Currently functions listening
|
||||||
to this signal are called after the regular teardown handlers, but this
|
to this signal are called after the regular teardown handlers, but this
|
||||||
is not something you can rely on.
|
is not something you can rely on.
|
||||||
|
|
||||||
|
|
@ -440,8 +617,8 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
||||||
|
|
||||||
.. data:: appcontext_tearing_down
|
.. data:: appcontext_tearing_down
|
||||||
|
|
||||||
This signal is sent when the app context is tearing down. This is always
|
This signal is sent when the app context is tearing down. This is always
|
||||||
called, even if an exception is caused. Currently functions listening
|
called, even if an exception is caused. Currently functions listening
|
||||||
to this signal are called after the regular teardown handlers, but this
|
to this signal are called after the regular teardown handlers, but this
|
||||||
is not something you can rely on.
|
is not something you can rely on.
|
||||||
|
|
||||||
|
|
@ -458,9 +635,9 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
||||||
|
|
||||||
.. data:: appcontext_pushed
|
.. data:: appcontext_pushed
|
||||||
|
|
||||||
This signal is sent when an application context is pushed. The sender
|
This signal is sent when an application context is pushed. The sender
|
||||||
is the application. This is usually useful for unittests in order to
|
is the application. This is usually useful for unittests in order to
|
||||||
temporarily hook in information. For instance it can be used to
|
temporarily hook in information. For instance it can be used to
|
||||||
set a resource early onto the `g` object.
|
set a resource early onto the `g` object.
|
||||||
|
|
||||||
Example usage::
|
Example usage::
|
||||||
|
|
@ -487,15 +664,16 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
||||||
|
|
||||||
.. data:: appcontext_popped
|
.. data:: appcontext_popped
|
||||||
|
|
||||||
This signal is sent when an application context is popped. The sender
|
This signal is sent when an application context is popped. The sender
|
||||||
is the application. This usually falls in line with the
|
is the application. This usually falls in line with the
|
||||||
:data:`appcontext_tearing_down` signal.
|
:data:`appcontext_tearing_down` signal.
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
|
||||||
.. data:: message_flashed
|
.. data:: message_flashed
|
||||||
|
|
||||||
This signal is sent when the application is flashing a message. The
|
This signal is sent when the application is flashing a message. The
|
||||||
messages is sent as `message` keyword argument and the category as
|
messages is sent as `message` keyword argument and the category as
|
||||||
`category`.
|
`category`.
|
||||||
|
|
||||||
|
|
@ -510,6 +688,23 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
.. class:: signals.Namespace
|
||||||
|
|
||||||
|
An alias for :class:`blinker.base.Namespace` if blinker is available,
|
||||||
|
otherwise a dummy class that creates fake signals. This class is
|
||||||
|
available for Flask extensions that want to provide the same fallback
|
||||||
|
system as Flask itself.
|
||||||
|
|
||||||
|
.. method:: signal(name, doc=None)
|
||||||
|
|
||||||
|
Creates a new signal for this namespace if blinker is available,
|
||||||
|
otherwise returns a fake signal that has a send method that will
|
||||||
|
do nothing but will fail with a :exc:`RuntimeError` for all other
|
||||||
|
operations, including connecting.
|
||||||
|
|
||||||
|
|
||||||
|
.. _blinker: https://pypi.org/project/blinker/
|
||||||
|
|
||||||
|
|
||||||
Class-Based Views
|
Class-Based Views
|
||||||
-----------------
|
-----------------
|
||||||
|
|
@ -537,7 +732,7 @@ Generally there are three ways to define rules for the routing system:
|
||||||
which is exposed as :attr:`flask.Flask.url_map`.
|
which is exposed as :attr:`flask.Flask.url_map`.
|
||||||
|
|
||||||
Variable parts in the route can be specified with angular brackets
|
Variable parts in the route can be specified with angular brackets
|
||||||
(``/user/<username>``). By default a variable part in the URL accepts any
|
(``/user/<username>``). By default a variable part in the URL accepts any
|
||||||
string without a slash however a different converter can be specified as
|
string without a slash however a different converter can be specified as
|
||||||
well by using ``<converter:name>``.
|
well by using ``<converter:name>``.
|
||||||
|
|
||||||
|
|
@ -571,7 +766,7 @@ Here are some examples::
|
||||||
pass
|
pass
|
||||||
|
|
||||||
An important detail to keep in mind is how Flask deals with trailing
|
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
|
slashes. The idea is to keep each URL unique so the following rules
|
||||||
apply:
|
apply:
|
||||||
|
|
||||||
1. If a rule ends with a slash and is requested without a slash by the
|
1. If a rule ends with a slash and is requested without a slash by the
|
||||||
|
|
@ -580,11 +775,11 @@ apply:
|
||||||
2. If a rule does not end with a trailing slash and the user requests the
|
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.
|
page with a trailing slash, a 404 not found is raised.
|
||||||
|
|
||||||
This is consistent with how web servers deal with static files. This
|
This is consistent with how web servers deal with static files. This
|
||||||
also makes it possible to use relative link targets safely.
|
also makes it possible to use relative link targets safely.
|
||||||
|
|
||||||
You can also define multiple rules for the same function. They have to be
|
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
|
unique however. Defaults can also be specified. Here for example is a
|
||||||
definition for a URL that accepts an optional page::
|
definition for a URL that accepts an optional page::
|
||||||
|
|
||||||
@app.route('/users/', defaults={'page': 1})
|
@app.route('/users/', defaults={'page': 1})
|
||||||
|
|
@ -593,49 +788,39 @@ definition for a URL that accepts an optional page::
|
||||||
pass
|
pass
|
||||||
|
|
||||||
This specifies that ``/users/`` will be the URL for page one and
|
This specifies that ``/users/`` will be the URL for page one and
|
||||||
``/users/page/N`` will be the URL for page ``N``.
|
``/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 308 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
|
Here are the parameters that :meth:`~flask.Flask.route` and
|
||||||
:meth:`~flask.Flask.add_url_rule` accept. The only difference is that
|
:meth:`~flask.Flask.add_url_rule` accept. The only difference is that
|
||||||
with the route parameter the view function is defined with the decorator
|
with the route parameter the view function is defined with the decorator
|
||||||
instead of the `view_func` parameter.
|
instead of the `view_func` parameter.
|
||||||
|
|
||||||
=============== ==========================================================
|
=============== ==========================================================
|
||||||
`rule` the URL rule as string
|
`rule` the URL rule as string
|
||||||
`endpoint` the endpoint for the registered URL rule. Flask itself
|
`endpoint` the endpoint for the registered URL rule. Flask itself
|
||||||
assumes that the name of the view function is the name
|
assumes that the name of the view function is the name
|
||||||
of the endpoint if not explicitly stated.
|
of the endpoint if not explicitly stated.
|
||||||
`view_func` the function to call when serving a request to the
|
`view_func` the function to call when serving a request to the
|
||||||
provided endpoint. If this is not provided one can
|
provided endpoint. If this is not provided one can
|
||||||
specify the function later by storing it in the
|
specify the function later by storing it in the
|
||||||
:attr:`~flask.Flask.view_functions` dictionary with the
|
:attr:`~flask.Flask.view_functions` dictionary with the
|
||||||
endpoint as key.
|
endpoint as key.
|
||||||
`defaults` A dictionary with defaults for this rule. See the
|
`defaults` A dictionary with defaults for this rule. See the
|
||||||
example above for how defaults work.
|
example above for how defaults work.
|
||||||
`subdomain` specifies the rule for the subdomain in case subdomain
|
`subdomain` specifies the rule for the subdomain in case subdomain
|
||||||
matching is in use. If not specified the default
|
matching is in use. If not specified the default
|
||||||
subdomain is assumed.
|
subdomain is assumed.
|
||||||
`**options` the options to be forwarded to the underlying
|
`**options` the options to be forwarded to the underlying
|
||||||
:class:`~werkzeug.routing.Rule` object. A change to
|
:class:`~werkzeug.routing.Rule` object. A change to
|
||||||
Werkzeug is handling of method options. methods is a list
|
Werkzeug is handling of method options. methods is a list
|
||||||
of methods this rule should be limited to (``GET``, ``POST``
|
of methods this rule should be limited to (``GET``, ``POST``
|
||||||
etc.). By default a rule just listens for ``GET`` (and
|
etc.). By default a rule just listens for ``GET`` (and
|
||||||
implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is
|
implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is
|
||||||
implicitly added and handled by the standard request
|
implicitly added and handled by the standard request
|
||||||
handling. They have to be specified as keyword arguments.
|
handling. They have to be specified as keyword arguments.
|
||||||
=============== ==========================================================
|
=============== ==========================================================
|
||||||
|
|
||||||
|
.. _view-func-options:
|
||||||
|
|
||||||
View Function Options
|
View Function Options
|
||||||
---------------------
|
---------------------
|
||||||
|
|
@ -645,19 +830,19 @@ customize behavior the view function would normally not have control over.
|
||||||
The following attributes can be provided optionally to either override
|
The following attributes can be provided optionally to either override
|
||||||
some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior:
|
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
|
- `__name__`: The name of a function is by default used as endpoint. If
|
||||||
endpoint is provided explicitly this value is used. Additionally this
|
endpoint is provided explicitly this value is used. Additionally this
|
||||||
will be prefixed with the name of the blueprint by default which
|
will be prefixed with the name of the blueprint by default which
|
||||||
cannot be customized from the function itself.
|
cannot be customized from the function itself.
|
||||||
|
|
||||||
- `methods`: If methods are not provided when the URL rule is added,
|
- `methods`: If methods are not provided when the URL rule is added,
|
||||||
Flask will look on the view function object itself if a `methods`
|
Flask will look on the view function object itself if a `methods`
|
||||||
attribute exists. If it does, it will pull the information for the
|
attribute exists. If it does, it will pull the information for the
|
||||||
methods from there.
|
methods from there.
|
||||||
|
|
||||||
- `provide_automatic_options`: if this attribute is set Flask will
|
- `provide_automatic_options`: if this attribute is set Flask will
|
||||||
either force enable or disable the automatic implementation of the
|
either force enable or disable the automatic implementation of the
|
||||||
HTTP ``OPTIONS`` response. This can be useful when working with
|
HTTP ``OPTIONS`` response. This can be useful when working with
|
||||||
decorators that want to customize the ``OPTIONS`` response on a per-view
|
decorators that want to customize the ``OPTIONS`` response on a per-view
|
||||||
basis.
|
basis.
|
||||||
|
|
||||||
|
|
@ -694,8 +879,6 @@ Command Line Interface
|
||||||
.. autoclass:: ScriptInfo
|
.. autoclass:: ScriptInfo
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autofunction:: load_dotenv
|
|
||||||
|
|
||||||
.. autofunction:: with_appcontext
|
.. autofunction:: with_appcontext
|
||||||
|
|
||||||
.. autofunction:: pass_script_info
|
.. autofunction:: pass_script_info
|
||||||
|
|
|
||||||
|
|
@ -1,187 +1,138 @@
|
||||||
The App and Request Context
|
.. _app-context:
|
||||||
===========================
|
|
||||||
|
|
||||||
The context keeps track of data and objects during a request, CLI command, or
|
The Application Context
|
||||||
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"
|
.. versionadded:: 0.9
|
||||||
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.
|
|
||||||
|
|
||||||
|
One of the design ideas behind Flask is that there are two different
|
||||||
|
“states” in which code is executed. The application setup state in which
|
||||||
|
the application implicitly is on the module level. It starts when the
|
||||||
|
:class:`Flask` object is instantiated, and it implicitly ends when the
|
||||||
|
first request comes in. While the application is in this state a few
|
||||||
|
assumptions are true:
|
||||||
|
|
||||||
Purpose of the Context
|
- the programmer can modify the application object safely.
|
||||||
----------------------
|
- no request handling happened so far
|
||||||
|
- you have to have a reference to the application object in order to
|
||||||
|
modify it, there is no magic proxy that can give you a reference to
|
||||||
|
the application object you're currently creating or modifying.
|
||||||
|
|
||||||
The context and proxies help solve two development issues: circular imports, and
|
In contrast, during request handling, a couple of other rules exist:
|
||||||
passing around global data during a request.
|
|
||||||
|
|
||||||
The :class:`.Flask` application object has attributes, such as
|
- while a request is active, the context local objects
|
||||||
:attr:`~.Flask.config`, that are useful to access within views and other
|
(:data:`flask.request` and others) point to the current request.
|
||||||
functions. However, importing the ``app`` instance within the modules in your
|
- any code can get hold of these objects at any time.
|
||||||
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.
|
There is a third state which is sitting in between a little bit.
|
||||||
Because a *worker* handles only one request at a time, the request data can be
|
Sometimes you are dealing with an application in a way that is similar to
|
||||||
considered global to that worker during that request. Passing it as an argument
|
how you interact with applications during request handling; just that there
|
||||||
through every function during the request becomes verbose and redundant.
|
is no request active. Consider, for instance, that you're sitting in an
|
||||||
|
interactive Python shell and interacting with the application, or a
|
||||||
|
command line application.
|
||||||
|
|
||||||
Flask solves these issues with the *active context* pattern. Rather than
|
The application context is what powers the :data:`~flask.current_app`
|
||||||
importing an ``app`` directly, or having to pass it and the request through to
|
context local.
|
||||||
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.
|
|
||||||
|
|
||||||
|
Purpose of the Application Context
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
Context During Setup
|
The main reason for the application's context existence is that in the
|
||||||
--------------------
|
past a bunch of functionality was attached to the request context for lack
|
||||||
|
of a better solution. Since one of the pillars of Flask's design is that
|
||||||
|
you can have more than one application in the same Python process.
|
||||||
|
|
||||||
If you try to access :data:`.current_app`, :data:`.g`, or anything that uses it,
|
So how does the code find the “right” application? In the past we
|
||||||
outside an app context, you'll get this error message:
|
recommended passing applications around explicitly, but that caused issues
|
||||||
|
with libraries that were not designed with that in mind.
|
||||||
|
|
||||||
.. code-block:: pytb
|
A common workaround for that problem was to use the
|
||||||
|
:data:`~flask.current_app` proxy later on, which was bound to the current
|
||||||
|
request's application reference. Since creating such a request context is
|
||||||
|
an unnecessarily expensive operation in case there is no request around,
|
||||||
|
the application context was introduced.
|
||||||
|
|
||||||
|
Creating an Application Context
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
There are two ways to make an application context. The first one is
|
||||||
|
implicit: whenever a request context is pushed, an application context
|
||||||
|
will be created alongside if this is necessary. As a result, you can
|
||||||
|
ignore the existence of the application context unless you need it.
|
||||||
|
|
||||||
|
The second way is the explicit way using the
|
||||||
|
:meth:`~flask.Flask.app_context` method::
|
||||||
|
|
||||||
|
from flask import Flask, current_app
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
with app.app_context():
|
||||||
|
# within this block, current_app points to app.
|
||||||
|
print current_app.name
|
||||||
|
|
||||||
|
The application context is also used by the :func:`~flask.url_for`
|
||||||
|
function in case a ``SERVER_NAME`` was configured. This allows you to
|
||||||
|
generate URLs even in the absence of a request.
|
||||||
|
|
||||||
|
If no request context has been pushed and an application context has
|
||||||
|
not been explicitly set, a ``RuntimeError`` will be raised. ::
|
||||||
|
|
||||||
RuntimeError: Working outside of application context.
|
RuntimeError: Working outside of application context.
|
||||||
|
|
||||||
Attempted to use functionality that expected a current application to be
|
Locality of the Context
|
||||||
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
|
The application context is created and destroyed as necessary. It never
|
||||||
initializing an extension, you can push a context manually since you have direct
|
moves between threads and it will not be shared between requests. As such
|
||||||
access to the ``app``. Use :meth:`.Flask.app_context` in a ``with`` block.
|
it is the perfect place to store database connection information and other
|
||||||
|
things. The internal stack object is called :data:`flask._app_ctx_stack`.
|
||||||
|
Extensions are free to store additional information on the topmost level,
|
||||||
|
assuming they pick a sufficiently unique name and should put their
|
||||||
|
information there, instead of on the :data:`flask.g` object which is reserved
|
||||||
|
for user code.
|
||||||
|
|
||||||
.. code-block:: python
|
For more information about that, see :ref:`extension-dev`.
|
||||||
|
|
||||||
def create_app():
|
Context Usage
|
||||||
app = Flask(__name__)
|
-------------
|
||||||
|
|
||||||
with app.app_context():
|
The context is typically used to cache resources that need to be created
|
||||||
init_db()
|
on a per-request or usage case. For instance, database connections are
|
||||||
|
destined to go there. When storing things on the application context
|
||||||
|
unique names should be chosen as this is a place that is shared between
|
||||||
|
Flask applications and extensions.
|
||||||
|
|
||||||
return app
|
The most common usage is to split resource management into two parts:
|
||||||
|
|
||||||
If you see that error somewhere else in your code not related to setting up the
|
1. an implicit resource caching on the context.
|
||||||
application, it most likely indicates that you should move that code into a view
|
2. a context teardown based resource deallocation.
|
||||||
function or CLI command.
|
|
||||||
|
|
||||||
|
Generally there would be a ``get_X()`` function that creates resource
|
||||||
|
``X`` if it does not exist yet and otherwise returns the same resource,
|
||||||
|
and a ``teardown_X()`` function that is registered as teardown handler.
|
||||||
|
|
||||||
Context During Testing
|
This is an example that connects to a database::
|
||||||
----------------------
|
|
||||||
|
|
||||||
See :doc:`/testing` for detailed information about managing the context during
|
import sqlite3
|
||||||
tests.
|
from flask import g
|
||||||
|
|
||||||
If you try to access :data:`.request`, :data:`.session`, or anything that uses
|
def get_db():
|
||||||
it, outside a request context, you'll get this error message:
|
db = getattr(g, '_database', None)
|
||||||
|
if db is None:
|
||||||
|
db = g._database = connect_to_database()
|
||||||
|
return db
|
||||||
|
|
||||||
.. code-block:: pytb
|
@app.teardown_appcontext
|
||||||
|
def teardown_db(exception):
|
||||||
|
db = getattr(g, '_database', None)
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
RuntimeError: Working outside of request context.
|
The first time ``get_db()`` is called the connection will be established.
|
||||||
|
To make this implicit a :class:`~werkzeug.local.LocalProxy` can be used::
|
||||||
|
|
||||||
Attempted to use functionality that expected an active HTTP request. See the
|
from werkzeug.local import LocalProxy
|
||||||
documentation on request context for more information.
|
db = LocalProxy(get_db)
|
||||||
|
|
||||||
This will probably only happen during tests. If you see that error somewhere
|
That way a user can directly access ``db`` which internally calls
|
||||||
else in your code not related to testing, it most likely indicates that you
|
``get_db()``.
|
||||||
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 request 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. All callbacks are
|
|
||||||
called even if any raise an error.
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
.. _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.
|
|
||||||
|
|
||||||
|
|
||||||
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 :doc:`run Flask with Gevent </gevent>` to
|
|
||||||
get many of the benefits of async request handling. Gevent patches low-level
|
|
||||||
Python functions to accomplish this, whereas ``async``/``await`` and ASGI use
|
|
||||||
standard, modern Python capabilities. Deciding whether you should use gevent
|
|
||||||
with Flask, or Quart, or something else is ultimately up to understanding the
|
|
||||||
specific needs of your project.
|
|
||||||
|
|
||||||
.. _Quart: https://quart.palletsprojects.com
|
|
||||||
.. _ASGI: https://asgi.readthedocs.io
|
|
||||||
|
|
||||||
|
|
||||||
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. See :ref:`gevent-asyncio` for an example.
|
|
||||||
101
docs/becomingbig.rst
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
.. _becomingbig:
|
||||||
|
|
||||||
|
Becoming Big
|
||||||
|
============
|
||||||
|
|
||||||
|
Here are your options when growing your codebase or scaling your application.
|
||||||
|
|
||||||
|
Read the Source.
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Flask started in part to demonstrate how to build your own framework on top of
|
||||||
|
existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it
|
||||||
|
developed, it became useful to a wide audience. As you grow your codebase,
|
||||||
|
don't just use Flask -- understand it. Read the source. Flask's code is
|
||||||
|
written to be read; its documentation is published so you can use its internal
|
||||||
|
APIs. Flask sticks to documented APIs in upstream libraries, and documents its
|
||||||
|
internal utilities so that you can find the hook points needed for your
|
||||||
|
project.
|
||||||
|
|
||||||
|
Hook. Extend.
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The :ref:`api` docs are full of available overrides, hook points, and
|
||||||
|
:ref:`signals`. You can provide custom classes for things like the request and
|
||||||
|
response objects. Dig deeper on the APIs you use, and look for the
|
||||||
|
customizations which are available out of the box in a Flask release. Look for
|
||||||
|
ways in which your project can be refactored into a collection of utilities and
|
||||||
|
Flask extensions. Explore the many `extensions
|
||||||
|
<http://flask.pocoo.org/extensions/>`_ in the community, and look for patterns to
|
||||||
|
build your own extensions if you do not find the tools you need.
|
||||||
|
|
||||||
|
Subclass.
|
||||||
|
---------
|
||||||
|
|
||||||
|
The :class:`~flask.Flask` class has many methods designed for subclassing. You
|
||||||
|
can quickly add or customize behavior by subclassing :class:`~flask.Flask` (see
|
||||||
|
the linked method docs) and using that subclass wherever you instantiate an
|
||||||
|
application class. This works well with :ref:`app-factories`. See :doc:`/patterns/subclassing` for an example.
|
||||||
|
|
||||||
|
Wrap with middleware.
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The :ref:`app-dispatch` chapter shows in detail how to apply middleware. You
|
||||||
|
can introduce WSGI middleware to wrap your Flask instances and introduce fixes
|
||||||
|
and changes at the layer between your Flask application and your HTTP
|
||||||
|
server. Werkzeug includes several `middlewares
|
||||||
|
<http://werkzeug.pocoo.org/docs/middlewares/>`_.
|
||||||
|
|
||||||
|
Fork.
|
||||||
|
-----
|
||||||
|
|
||||||
|
If none of the above options work, fork Flask. The majority of code of Flask
|
||||||
|
is within Werkzeug and Jinja2. These libraries do the majority of the work.
|
||||||
|
Flask is just the paste that glues those together. For every project there is
|
||||||
|
the point where the underlying framework gets in the way (due to assumptions
|
||||||
|
the original developers had). This is natural because if this would not be the
|
||||||
|
case, the framework would be a very complex system to begin with which causes a
|
||||||
|
steep learning curve and a lot of user frustration.
|
||||||
|
|
||||||
|
This is not unique to Flask. Many people use patched and modified
|
||||||
|
versions of their framework to counter shortcomings. This idea is also
|
||||||
|
reflected in the license of Flask. You don't have to contribute any
|
||||||
|
changes back if you decide to modify the framework.
|
||||||
|
|
||||||
|
The downside of forking is of course that Flask extensions will most
|
||||||
|
likely break because the new framework has a different import name.
|
||||||
|
Furthermore integrating upstream changes can be a complex process,
|
||||||
|
depending on the number of changes. Because of that, forking should be
|
||||||
|
the very last resort.
|
||||||
|
|
||||||
|
Scale like a pro.
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
For many web applications the complexity of the code is less an issue than
|
||||||
|
the scaling for the number of users or data entries expected. Flask by
|
||||||
|
itself is only limited in terms of scaling by your application code, the
|
||||||
|
data store you want to use and the Python implementation and webserver you
|
||||||
|
are running on.
|
||||||
|
|
||||||
|
Scaling well means for example that if you double the amount of servers
|
||||||
|
you get about twice the performance. Scaling bad means that if you add a
|
||||||
|
new server the application won't perform any better or would not even
|
||||||
|
support a second server.
|
||||||
|
|
||||||
|
There is only one limiting factor regarding scaling in Flask which are
|
||||||
|
the context local proxies. They depend on context which in Flask is
|
||||||
|
defined as being either a thread, process or greenlet. If your server
|
||||||
|
uses some kind of concurrency that is not based on threads or greenlets,
|
||||||
|
Flask will no longer be able to support these global proxies. However the
|
||||||
|
majority of servers are using either threads, greenlets or separate
|
||||||
|
processes to achieve concurrency which are all methods well supported by
|
||||||
|
the underlying Werkzeug library.
|
||||||
|
|
||||||
|
Discuss with the community.
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The Flask developers keep the framework accessible to users with codebases big
|
||||||
|
and small. If you find an obstacle in your way, caused by Flask, don't hesitate
|
||||||
|
to contact the developers on the mailinglist or IRC channel. The best way for
|
||||||
|
the Flask and Flask extension developers to improve the tools for larger
|
||||||
|
applications is getting feedback from users.
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _blueprints:
|
||||||
|
|
||||||
Modular Applications with Blueprints
|
Modular Applications with Blueprints
|
||||||
====================================
|
====================================
|
||||||
|
|
||||||
|
|
@ -35,9 +37,8 @@ Blueprints in Flask are intended for these cases:
|
||||||
A blueprint in Flask is not a pluggable app because it is not actually an
|
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 -- it's a set of operations which can be registered on an
|
||||||
application, even multiple times. Why not have multiple application
|
application, even multiple times. Why not have multiple application
|
||||||
objects? You can do that (see :doc:`/patterns/appdispatch`), but your
|
objects? You can do that (see :ref:`app-dispatch`), but your applications
|
||||||
applications will have separate configs and will be managed at the WSGI
|
will have separate configs and will be managed at the WSGI layer.
|
||||||
layer.
|
|
||||||
|
|
||||||
Blueprints instead provide separation at the Flask level, share
|
Blueprints instead provide separation at the Flask level, share
|
||||||
application config, and can change an application object as necessary with
|
application config, and can change an application object as necessary with
|
||||||
|
|
@ -69,17 +70,16 @@ implement a blueprint that does simple rendering of static templates::
|
||||||
@simple_page.route('/<page>')
|
@simple_page.route('/<page>')
|
||||||
def show(page):
|
def show(page):
|
||||||
try:
|
try:
|
||||||
return render_template(f'pages/{page}.html')
|
return render_template('pages/%s.html' % page)
|
||||||
except TemplateNotFound:
|
except TemplateNotFound:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
When you bind a function with the help of the ``@simple_page.route``
|
When you bind a function with the help of the ``@simple_page.route``
|
||||||
decorator, the blueprint will record the intention of registering the
|
decorator the blueprint will record the intention of registering the
|
||||||
function ``show`` on the application when it's later registered.
|
function `show` on the application when it's later registered.
|
||||||
Additionally it will prefix the endpoint of the function with the
|
Additionally it will prefix the endpoint of the function with the
|
||||||
name of the blueprint which was given to the :class:`Blueprint`
|
name of the blueprint which was given to the :class:`Blueprint`
|
||||||
constructor (in this case also ``simple_page``). The blueprint's name
|
constructor (in this case also ``simple_page``).
|
||||||
does not modify the URL, only the endpoint.
|
|
||||||
|
|
||||||
Registering Blueprints
|
Registering Blueprints
|
||||||
----------------------
|
----------------------
|
||||||
|
|
@ -95,10 +95,9 @@ So how do you register that blueprint? Like this::
|
||||||
If you check the rules registered on the application, you will find
|
If you check the rules registered on the application, you will find
|
||||||
these::
|
these::
|
||||||
|
|
||||||
>>> app.url_map
|
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||||
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
|
||||||
<Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
|
<Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
|
||||||
<Rule '/' (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
|
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``
|
files. The other two are for the `show` function of the ``simple_page``
|
||||||
|
|
@ -111,53 +110,14 @@ Blueprints however can also be mounted at different locations::
|
||||||
|
|
||||||
And sure enough, these are the generated rules::
|
And sure enough, these are the generated rules::
|
||||||
|
|
||||||
>>> app.url_map
|
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||||
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
|
||||||
<Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
|
<Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
|
||||||
<Rule '/pages/' (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
|
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 might respond properly to that. In fact it depends on how the
|
||||||
blueprint is implemented if it can be mounted more than once.
|
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
|
Blueprint Resources
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
@ -191,31 +151,23 @@ To quickly open sources from this folder you can use the
|
||||||
Static Files
|
Static Files
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
A blueprint can expose a folder with static files by providing the path
|
A blueprint can expose a folder with static files by providing a path to a
|
||||||
to the folder on the filesystem with the ``static_folder`` argument.
|
folder on the filesystem via the `static_folder` keyword argument. It can
|
||||||
It is either an absolute path or relative to the blueprint's location::
|
either be an absolute path or one relative to the folder of the
|
||||||
|
blueprint::
|
||||||
|
|
||||||
admin = Blueprint('admin', __name__, static_folder='static')
|
admin = Blueprint('admin', __name__, static_folder='static')
|
||||||
|
|
||||||
By default the rightmost part of the path is where it is exposed on the
|
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
|
web. Because the folder is called :file:`static` here it will be available at
|
||||||
folder is called ``static`` here it will be available at the
|
the location of the blueprint + ``/static``. Say the blueprint is
|
||||||
``url_prefix`` of the blueprint + ``/static``. If the blueprint
|
registered for ``/admin`` the static folder will be at ``/admin/static``.
|
||||||
has the prefix ``/admin``, the static URL will be ``/admin/static``.
|
|
||||||
|
|
||||||
The endpoint is named ``blueprint_name.static``. You can generate URLs
|
The endpoint is named `blueprint_name.static` so you can generate URLs to
|
||||||
to it with :func:`url_for` like you would with the static folder of the
|
it like you would do to the static folder of the application::
|
||||||
application::
|
|
||||||
|
|
||||||
url_for('admin.static', filename='style.css')
|
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
|
Templates
|
||||||
`````````
|
`````````
|
||||||
|
|
||||||
|
|
@ -225,11 +177,11 @@ the `template_folder` parameter to the :class:`Blueprint` constructor::
|
||||||
admin = Blueprint('admin', __name__, template_folder='templates')
|
admin = Blueprint('admin', __name__, template_folder='templates')
|
||||||
|
|
||||||
For static files, the path can be absolute or relative to the blueprint
|
For static files, the path can be absolute or relative to the blueprint
|
||||||
resource folder.
|
resource folder.
|
||||||
|
|
||||||
The template folder is added to the search path of templates but with a lower
|
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
|
priority than the actual application's template folder. That way you can
|
||||||
easily override templates that a blueprint provides in the actual application.
|
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
|
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
|
overridden, make sure that no other blueprint or actual application template
|
||||||
has the same relative path. When multiple blueprints provide the same relative
|
has the same relative path. When multiple blueprints provide the same relative
|
||||||
|
|
@ -242,7 +194,7 @@ want to render the template ``'admin/index.html'`` and you have provided
|
||||||
this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
|
this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
|
||||||
for the extra ``admin`` folder is to avoid getting our template overridden
|
for the extra ``admin`` folder is to avoid getting our template overridden
|
||||||
by a template named ``index.html`` in the actual application template
|
by a template named ``index.html`` in the actual application template
|
||||||
folder.
|
folder.
|
||||||
|
|
||||||
To further reiterate this: if you have a blueprint named ``admin`` and you
|
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
|
want to render a template called :file:`index.html` which is specific to this
|
||||||
|
|
@ -280,11 +232,10 @@ you can use relative redirects by prefixing the endpoint with a dot only::
|
||||||
This will link to ``admin.index`` for instance in case the current request
|
This will link to ``admin.index`` for instance in case the current request
|
||||||
was dispatched to any other admin blueprint endpoint.
|
was dispatched to any other admin blueprint endpoint.
|
||||||
|
|
||||||
|
Error Handlers
|
||||||
|
--------------
|
||||||
|
|
||||||
Blueprint Error Handlers
|
Blueprints support the errorhandler decorator just like the :class:`Flask`
|
||||||
------------------------
|
|
||||||
|
|
||||||
Blueprints support the ``errorhandler`` decorator just like the :class:`Flask`
|
|
||||||
application object, so it is easy to make Blueprint-specific custom error
|
application object, so it is easy to make Blueprint-specific custom error
|
||||||
pages.
|
pages.
|
||||||
|
|
||||||
|
|
@ -294,22 +245,4 @@ Here is an example for a "404 Page Not Found" exception::
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
return render_template('pages/404.html')
|
return render_template('pages/404.html')
|
||||||
|
|
||||||
Most errorhandlers will simply work as expected; however, there is a caveat
|
More information on error handling see :ref:`errorpages`.
|
||||||
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`.
|
|
||||||
|
|
|
||||||
1
docs/changelog.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.. include:: ../CHANGES
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
Changes
|
|
||||||
=======
|
|
||||||
|
|
||||||
.. include:: ../CHANGES.rst
|
|
||||||
652
docs/cli.rst
|
|
@ -1,556 +1,248 @@
|
||||||
.. currentmodule:: flask
|
.. _cli:
|
||||||
|
|
||||||
Command Line Interface
|
Command Line Interface
|
||||||
======================
|
======================
|
||||||
|
|
||||||
Installing Flask installs the ``flask`` script, a `Click`_ command line
|
.. versionadded:: 0.11
|
||||||
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/
|
.. currentmodule:: flask
|
||||||
|
|
||||||
|
One of the nice new features in Flask 0.11 is the built-in integration of
|
||||||
|
the `click <http://click.pocoo.org/>`_ command line interface. This
|
||||||
|
enables a wide range of new features for the Flask ecosystem and your own
|
||||||
|
applications.
|
||||||
|
|
||||||
Application Discovery
|
Basic Usage
|
||||||
---------------------
|
-----------
|
||||||
|
|
||||||
The ``flask`` command is installed by Flask, not your application; it must be
|
After installation of Flask you will now find a :command:`flask` script
|
||||||
told where to find your application in order to use it. The ``--app``
|
installed into your virtualenv. If you don't want to install Flask or you
|
||||||
option is used to specify how to load the application.
|
have a special use-case you can also use ``python -m flask`` to accomplish
|
||||||
|
exactly the same.
|
||||||
|
|
||||||
While ``--app`` supports a variety of options for specifying your
|
The way this script works is by providing access to all the commands on
|
||||||
application, most use cases should be simple. Here are the typical values:
|
your Flask application's :attr:`Flask.cli` instance as well as some
|
||||||
|
built-in commands that are always there. Flask extensions can also
|
||||||
|
register more commands there if they desire so.
|
||||||
|
|
||||||
(nothing)
|
For the :command:`flask` script to work, an application needs to be
|
||||||
The name "app" or "wsgi" is imported (as a ".py" file, or package),
|
discovered. This is achieved by exporting the ``FLASK_APP`` environment
|
||||||
automatically detecting an app (``app`` or ``application``) or
|
variable. It can be either set to an import path or to a filename of a
|
||||||
factory (``create_app`` or ``make_app``).
|
Python module that contains a Flask application.
|
||||||
|
|
||||||
``--app hello``
|
In that imported file the name of the app needs to be called ``app`` or
|
||||||
The given name is imported, automatically detecting an app (``app``
|
optionally be specified after a colon. For instance
|
||||||
or ``application``) or factory (``create_app`` or ``make_app``).
|
``mymodule:application`` would tell it to use the `application` object in
|
||||||
|
the :file:`mymodule.py` file.
|
||||||
|
|
||||||
----
|
Given a :file:`hello.py` file with the application in it named ``app``
|
||||||
|
this is how it can be run.
|
||||||
|
|
||||||
``--app`` has three parts: an optional path that sets the current working
|
Environment variables (On Windows use ``set`` instead of ``export``)::
|
||||||
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``
|
export FLASK_APP=hello
|
||||||
Sets the current working directory to ``src`` then imports ``hello``.
|
flask run
|
||||||
|
|
||||||
``--app hello.web``
|
Or with a filename::
|
||||||
Imports the path ``hello.web``.
|
|
||||||
|
|
||||||
``--app hello:app2``
|
export FLASK_APP=/path/to/hello.py
|
||||||
Uses the ``app2`` Flask instance in ``hello``.
|
flask run
|
||||||
|
|
||||||
``--app 'hello:create_app("dev")'``
|
Virtualenv Integration
|
||||||
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
|
If you are constantly working with a virtualenv you can also put the
|
||||||
"wsgi" (as a ".py" file, or package) and try to detect an application
|
``export FLASK_APP`` into your ``activate`` script by adding it to the
|
||||||
instance or factory.
|
bottom of the file. That way every time you activate your virtualenv you
|
||||||
|
automatically also activate the correct application name.
|
||||||
|
|
||||||
Within the given import, the command looks for an application instance named
|
Debug Flag
|
||||||
``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
|
The :command:`flask` script can also be instructed to enable the debug
|
||||||
Python literals and passed as arguments and keyword arguments to the
|
mode of the application automatically by exporting ``FLASK_DEBUG``. If
|
||||||
function. This means that strings must still be in quotes.
|
set to ``1`` debug is enabled or ``0`` disables it::
|
||||||
|
|
||||||
|
export FLASK_DEBUG=1
|
||||||
|
|
||||||
Run the Development Server
|
Running a Shell
|
||||||
--------------------------
|
---------------
|
||||||
|
|
||||||
The :func:`run <cli.run_command>` command will start the development server. It
|
To run an interactive Python shell you can use the ``shell`` command::
|
||||||
replaces the :meth:`Flask.run` method in most cases. ::
|
|
||||||
|
|
||||||
$ flask --app hello run
|
flask shell
|
||||||
* 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.
|
|
||||||
|
|
||||||
|
This will start up an interactive Python shell, setup the correct
|
||||||
|
application context and setup the local variables in the shell. This is
|
||||||
|
done by invoking the :meth:`Flask.make_shell_context` method of the
|
||||||
|
application. By default you have access to your ``app`` and :data:`g`.
|
||||||
|
|
||||||
Custom Commands
|
Custom Commands
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
The ``flask`` command is implemented using `Click`_. See that project's
|
If you want to add more commands to the shell script you can do this
|
||||||
documentation for full information about writing commands.
|
easily. Flask uses `click`_ for the command interface which makes
|
||||||
|
creating custom commands very easy. For instance if you want a shell
|
||||||
This example adds the command ``create-user`` that takes the argument
|
command to initialize the database you can do this::
|
||||||
``name``. ::
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@app.cli.command("create-user")
|
@app.cli.command()
|
||||||
@click.argument("name")
|
def initdb():
|
||||||
def create_user(name):
|
"""Initialize the database."""
|
||||||
...
|
click.echo('Init the db')
|
||||||
|
|
||||||
::
|
The command will then show up on the command line::
|
||||||
|
|
||||||
$ 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
|
|
||||||
|
|
||||||
|
$ flask initdb
|
||||||
|
Init the db
|
||||||
|
|
||||||
Application Context
|
Application Context
|
||||||
~~~~~~~~~~~~~~~~~~~
|
-------------------
|
||||||
|
|
||||||
Commands added using the Flask app's :attr:`~Flask.cli` or
|
Most commands operate on the application so it makes a lot of sense if
|
||||||
:class:`~flask.cli.FlaskGroup` :meth:`~cli.AppGroup.command` decorator
|
they have the application context setup. Because of this, if you register
|
||||||
will be executed with an application context pushed, so your custom
|
a callback on ``app.cli`` with the :meth:`~flask.cli.AppGroup.command` the
|
||||||
commands and parameters have access to the app and its configuration. The
|
callback will automatically be wrapped through :func:`cli.with_appcontext`
|
||||||
:func:`~cli.with_appcontext` decorator can be used to get the same
|
which informs the cli system to ensure that an application context is set
|
||||||
behavior, but is not needed in most cases.
|
up. This behavior is not available if a command is added later with
|
||||||
|
:func:`~click.Group.add_command` or through other means.
|
||||||
|
|
||||||
.. code-block:: python
|
It can also be disabled by passing ``with_appcontext=False`` to the
|
||||||
|
decorator::
|
||||||
|
|
||||||
import click
|
@app.cli.command(with_appcontext=False)
|
||||||
from flask.cli import with_appcontext
|
def example():
|
||||||
|
pass
|
||||||
|
|
||||||
@click.command()
|
Factory Functions
|
||||||
@with_appcontext
|
-----------------
|
||||||
def do_work():
|
|
||||||
...
|
|
||||||
|
|
||||||
app.cli.add_command(do_work)
|
In case you are using factory functions to create your application (see
|
||||||
|
:ref:`app-factories`) you will discover that the :command:`flask` command
|
||||||
|
cannot work with them directly. Flask won't be able to figure out how to
|
||||||
|
instantiate your application properly by itself. Because of this reason
|
||||||
|
the recommendation is to create a separate file that instantiates
|
||||||
|
applications. This is not the only way to make this work. Another is the
|
||||||
|
:ref:`custom-scripts` support.
|
||||||
|
|
||||||
|
For instance if you have a factory function that creates an application
|
||||||
|
from a filename you could make a separate file that creates such an
|
||||||
|
application from an environment variable.
|
||||||
|
|
||||||
Plugins
|
This could be a file named :file:`autoapp.py` with these contents::
|
||||||
-------
|
|
||||||
|
|
||||||
Flask will automatically load commands specified in the ``flask.commands``
|
import os
|
||||||
`entry point`_. This is useful for extensions that want to add commands when
|
from yourapplication import create_app
|
||||||
they are installed. Entry points are specified in :file:`pyproject.toml`:
|
app = create_app(os.environ['YOURAPPLICATION_CONFIG'])
|
||||||
|
|
||||||
.. code-block:: toml
|
Once this has happened you can make the :command:`flask` command automatically
|
||||||
|
pick it up::
|
||||||
|
|
||||||
[project.entry-points."flask.commands"]
|
export YOURAPPLICATION_CONFIG=/path/to/config.cfg
|
||||||
my-command = "my_extension.commands:cli"
|
export FLASK_APP=/path/to/autoapp.py
|
||||||
|
|
||||||
.. _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.
|
|
||||||
|
|
||||||
|
From this point onwards :command:`flask` will find your application.
|
||||||
|
|
||||||
.. _custom-scripts:
|
.. _custom-scripts:
|
||||||
|
|
||||||
Custom Scripts
|
Custom Scripts
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
When you are using the app factory pattern, it may be more convenient to define
|
While the most common way is to use the :command:`flask` command, you can
|
||||||
your own Click script. Instead of using ``--app`` and letting Flask load
|
also make your own "driver scripts". Since Flask uses click for the
|
||||||
your application, you can create your own Click object and export it as a
|
scripts there is no reason you cannot hook these scripts into any click
|
||||||
`console script`_ entry point.
|
application. There is one big caveat and that is, that commands
|
||||||
|
registered to :attr:`Flask.cli` will expect to be (indirectly at least)
|
||||||
|
launched from a :class:`flask.cli.FlaskGroup` click group. This is
|
||||||
|
necessary so that the commands know which Flask application they have to
|
||||||
|
work with.
|
||||||
|
|
||||||
Create an instance of :class:`~cli.FlaskGroup` and pass it the factory::
|
To understand why you might want custom scripts you need to understand how
|
||||||
|
click finds and executes the Flask application. If you use the
|
||||||
|
:command:`flask` script you specify the application to work with on the
|
||||||
|
command line or environment variable as an import name. This is simple
|
||||||
|
but it has some limitations. Primarily it does not work with application
|
||||||
|
factory functions (see :ref:`app-factories`).
|
||||||
|
|
||||||
|
With a custom script you don't have this problem as you can fully
|
||||||
|
customize how the application will be created. This is very useful if you
|
||||||
|
write reusable applications that you want to ship to users and they should
|
||||||
|
be presented with a custom management script.
|
||||||
|
|
||||||
|
To explain all of this, here is an example :file:`manage.py` script that
|
||||||
|
manages a hypothetical wiki application. We will go through the details
|
||||||
|
afterwards::
|
||||||
|
|
||||||
|
import os
|
||||||
import click
|
import click
|
||||||
from flask import Flask
|
|
||||||
from flask.cli import FlaskGroup
|
from flask.cli import FlaskGroup
|
||||||
|
|
||||||
def create_app():
|
def create_wiki_app(info):
|
||||||
app = Flask('wiki')
|
from yourwiki import create_app
|
||||||
# other setup
|
return create_app(
|
||||||
return app
|
config=os.environ.get('WIKI_CONFIG', 'wikiconfig.py'))
|
||||||
|
|
||||||
@click.group(cls=FlaskGroup, create_app=create_app)
|
@click.group(cls=FlaskGroup, create_app=create_wiki_app)
|
||||||
def cli():
|
def cli():
|
||||||
"""Management script for the Wiki application."""
|
"""This is a management script for the wiki application."""
|
||||||
|
|
||||||
Define the entry point in :file:`pyproject.toml`:
|
if __name__ == '__main__':
|
||||||
|
cli()
|
||||||
|
|
||||||
.. code-block:: toml
|
That's a lot of code for not much, so let's go through all parts step by
|
||||||
|
step.
|
||||||
|
|
||||||
[project.scripts]
|
1. First we import the ``click`` library as well as the click extensions
|
||||||
wiki = "wiki:cli"
|
from the ``flask.cli`` package. Primarily we are here interested
|
||||||
|
in the :class:`~flask.cli.FlaskGroup` click group.
|
||||||
|
2. The next thing we do is defining a function that is invoked with the
|
||||||
|
script info object (:class:`~flask.cli.ScriptInfo`) from Flask and its
|
||||||
|
purpose is to fully import and create the application. This can
|
||||||
|
either directly import an application object or create it (see
|
||||||
|
:ref:`app-factories`). In this case we load the config from an
|
||||||
|
environment variable.
|
||||||
|
3. Next step is to create a :class:`FlaskGroup`. In this case we just
|
||||||
|
make an empty function with a help doc string that just does nothing
|
||||||
|
and then pass the ``create_wiki_app`` function as a factory function.
|
||||||
|
|
||||||
Install the application in the virtualenv in editable mode and the custom
|
Whenever click now needs to operate on a Flask application it will
|
||||||
script is available. Note that you don't need to set ``--app``. ::
|
call that function with the script info and ask for it to be created.
|
||||||
|
4. All is rounded up by invoking the script.
|
||||||
|
|
||||||
$ pip install -e .
|
CLI Plugins
|
||||||
$ wiki run
|
-----------
|
||||||
|
|
||||||
.. admonition:: Errors in Custom Scripts
|
Flask extensions can always patch the :attr:`Flask.cli` instance with more
|
||||||
|
commands if they want. However there is a second way to add CLI plugins
|
||||||
|
to Flask which is through ``setuptools``. If you make a Python package that
|
||||||
|
should export a Flask command line plugin you can ship a :file:`setup.py` file
|
||||||
|
that declares an entrypoint that points to a click command:
|
||||||
|
|
||||||
When using a custom script, if you introduce an error in your
|
Example :file:`setup.py`::
|
||||||
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
|
from setuptools import setup
|
||||||
this issue and is recommended in most cases.
|
|
||||||
|
|
||||||
.. _console script: https://packaging.python.org/tutorials/packaging-projects/#console-scripts
|
setup(
|
||||||
|
name='flask-my-extension',
|
||||||
|
...
|
||||||
|
entry_points='''
|
||||||
|
[flask.commands]
|
||||||
|
my-command=mypackage.commands:cli
|
||||||
|
''',
|
||||||
|
)
|
||||||
|
|
||||||
|
Inside :file:`mypackage/commands.py` you can then export a Click object::
|
||||||
|
|
||||||
PyCharm Integration
|
import click
|
||||||
-------------------
|
|
||||||
|
|
||||||
PyCharm Professional provides a special Flask run configuration to run the development
|
@click.command()
|
||||||
server. For the Community Edition, and for other commands besides ``run``, you need to
|
def cli():
|
||||||
create a custom run configuration. These instructions should be similar for any other
|
"""This is an example command."""
|
||||||
IDE you use.
|
|
||||||
|
|
||||||
In PyCharm, with your project open, click on *Run* from the menu bar and go to *Edit
|
Once that package is installed in the same virtualenv as Flask itself you
|
||||||
Configurations*. You'll see a screen similar to this:
|
can run ``flask my-command`` to invoke your command. This is useful to
|
||||||
|
provide extra functionality that Flask itself cannot ship.
|
||||||
.. 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.
|
|
||||||
|
|
|
||||||
182
docs/conf.py
|
|
@ -1,96 +1,162 @@
|
||||||
import packaging.version
|
# -*- coding: utf-8 -*-
|
||||||
from pallets_sphinx_themes import get_version
|
from __future__ import print_function
|
||||||
from pallets_sphinx_themes import ProjectLink
|
|
||||||
|
import inspect
|
||||||
|
import re
|
||||||
|
|
||||||
|
from pallets_sphinx_themes import ProjectLink, get_version
|
||||||
|
|
||||||
# Project --------------------------------------------------------------
|
# Project --------------------------------------------------------------
|
||||||
|
|
||||||
project = "Flask"
|
project = 'Flask'
|
||||||
copyright = "2010 Pallets"
|
copyright = '2010 Pallets Team'
|
||||||
author = "Pallets"
|
author = 'Pallets Team'
|
||||||
release, version = get_version("Flask")
|
release, version = get_version('Flask')
|
||||||
|
|
||||||
# General --------------------------------------------------------------
|
# General --------------------------------------------------------------
|
||||||
|
|
||||||
default_role = "code"
|
master_doc = 'index'
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
"sphinx.ext.autodoc",
|
'sphinx.ext.autodoc',
|
||||||
"sphinx.ext.extlinks",
|
'sphinx.ext.intersphinx',
|
||||||
"sphinx.ext.intersphinx",
|
'sphinxcontrib.log_cabinet',
|
||||||
"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 = {
|
intersphinx_mapping = {
|
||||||
"python": ("https://docs.python.org/3/", None),
|
'python': ('https://docs.python.org/3/', None),
|
||||||
"werkzeug": ("https://werkzeug.palletsprojects.com/", None),
|
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
|
||||||
"click": ("https://click.palletsprojects.com/", None),
|
'click': ('http://click.pocoo.org/', None),
|
||||||
"jinja": ("https://jinja.palletsprojects.com/", None),
|
'jinja': ('http://jinja.pocoo.org/docs/', None),
|
||||||
"itsdangerous": ("https://itsdangerous.palletsprojects.com/", None),
|
'itsdangerous': ('https://pythonhosted.org/itsdangerous', None),
|
||||||
"sqlalchemy": ("https://docs.sqlalchemy.org/", None),
|
'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None),
|
||||||
"wtforms": ("https://wtforms.readthedocs.io/", None),
|
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
|
||||||
"blinker": ("https://blinker.readthedocs.io/", None),
|
'blinker': ('https://pythonhosted.org/blinker/', None),
|
||||||
}
|
}
|
||||||
|
|
||||||
# HTML -----------------------------------------------------------------
|
# HTML -----------------------------------------------------------------
|
||||||
|
|
||||||
html_theme = "flask"
|
html_theme = 'flask'
|
||||||
html_theme_options = {"index_sidebar_logo": False}
|
|
||||||
html_context = {
|
html_context = {
|
||||||
"project_links": [
|
'project_links': [
|
||||||
ProjectLink("Donate", "https://palletsprojects.com/donate"),
|
ProjectLink('Donate to Pallets', 'https://psfmember.org/civicrm/contribute/transact?reset=1&id=20'),
|
||||||
ProjectLink("PyPI Releases", "https://pypi.org/project/Flask/"),
|
ProjectLink('Flask Website', 'https://palletsprojects.com/p/flask/'),
|
||||||
ProjectLink("Source Code", "https://github.com/pallets/flask/"),
|
ProjectLink('PyPI releases', 'https://pypi.org/project/Flask/'),
|
||||||
ProjectLink("Issue Tracker", "https://github.com/pallets/flask/issues/"),
|
ProjectLink('Source Code', 'https://github.com/pallets/flask/'),
|
||||||
ProjectLink("Chat", "https://discord.gg/pallets"),
|
ProjectLink(
|
||||||
]
|
'Issue Tracker', 'https://github.com/pallets/flask/issues/'),
|
||||||
|
],
|
||||||
|
'canonical_url': 'http://flask.pocoo.org/docs/{}/'.format(version),
|
||||||
|
'carbon_ads_args': 'zoneid=1673&serve=C6AILKT&placement=pocooorg',
|
||||||
}
|
}
|
||||||
html_sidebars = {
|
html_sidebars = {
|
||||||
"index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"],
|
'index': [
|
||||||
"**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"],
|
'project.html',
|
||||||
|
'versions.html',
|
||||||
|
'searchbox.html',
|
||||||
|
],
|
||||||
|
'**': [
|
||||||
|
'localtoc.html',
|
||||||
|
'relations.html',
|
||||||
|
'versions.html',
|
||||||
|
'carbon_ads.html',
|
||||||
|
'searchbox.html',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
html_static_path = ['_static']
|
||||||
|
html_favicon = '_static/flask-favicon.ico'
|
||||||
|
html_logo = '_static/flask.png'
|
||||||
|
html_additional_pages = {
|
||||||
|
'404': '404.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
|
html_show_sourcelink = False
|
||||||
|
|
||||||
gettext_uuid = True
|
# LaTeX ----------------------------------------------------------------
|
||||||
gettext_compact = False
|
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'Flask.tex', 'Flask Documentation', 'Pallets Team', 'manual'),
|
||||||
|
]
|
||||||
|
latex_use_modindex = False
|
||||||
|
latex_elements = {
|
||||||
|
'papersize': 'a4paper',
|
||||||
|
'pointsize': '12pt',
|
||||||
|
'fontpkg': r'\usepackage{mathpazo}',
|
||||||
|
'preamble': r'\usepackage{flaskstyle}',
|
||||||
|
}
|
||||||
|
latex_use_parts = True
|
||||||
|
latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
|
||||||
|
|
||||||
|
# linkcheck ------------------------------------------------------------
|
||||||
|
|
||||||
|
linkcheck_anchors = False
|
||||||
|
|
||||||
# Local Extensions -----------------------------------------------------
|
# Local Extensions -----------------------------------------------------
|
||||||
|
|
||||||
|
def unwrap_decorators():
|
||||||
|
import sphinx.util.inspect as inspect
|
||||||
|
import functools
|
||||||
|
|
||||||
def github_link(name, rawtext, text, lineno, inliner, options=None, content=None):
|
old_getargspec = inspect.getargspec
|
||||||
|
def getargspec(x):
|
||||||
|
return old_getargspec(getattr(x, '_original_function', x))
|
||||||
|
inspect.getargspec = getargspec
|
||||||
|
|
||||||
|
old_update_wrapper = functools.update_wrapper
|
||||||
|
def update_wrapper(wrapper, wrapped, *a, **kw):
|
||||||
|
rv = old_update_wrapper(wrapper, wrapped, *a, **kw)
|
||||||
|
rv._original_function = wrapped
|
||||||
|
return rv
|
||||||
|
functools.update_wrapper = update_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
unwrap_decorators()
|
||||||
|
del unwrap_decorators
|
||||||
|
|
||||||
|
|
||||||
|
_internal_mark_re = re.compile(r'^\s*:internal:\s*$(?m)', re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def skip_internal(app, what, name, obj, skip, options):
|
||||||
|
docstring = inspect.getdoc(obj) or ''
|
||||||
|
|
||||||
|
if skip or _internal_mark_re.search(docstring) is not None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def cut_module_meta(app, what, name, obj, options, lines):
|
||||||
|
"""Remove metadata from autodoc output."""
|
||||||
|
if what != 'module':
|
||||||
|
return
|
||||||
|
|
||||||
|
lines[:] = [
|
||||||
|
line for line in lines
|
||||||
|
if not line.startswith((':copyright:', ':license:'))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def github_link(
|
||||||
|
name, rawtext, text, lineno, inliner, options=None, content=None
|
||||||
|
):
|
||||||
app = inliner.document.settings.env.app
|
app = inliner.document.settings.env.app
|
||||||
release = app.config.release
|
release = app.config.release
|
||||||
base_url = "https://github.com/pallets/flask/tree/"
|
base_url = 'https://github.com/pallets/flask/tree/'
|
||||||
|
|
||||||
if text.endswith(">"):
|
if text.endswith('>'):
|
||||||
words, text = text[:-1].rsplit("<", 1)
|
words, text = text[:-1].rsplit('<', 1)
|
||||||
words = words.strip()
|
words = words.strip()
|
||||||
else:
|
else:
|
||||||
words = None
|
words = None
|
||||||
|
|
||||||
if packaging.version.parse(release).is_devrelease:
|
if release.endswith('dev'):
|
||||||
url = f"{base_url}main/{text}"
|
url = '{0}master/{1}'.format(base_url, text)
|
||||||
else:
|
else:
|
||||||
url = f"{base_url}{release}/{text}"
|
url = '{0}{1}/{2}'.format(base_url, release, text)
|
||||||
|
|
||||||
if words is None:
|
if words is None:
|
||||||
words = url
|
words = url
|
||||||
|
|
||||||
from docutils.nodes import reference
|
from docutils.nodes import reference
|
||||||
from docutils.parsers.rst.roles import set_classes
|
from docutils.parsers.rst.roles import set_classes
|
||||||
|
|
||||||
options = options or {}
|
options = options or {}
|
||||||
set_classes(options)
|
set_classes(options)
|
||||||
node = reference(rawtext, words, refuri=url, **options)
|
node = reference(rawtext, words, refuri=url, **options)
|
||||||
|
|
@ -98,4 +164,6 @@ def github_link(name, rawtext, text, lineno, inliner, options=None, content=None
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.add_role("gh", github_link)
|
app.connect('autodoc-skip-member', skip_internal)
|
||||||
|
app.connect('autodoc-process-docstring', cut_module_meta)
|
||||||
|
app.add_role('gh', github_link)
|
||||||
|
|
|
||||||
788
docs/config.rst
|
|
@ -1,13 +1,17 @@
|
||||||
|
.. _config:
|
||||||
|
|
||||||
Configuration Handling
|
Configuration Handling
|
||||||
======================
|
======================
|
||||||
|
|
||||||
|
.. versionadded:: 0.3
|
||||||
|
|
||||||
Applications need some kind of configuration. There are different settings
|
Applications need some kind of configuration. There are different settings
|
||||||
you might want to change depending on the application environment like
|
you might want to change depending on the application environment like
|
||||||
toggling the debug mode, setting the secret key, and other such
|
toggling the debug mode, setting the secret key, and other such
|
||||||
environment-specific things.
|
environment-specific things.
|
||||||
|
|
||||||
The way Flask is designed usually requires the configuration to be
|
The way Flask is designed usually requires the configuration to be
|
||||||
available when the application starts up. You can hard code the
|
available when the application starts up. You can hardcode the
|
||||||
configuration in the code, which for many small applications is not
|
configuration in the code, which for many small applications is not
|
||||||
actually that bad, but there are better ways.
|
actually that bad, but there are better ways.
|
||||||
|
|
||||||
|
|
@ -18,7 +22,6 @@ object. This is the place where Flask itself puts certain configuration
|
||||||
values and also where extensions can put their configuration values. But
|
values and also where extensions can put their configuration values. But
|
||||||
this is also where you can have your own configuration.
|
this is also where you can have your own configuration.
|
||||||
|
|
||||||
|
|
||||||
Configuration Basics
|
Configuration Basics
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
@ -26,373 +29,193 @@ The :attr:`~flask.Flask.config` is actually a subclass of a dictionary and
|
||||||
can be modified just like any dictionary::
|
can be modified just like any dictionary::
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['TESTING'] = True
|
app.config['DEBUG'] = True
|
||||||
|
|
||||||
Certain configuration values are also forwarded to the
|
Certain configuration values are also forwarded to the
|
||||||
:attr:`~flask.Flask` object so you can read and write them from there::
|
:attr:`~flask.Flask` object so you can read and write them from there::
|
||||||
|
|
||||||
app.testing = True
|
app.debug = True
|
||||||
|
|
||||||
To update multiple keys at once you can use the :meth:`dict.update`
|
To update multiple keys at once you can use the :meth:`dict.update`
|
||||||
method::
|
method::
|
||||||
|
|
||||||
app.config.update(
|
app.config.update(
|
||||||
TESTING=True,
|
DEBUG=True,
|
||||||
SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
SECRET_KEY='...'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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
|
Builtin Configuration Values
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
The following configuration values are used internally by Flask:
|
The following configuration values are used internally by Flask:
|
||||||
|
|
||||||
.. py:data:: DEBUG
|
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
|
||||||
|
|
||||||
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
|
``DEBUG`` enable/disable debug mode
|
||||||
server will be reloaded when code changes. The :attr:`~flask.Flask.debug` attribute
|
``TESTING`` enable/disable testing mode
|
||||||
maps to this config key. This is set with the ``FLASK_DEBUG`` environment variable.
|
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
|
||||||
It may not behave as expected if set in code.
|
propagation of exceptions. If not set or
|
||||||
|
explicitly set to ``None`` this is
|
||||||
**Do not enable debug mode when deploying in production.**
|
implicitly true if either ``TESTING`` or
|
||||||
|
``DEBUG`` is true.
|
||||||
Default: ``False``
|
``PRESERVE_CONTEXT_ON_EXCEPTION`` By default if the application is in
|
||||||
|
debug mode the request context is not
|
||||||
.. py:data:: TESTING
|
popped on exceptions to enable debuggers
|
||||||
|
to introspect the data. This can be
|
||||||
Enable testing mode. Exceptions are propagated rather than handled by
|
disabled by this key. You can also use
|
||||||
the app's error handlers. Extensions may also change their behavior to
|
this setting to force-enable it for non
|
||||||
facilitate easier testing. You should enable this in your own tests.
|
debug execution which might be useful to
|
||||||
|
debug production applications (but also
|
||||||
Default: ``False``
|
very risky).
|
||||||
|
``SECRET_KEY`` the secret key
|
||||||
.. py:data:: PROPAGATE_EXCEPTIONS
|
``SESSION_COOKIE_NAME`` the name of the session cookie
|
||||||
|
``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If
|
||||||
Exceptions are re-raised rather than being handled by the app's error
|
this is not set, the cookie will be
|
||||||
handlers. If not set, this is implicitly true if ``TESTING`` or ``DEBUG``
|
valid for all subdomains of
|
||||||
is enabled.
|
``SERVER_NAME``.
|
||||||
|
``SESSION_COOKIE_PATH`` the path for the session cookie. If
|
||||||
Default: ``None``
|
this is not set the cookie will be valid
|
||||||
|
for all of ``APPLICATION_ROOT`` or if
|
||||||
.. py:data:: TRAP_HTTP_EXCEPTIONS
|
that is not set for ``'/'``.
|
||||||
|
``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set
|
||||||
If there is no handler for an ``HTTPException``-type exception, re-raise it
|
with the httponly flag. Defaults to
|
||||||
to be handled by the interactive debugger instead of returning it as a
|
``True``.
|
||||||
simple error response.
|
``SESSION_COOKIE_SECURE`` controls if the cookie should be set
|
||||||
|
with the secure flag. Defaults to
|
||||||
Default: ``False``
|
``False``.
|
||||||
|
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
|
||||||
.. py:data:: TRAP_BAD_REQUEST_ERRORS
|
:class:`datetime.timedelta` object.
|
||||||
|
Starting with Flask 0.8 this can also be
|
||||||
Trying to access a key that doesn't exist from request dicts like ``args``
|
an integer representing seconds.
|
||||||
and ``form`` will return a 400 Bad Request error page. Enable this to treat
|
``SESSION_REFRESH_EACH_REQUEST`` this flag controls how permanent
|
||||||
the error as an unhandled exception instead so that you get the interactive
|
sessions are refreshed. If set to ``True``
|
||||||
debugger. This is a more specific version of ``TRAP_HTTP_EXCEPTIONS``. If
|
(which is the default) then the cookie
|
||||||
unset, it is enabled in debug mode.
|
is refreshed each request which
|
||||||
|
automatically bumps the lifetime. If
|
||||||
Default: ``None``
|
set to ``False`` a `set-cookie` header is
|
||||||
|
only sent if the session is modified.
|
||||||
.. py:data:: SECRET_KEY
|
Non permanent sessions are not affected
|
||||||
|
by this.
|
||||||
A secret key that will be used for securely signing the session cookie
|
``USE_X_SENDFILE`` enable/disable x-sendfile
|
||||||
and can be used for any other security related needs by extensions or your
|
``LOGGER_NAME`` the name of the logger
|
||||||
application. It should be a long random ``bytes`` or ``str``. For
|
``LOGGER_HANDLER_POLICY`` the policy of the default logging
|
||||||
example, copy the output of this to your config::
|
handler. The default is ``'always'``
|
||||||
|
which means that the default logging
|
||||||
$ python -c 'import secrets; print(secrets.token_hex())'
|
handler is always active. ``'debug'``
|
||||||
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
will only activate logging in debug
|
||||||
|
mode, ``'production'`` will only log in
|
||||||
**Do not reveal the secret key when posting questions or committing code.**
|
production and ``'never'`` disables it
|
||||||
|
entirely.
|
||||||
Default: ``None``
|
``SERVER_NAME`` the name and port number of the server.
|
||||||
|
Required for subdomain support (e.g.:
|
||||||
.. py:data:: SECRET_KEY_FALLBACKS
|
``'myapp.dev:5000'``) Note that
|
||||||
|
localhost does not support subdomains so
|
||||||
A list of old secret keys that can still be used for unsigning. This allows
|
setting this to “localhost” does not
|
||||||
a project to implement key rotation without invalidating active sessions or
|
help. Setting a ``SERVER_NAME`` also
|
||||||
other recently-signed secrets.
|
by default enables URL generation
|
||||||
|
without a request context but with an
|
||||||
Keys should be removed after an appropriate period of time, as checking each
|
application context.
|
||||||
additional key adds some overhead.
|
``APPLICATION_ROOT`` If the application does not occupy
|
||||||
|
a whole domain or subdomain this can
|
||||||
Order should not matter, but the default implementation will test the last
|
be set to the path where the application
|
||||||
key in the list first, so it might make sense to order oldest to newest.
|
is configured to live. This is for
|
||||||
|
session cookie as path value. If
|
||||||
Flask's built-in secure cookie session supports this. Extensions that use
|
domains are used, this should be
|
||||||
:data:`SECRET_KEY` may not support this yet.
|
``None``.
|
||||||
|
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||||
Default: ``None``
|
reject incoming requests with a
|
||||||
|
content length greater than this by
|
||||||
.. versionadded:: 3.1
|
returning a 413 status code.
|
||||||
|
``SEND_FILE_MAX_AGE_DEFAULT`` Default cache control max age to use with
|
||||||
.. py:data:: SESSION_COOKIE_NAME
|
:meth:`~flask.Flask.send_static_file` (the
|
||||||
|
default static file handler) and
|
||||||
The name of the session cookie. Can be changed in case you already have a
|
:func:`~flask.send_file`, as
|
||||||
cookie with the same name.
|
:class:`datetime.timedelta` or as seconds.
|
||||||
|
Override this value on a per-file
|
||||||
Default: ``'session'``
|
basis using the
|
||||||
|
:meth:`~flask.Flask.get_send_file_max_age`
|
||||||
.. py:data:: SESSION_COOKIE_DOMAIN
|
hook on :class:`~flask.Flask` or
|
||||||
|
:class:`~flask.Blueprint`,
|
||||||
The value of the ``Domain`` parameter on the session cookie. If not set, browsers
|
respectively. Defaults to 43200 (12 hours).
|
||||||
will only send the cookie to the exact domain it was set from. Otherwise, they
|
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
|
||||||
will send it to any subdomain of the given value as well.
|
not execute the error handlers of HTTP
|
||||||
|
exceptions but instead treat the
|
||||||
Not setting this value is more restricted and secure than setting it.
|
exception like any other and bubble it
|
||||||
|
through the exception stack. This is
|
||||||
Default: ``None``
|
helpful for hairy debugging situations
|
||||||
|
where you have to find out where an HTTP
|
||||||
.. warning::
|
exception is coming from.
|
||||||
If this is changed after the browser created a cookie is created with
|
``TRAP_BAD_REQUEST_ERRORS`` Werkzeug's internal data structures that
|
||||||
one setting, it may result in another being created. Browsers may send
|
deal with request specific data will
|
||||||
send both in an undefined order. In that case, you may want to change
|
raise special key errors that are also
|
||||||
:data:`SESSION_COOKIE_NAME` as well or otherwise invalidate old sessions.
|
bad request exceptions. Likewise many
|
||||||
|
operations can implicitly fail with a
|
||||||
.. versionchanged:: 2.3
|
BadRequest exception for consistency.
|
||||||
Not set by default, does not fall back to ``SERVER_NAME``.
|
Since it's nice for debugging to know
|
||||||
|
why exactly it failed this flag can be
|
||||||
.. py:data:: SESSION_COOKIE_PATH
|
used to debug those situations. If this
|
||||||
|
config is set to ``True`` you will get
|
||||||
The path that the session cookie will be valid for. If not set, the cookie
|
a regular traceback instead.
|
||||||
will be valid underneath ``APPLICATION_ROOT`` or ``/`` if that is not set.
|
``PREFERRED_URL_SCHEME`` The URL scheme that should be used for
|
||||||
|
URL generation if no URL scheme is
|
||||||
Default: ``None``
|
available. This defaults to ``http``.
|
||||||
|
``JSON_AS_ASCII`` By default Flask serialize object to
|
||||||
.. py:data:: SESSION_COOKIE_HTTPONLY
|
ascii-encoded JSON. If this is set to
|
||||||
|
``False`` Flask will not encode to ASCII
|
||||||
Browsers will not allow JavaScript access to cookies marked as "HTTP only"
|
and output strings as-is and return
|
||||||
for security.
|
unicode strings. ``jsonify`` will
|
||||||
|
automatically encode it in ``utf-8``
|
||||||
Default: ``True``
|
then for transport for instance.
|
||||||
|
``JSON_SORT_KEYS`` By default Flask will serialize JSON
|
||||||
.. py:data:: SESSION_COOKIE_SECURE
|
objects in a way that the keys are
|
||||||
|
ordered. This is done in order to
|
||||||
Browsers will only send cookies with requests over HTTPS if the cookie is
|
ensure that independent of the hash seed
|
||||||
marked "secure". The application must be served over HTTPS for this to make
|
of the dictionary the return value will
|
||||||
sense.
|
be consistent to not trash external HTTP
|
||||||
|
caches. You can override the default
|
||||||
Default: ``False``
|
behavior by changing this variable.
|
||||||
|
This is not recommended but might give
|
||||||
.. py:data:: SESSION_COOKIE_PARTITIONED
|
you a performance improvement on the
|
||||||
|
cost of cacheability.
|
||||||
Browsers will send cookies based on the top-level document's domain, rather
|
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default)
|
||||||
than only the domain of the document setting the cookie. This prevents third
|
jsonify responses will be pretty printed
|
||||||
party cookies set in iframes from "leaking" between separate sites.
|
if they are not requested by an
|
||||||
|
XMLHttpRequest object (controlled by
|
||||||
Browsers are beginning to disallow non-partitioned third party cookies, so
|
the ``X-Requested-With`` header)
|
||||||
you need to mark your cookies partitioned if you expect them to work in such
|
``JSONIFY_MIMETYPE`` MIME type used for jsonify responses.
|
||||||
embedded situations.
|
``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of
|
||||||
|
the template source and reload it
|
||||||
Enabling this implicitly enables :data:`SESSION_COOKIE_SECURE` as well, as
|
automatically. By default the value is
|
||||||
it is only valid when served over HTTPS.
|
``None`` which means that Flask checks
|
||||||
|
original file only in debug mode.
|
||||||
Default: ``False``
|
``EXPLAIN_TEMPLATE_LOADING`` If this is enabled then every attempt to
|
||||||
|
load a template will write an info
|
||||||
.. versionadded:: 3.1
|
message to the logger explaining the
|
||||||
|
attempts to locate the template. This
|
||||||
.. py:data:: SESSION_COOKIE_SAMESITE
|
can be useful to figure out why
|
||||||
|
templates cannot be found or wrong
|
||||||
Restrict how cookies are sent with requests from external sites. Can
|
templates appear to be loaded.
|
||||||
be set to ``'Lax'`` (recommended) or ``'Strict'``.
|
================================= =========================================
|
||||||
See :ref:`security-cookie`.
|
|
||||||
|
.. admonition:: More on ``SERVER_NAME``
|
||||||
Default: ``None``
|
|
||||||
|
The ``SERVER_NAME`` key is used for the subdomain support. Because
|
||||||
.. versionadded:: 1.0
|
Flask cannot guess the subdomain part without the knowledge of the
|
||||||
|
actual server name, this is required if you want to work with
|
||||||
.. py:data:: PERMANENT_SESSION_LIFETIME
|
subdomains. This is also used for the session cookie.
|
||||||
|
|
||||||
If ``session.permanent`` is true, the cookie's expiration will be set this
|
Please keep in mind that not only Flask has the problem of not knowing
|
||||||
number of seconds in the future. Can either be a
|
what subdomains are, your web browser does as well. Most modern web
|
||||||
:class:`datetime.timedelta` or an ``int``.
|
browsers will not allow cross-subdomain cookies to be set on a
|
||||||
|
server name without dots in it. So if your server name is
|
||||||
Flask's default cookie implementation validates that the cryptographic
|
``'localhost'`` you will not be able to set a cookie for
|
||||||
signature is not older than this value.
|
``'localhost'`` and every subdomain of it. Please choose a different
|
||||||
|
server name in that case, like ``'myapplication.local'`` and add
|
||||||
Default: ``timedelta(days=31)`` (``2678400`` seconds)
|
this name + the subdomains you want to use into your host config
|
||||||
|
or setup a local `bind`_.
|
||||||
.. py:data:: SESSION_REFRESH_EACH_REQUEST
|
|
||||||
|
.. _bind: https://www.isc.org/downloads/bind/
|
||||||
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
|
.. versionadded:: 0.4
|
||||||
``LOGGER_NAME``
|
``LOGGER_NAME``
|
||||||
|
|
@ -422,42 +245,16 @@ The following configuration values are used internally by Flask:
|
||||||
``SESSION_REFRESH_EACH_REQUEST``, ``TEMPLATES_AUTO_RELOAD``,
|
``SESSION_REFRESH_EACH_REQUEST``, ``TEMPLATES_AUTO_RELOAD``,
|
||||||
``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING``
|
``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING``
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
Configuring from Files
|
||||||
``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
|
Configuration becomes more useful if you can store it in a separate file,
|
||||||
variable.
|
ideally located outside the actual application package. This makes
|
||||||
|
packaging and distributing your application possible via various package
|
||||||
|
handling tools (:ref:`distribute-deployment`) and finally modifying the
|
||||||
|
configuration file afterwards.
|
||||||
|
|
||||||
Added :data:`SESSION_COOKIE_SAMESITE` to control the session
|
So a common pattern is this::
|
||||||
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.1
|
|
||||||
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 = Flask(__name__)
|
||||||
app.config.from_object('yourapplication.default_settings')
|
app.config.from_object('yourapplication.default_settings')
|
||||||
|
|
@ -466,42 +263,18 @@ A common pattern is this::
|
||||||
This first loads the configuration from the
|
This first loads the configuration from the
|
||||||
`yourapplication.default_settings` module and then overrides the values
|
`yourapplication.default_settings` module and then overrides the values
|
||||||
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||||
environment variable points to. This environment variable can be set
|
environment variable points to. This environment variable can be set on
|
||||||
in the shell before starting the server:
|
Linux or OS X with the export command in the shell before starting the
|
||||||
|
server::
|
||||||
|
|
||||||
.. tabs::
|
$ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg
|
||||||
|
$ python run-app.py
|
||||||
|
* Running on http://127.0.0.1:5000/
|
||||||
|
* Restarting with reloader...
|
||||||
|
|
||||||
.. group-tab:: Bash
|
On Windows systems use the `set` builtin instead::
|
||||||
|
|
||||||
.. code-block:: text
|
>set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg
|
||||||
|
|
||||||
$ 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
|
The configuration files themselves are actual Python files. Only values
|
||||||
in uppercase are actually stored in the config object later on. So make
|
in uppercase are actually stored in the config object later on. So make
|
||||||
|
|
@ -510,7 +283,8 @@ sure to use uppercase letters for your config keys.
|
||||||
Here is an example of a configuration file::
|
Here is an example of a configuration file::
|
||||||
|
|
||||||
# Example configuration
|
# Example configuration
|
||||||
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
DEBUG = False
|
||||||
|
SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
|
||||||
|
|
||||||
Make sure to load the configuration very early on, so that extensions have
|
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
|
the ability to access the configuration when starting up. There are other
|
||||||
|
|
@ -519,118 +293,6 @@ complete reference, read the :class:`~flask.Config` object's
|
||||||
documentation.
|
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
|
Configuration Best Practices
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
|
@ -641,17 +303,13 @@ that experience:
|
||||||
|
|
||||||
1. Create your application in a function and register blueprints on it.
|
1. Create your application in a function and register blueprints on it.
|
||||||
That way you can create multiple instances of your application with
|
That way you can create multiple instances of your application with
|
||||||
different configurations attached which makes unit testing a lot
|
different configurations attached which makes unittesting a lot
|
||||||
easier. You can use this to pass in configuration as needed.
|
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
|
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
|
limit yourself to request-only accesses to the configuration you can
|
||||||
reconfigure the object later on as needed.
|
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:
|
.. _config-dev-prod:
|
||||||
|
|
||||||
Development / Production
|
Development / Production
|
||||||
|
|
@ -678,22 +336,23 @@ the config file by adding ``from yourapplication.default_settings
|
||||||
import *`` to the top of the file and then overriding the changes by hand.
|
import *`` to the top of the file and then overriding the changes by hand.
|
||||||
You could also inspect an environment variable like
|
You could also inspect an environment variable like
|
||||||
``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc
|
``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc
|
||||||
and import different hard-coded files based on that.
|
and import different hardcoded files based on that.
|
||||||
|
|
||||||
An interesting pattern is also to use classes and inheritance for
|
An interesting pattern is also to use classes and inheritance for
|
||||||
configuration::
|
configuration::
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
|
DEBUG = False
|
||||||
TESTING = False
|
TESTING = False
|
||||||
|
DATABASE_URI = 'sqlite://:memory:'
|
||||||
|
|
||||||
class ProductionConfig(Config):
|
class ProductionConfig(Config):
|
||||||
DATABASE_URI = 'mysql://user@localhost/foo'
|
DATABASE_URI = 'mysql://user@localhost/foo'
|
||||||
|
|
||||||
class DevelopmentConfig(Config):
|
class DevelopmentConfig(Config):
|
||||||
DATABASE_URI = "sqlite:////tmp/foo.db"
|
DEBUG = True
|
||||||
|
|
||||||
class TestingConfig(Config):
|
class TestingConfig(Config):
|
||||||
DATABASE_URI = 'sqlite:///:memory:'
|
|
||||||
TESTING = True
|
TESTING = True
|
||||||
|
|
||||||
To enable such a config you just have to call into
|
To enable such a config you just have to call into
|
||||||
|
|
@ -701,41 +360,6 @@ To enable such a config you just have to call into
|
||||||
|
|
||||||
app.config.from_object('configmodule.ProductionConfig')
|
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
|
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:
|
your configuration files. However here a list of good recommendations:
|
||||||
|
|
||||||
|
|
@ -749,10 +373,12 @@ your configuration files. However here a list of good recommendations:
|
||||||
code at all. If you are working often on different projects you can
|
code at all. If you are working often on different projects you can
|
||||||
even create your own script for sourcing that activates a virtualenv
|
even create your own script for sourcing that activates a virtualenv
|
||||||
and exports the development configuration for you.
|
and exports the development configuration for you.
|
||||||
- Use a tool like `fabric`_ to push code and configuration separately
|
- Use a tool like `fabric`_ in production to push code and
|
||||||
to the production server(s).
|
configurations separately to the production server(s). For some
|
||||||
|
details about how to do that, head over to the
|
||||||
|
:ref:`fabric-deployment` pattern.
|
||||||
|
|
||||||
.. _fabric: https://www.fabfile.org/
|
.. _fabric: http://www.fabfile.org/
|
||||||
|
|
||||||
|
|
||||||
.. _instance-folders:
|
.. _instance-folders:
|
||||||
|
|
@ -800,7 +426,7 @@ locations are used:
|
||||||
|
|
||||||
- Installed module or package::
|
- Installed module or package::
|
||||||
|
|
||||||
$PREFIX/lib/pythonX.Y/site-packages/myapp
|
$PREFIX/lib/python2.X/site-packages/myapp
|
||||||
$PREFIX/var/myapp-instance
|
$PREFIX/var/myapp-instance
|
||||||
|
|
||||||
``$PREFIX`` is the prefix of your Python installation. This can be
|
``$PREFIX`` is the prefix of your Python installation. This can be
|
||||||
|
|
@ -817,7 +443,7 @@ root” (the default) to “relative to instance folder” via the
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
|
||||||
Here is a full example of how to configure Flask to preload the config
|
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
|
from a module and then override the config from a file in the config
|
||||||
folder if it exists::
|
folder if it exists::
|
||||||
|
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
|
|
||||||
61
docs/contents.rst.inc
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
User's Guide
|
||||||
|
------------
|
||||||
|
|
||||||
|
This part of the documentation, which is mostly prose, begins with some
|
||||||
|
background information about Flask, then focuses on step-by-step
|
||||||
|
instructions for web development with Flask.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
foreword
|
||||||
|
advanced_foreword
|
||||||
|
installation
|
||||||
|
quickstart
|
||||||
|
tutorial/index
|
||||||
|
templating
|
||||||
|
testing
|
||||||
|
errorhandling
|
||||||
|
config
|
||||||
|
signals
|
||||||
|
views
|
||||||
|
appcontext
|
||||||
|
reqcontext
|
||||||
|
blueprints
|
||||||
|
extensions
|
||||||
|
cli
|
||||||
|
server
|
||||||
|
shell
|
||||||
|
patterns/index
|
||||||
|
deploying/index
|
||||||
|
becomingbig
|
||||||
|
|
||||||
|
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
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Design notes, legal information and changelog are here for the interested.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
design
|
||||||
|
htmlfaq
|
||||||
|
security
|
||||||
|
unicode
|
||||||
|
extensiondev
|
||||||
|
styleguide
|
||||||
|
python3
|
||||||
|
upgrading
|
||||||
|
changelog
|
||||||
|
license
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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/
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
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``.
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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
|
|
||||||
61
docs/deploying/cgi.rst
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
CGI
|
||||||
|
===
|
||||||
|
|
||||||
|
If all other deployment methods do not work, CGI will work for sure.
|
||||||
|
CGI is supported by all major servers but usually has a sub-optimal
|
||||||
|
performance.
|
||||||
|
|
||||||
|
This is also the way you can use a Flask application on Google's `App
|
||||||
|
Engine`_, where execution happens in a CGI-like environment.
|
||||||
|
|
||||||
|
.. admonition:: Watch Out
|
||||||
|
|
||||||
|
Please make sure in advance that any ``app.run()`` calls you might
|
||||||
|
have in your application file are inside an ``if __name__ ==
|
||||||
|
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||||
|
not called because this will always start a local WSGI server which
|
||||||
|
we do not want if we deploy that application to CGI / app engine.
|
||||||
|
|
||||||
|
With CGI, you will also have to make sure that your code does not contain
|
||||||
|
any ``print`` statements, or that ``sys.stdout`` is overridden by something
|
||||||
|
that doesn't write into the HTTP response.
|
||||||
|
|
||||||
|
Creating a `.cgi` file
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
First you need to create the CGI application file. Let's call it
|
||||||
|
:file:`yourapplication.cgi`::
|
||||||
|
|
||||||
|
#!/usr/bin/python
|
||||||
|
from wsgiref.handlers import CGIHandler
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
CGIHandler().run(app)
|
||||||
|
|
||||||
|
Server Setup
|
||||||
|
------------
|
||||||
|
|
||||||
|
Usually there are two ways to configure the server. Either just copy the
|
||||||
|
``.cgi`` into a :file:`cgi-bin` (and use `mod_rewrite` or something similar to
|
||||||
|
rewrite the URL) or let the server point to the file directly.
|
||||||
|
|
||||||
|
In Apache for example you can put something like this into the config:
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
ScriptAlias /app /path/to/the/application.cgi
|
||||||
|
|
||||||
|
On shared webhosting, though, you might not have access to your Apache config.
|
||||||
|
In this case, a file called ``.htaccess``, sitting in the public directory you want
|
||||||
|
your app to be available, works too but the ``ScriptAlias`` directive won't
|
||||||
|
work in that case:
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f # Don't interfere with static files
|
||||||
|
RewriteRule ^(.*)$ /path/to/the/application.cgi/$1 [L]
|
||||||
|
|
||||||
|
For more information consult the documentation of your webserver.
|
||||||
|
|
||||||
|
.. _App Engine: https://developers.google.com/appengine/
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
:orphan:
|
|
||||||
|
|
||||||
eventlet
|
|
||||||
========
|
|
||||||
|
|
||||||
`Eventlet is no longer maintained.`__ Use :doc:`/deploying/gevent` instead.
|
|
||||||
|
|
||||||
__ https://eventlet.readthedocs.io
|
|
||||||
240
docs/deploying/fastcgi.rst
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
.. _deploying-fastcgi:
|
||||||
|
|
||||||
|
FastCGI
|
||||||
|
=======
|
||||||
|
|
||||||
|
FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
|
||||||
|
`cherokee`_; see :ref:`deploying-uwsgi` and :ref:`deploying-wsgi-standalone`
|
||||||
|
for other options. To use your WSGI application with any of them you will need
|
||||||
|
a FastCGI server first. The most popular one is `flup`_ which we will use for
|
||||||
|
this guide. Make sure to have it installed to follow along.
|
||||||
|
|
||||||
|
.. admonition:: Watch Out
|
||||||
|
|
||||||
|
Please make sure in advance that any ``app.run()`` calls you might
|
||||||
|
have in your application file are inside an ``if __name__ ==
|
||||||
|
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||||
|
not called because this will always start a local WSGI server which
|
||||||
|
we do not want if we deploy that application to FastCGI.
|
||||||
|
|
||||||
|
Creating a `.fcgi` file
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
First you need to create the FastCGI server file. Let's call it
|
||||||
|
`yourapplication.fcgi`::
|
||||||
|
|
||||||
|
#!/usr/bin/python
|
||||||
|
from flup.server.fcgi import WSGIServer
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
WSGIServer(app).run()
|
||||||
|
|
||||||
|
This is enough for Apache to work, however nginx and older versions of
|
||||||
|
lighttpd need a socket to be explicitly passed to communicate with the
|
||||||
|
FastCGI server. For that to work you need to pass the path to the
|
||||||
|
socket to the :class:`~flup.server.fcgi.WSGIServer`::
|
||||||
|
|
||||||
|
WSGIServer(application, bindAddress='/path/to/fcgi.sock').run()
|
||||||
|
|
||||||
|
The path has to be the exact same path you define in the server
|
||||||
|
config.
|
||||||
|
|
||||||
|
Save the :file:`yourapplication.fcgi` file somewhere you will find it again.
|
||||||
|
It makes sense to have that in :file:`/var/www/yourapplication` or something
|
||||||
|
similar.
|
||||||
|
|
||||||
|
Make sure to set the executable bit on that file so that the servers
|
||||||
|
can execute it:
|
||||||
|
|
||||||
|
.. sourcecode:: text
|
||||||
|
|
||||||
|
# chmod +x /var/www/yourapplication/yourapplication.fcgi
|
||||||
|
|
||||||
|
Configuring Apache
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The example above is good enough for a basic Apache deployment but your
|
||||||
|
`.fcgi` file will appear in your application URL e.g.
|
||||||
|
``example.com/yourapplication.fcgi/news/``. There are few ways to configure
|
||||||
|
your application so that yourapplication.fcgi does not appear in the URL.
|
||||||
|
A preferable way is to use the ScriptAlias and SetHandler configuration
|
||||||
|
directives to route requests to the FastCGI server. The following example
|
||||||
|
uses FastCgiServer to start 5 instances of the application which will
|
||||||
|
handle all incoming requests::
|
||||||
|
|
||||||
|
LoadModule fastcgi_module /usr/lib64/httpd/modules/mod_fastcgi.so
|
||||||
|
|
||||||
|
FastCgiServer /var/www/html/yourapplication/app.fcgi -idle-timeout 300 -processes 5
|
||||||
|
|
||||||
|
<VirtualHost *>
|
||||||
|
ServerName webapp1.mydomain.com
|
||||||
|
DocumentRoot /var/www/html/yourapplication
|
||||||
|
|
||||||
|
AddHandler fastcgi-script fcgi
|
||||||
|
ScriptAlias / /var/www/html/yourapplication/app.fcgi/
|
||||||
|
|
||||||
|
<Location />
|
||||||
|
SetHandler fastcgi-script
|
||||||
|
</Location>
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
These processes will be managed by Apache. If you're using a standalone
|
||||||
|
FastCGI server, you can use the FastCgiExternalServer directive instead.
|
||||||
|
Note that in the following the path is not real, it's simply used as an
|
||||||
|
identifier to other
|
||||||
|
directives such as AliasMatch::
|
||||||
|
|
||||||
|
FastCgiServer /var/www/html/yourapplication -host 127.0.0.1:3000
|
||||||
|
|
||||||
|
If you cannot set ScriptAlias, for example on a shared web host, you can use
|
||||||
|
WSGI middleware to remove yourapplication.fcgi from the URLs. Set .htaccess::
|
||||||
|
|
||||||
|
<IfModule mod_fcgid.c>
|
||||||
|
AddHandler fcgid-script .fcgi
|
||||||
|
<Files ~ (\.fcgi)>
|
||||||
|
SetHandler fcgid-script
|
||||||
|
Options +FollowSymLinks +ExecCGI
|
||||||
|
</Files>
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
Options +FollowSymlinks
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L]
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
Set yourapplication.fcgi::
|
||||||
|
|
||||||
|
#!/usr/bin/python
|
||||||
|
#: optional path to your local python site-packages folder
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '<your_local_path>/lib/python2.6/site-packages')
|
||||||
|
|
||||||
|
from flup.server.fcgi import WSGIServer
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
class ScriptNameStripper(object):
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['SCRIPT_NAME'] = ''
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
app = ScriptNameStripper(app)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
WSGIServer(app).run()
|
||||||
|
|
||||||
|
Configuring lighttpd
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
A basic FastCGI configuration for lighttpd looks like that::
|
||||||
|
|
||||||
|
fastcgi.server = ("/yourapplication.fcgi" =>
|
||||||
|
((
|
||||||
|
"socket" => "/tmp/yourapplication-fcgi.sock",
|
||||||
|
"bin-path" => "/var/www/yourapplication/yourapplication.fcgi",
|
||||||
|
"check-local" => "disable",
|
||||||
|
"max-procs" => 1
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
alias.url = (
|
||||||
|
"/static/" => "/path/to/your/static"
|
||||||
|
)
|
||||||
|
|
||||||
|
url.rewrite-once = (
|
||||||
|
"^(/static($|/.*))$" => "$1",
|
||||||
|
"^(/.*)$" => "/yourapplication.fcgi$1"
|
||||||
|
)
|
||||||
|
|
||||||
|
Remember to enable the FastCGI, alias and rewrite modules. This configuration
|
||||||
|
binds the application to ``/yourapplication``. If you want the application to
|
||||||
|
work in the URL root you have to work around a lighttpd bug with the
|
||||||
|
:class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` middleware.
|
||||||
|
|
||||||
|
Make sure to apply it only if you are mounting the application the URL
|
||||||
|
root. Also, see the Lighty docs for more information on `FastCGI and Python
|
||||||
|
<http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI>`_ (note that
|
||||||
|
explicitly passing a socket to run() is no longer necessary).
|
||||||
|
|
||||||
|
Configuring nginx
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Installing FastCGI applications on nginx is a bit different because by
|
||||||
|
default no FastCGI parameters are forwarded.
|
||||||
|
|
||||||
|
A basic Flask FastCGI configuration for nginx looks like this::
|
||||||
|
|
||||||
|
location = /yourapplication { rewrite ^ /yourapplication/ last; }
|
||||||
|
location /yourapplication { try_files $uri @yourapplication; }
|
||||||
|
location @yourapplication {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_split_path_info ^(/yourapplication)(.*)$;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||||
|
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
This configuration binds the application to ``/yourapplication``. If you
|
||||||
|
want to have it in the URL root it's a bit simpler because you don't
|
||||||
|
have to figure out how to calculate ``PATH_INFO`` and ``SCRIPT_NAME``::
|
||||||
|
|
||||||
|
location / { try_files $uri @yourapplication; }
|
||||||
|
location @yourapplication {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_script_name;
|
||||||
|
fastcgi_param SCRIPT_NAME "";
|
||||||
|
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
Running FastCGI Processes
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Since nginx and others do not load FastCGI apps, you have to do it by
|
||||||
|
yourself. `Supervisor can manage FastCGI processes.
|
||||||
|
<http://supervisord.org/configuration.html#fcgi-program-x-section-settings>`_
|
||||||
|
You can look around for other FastCGI process managers or write a script
|
||||||
|
to run your `.fcgi` file at boot, e.g. using a SysV ``init.d`` script.
|
||||||
|
For a temporary solution, you can always run the ``.fcgi`` script inside
|
||||||
|
GNU screen. See ``man screen`` for details, and note that this is a
|
||||||
|
manual solution which does not persist across system restart::
|
||||||
|
|
||||||
|
$ screen
|
||||||
|
$ /var/www/yourapplication/yourapplication.fcgi
|
||||||
|
|
||||||
|
Debugging
|
||||||
|
---------
|
||||||
|
|
||||||
|
FastCGI deployments tend to be hard to debug on most web servers. Very
|
||||||
|
often the only thing the server log tells you is something along the
|
||||||
|
lines of "premature end of headers". In order to debug the application
|
||||||
|
the only thing that can really give you ideas why it breaks is switching
|
||||||
|
to the correct user and executing the application by hand.
|
||||||
|
|
||||||
|
This example assumes your application is called `application.fcgi` and
|
||||||
|
that your web server user is `www-data`::
|
||||||
|
|
||||||
|
$ su www-data
|
||||||
|
$ cd /var/www/yourapplication
|
||||||
|
$ python application.fcgi
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "yourapplication.fcgi", line 4, in <module>
|
||||||
|
ImportError: No module named yourapplication
|
||||||
|
|
||||||
|
In this case the error seems to be "yourapplication" not being on the
|
||||||
|
python path. Common problems are:
|
||||||
|
|
||||||
|
- Relative paths being used. Don't rely on the current working directory.
|
||||||
|
- The code depending on environment variables that are not set by the
|
||||||
|
web server.
|
||||||
|
- Different python interpreters being used.
|
||||||
|
|
||||||
|
.. _nginx: http://nginx.org/
|
||||||
|
.. _lighttpd: http://www.lighttpd.net/
|
||||||
|
.. _cherokee: http://cherokee-project.com/
|
||||||
|
.. _flup: https://pypi.org/project/flup/
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
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``. This is
|
|
||||||
not the same as Python's ``async/await``, or the ASGI server spec.
|
|
||||||
|
|
||||||
gevent provides a WSGI server that can handle many connections at once
|
|
||||||
instead of one per worker process. See :doc:`/gevent` for more
|
|
||||||
information about enabling it in your application.
|
|
||||||
|
|
||||||
.. _gevent: https://www.gevent.org/
|
|
||||||
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
When using gevent, greenlet>=1.0 is required. 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.
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The default sync worker is appropriate for most use cases. If you need numerous,
|
|
||||||
long running, concurrent connections, Gunicorn provides an asynchronous worker
|
|
||||||
using `gevent`_. This is not the same as Python's ``async/await``, or the ASGI
|
|
||||||
server spec. See :doc:`/gevent` for more information about enabling it in your
|
|
||||||
application.
|
|
||||||
|
|
||||||
.. _gevent: https://www.gevent.org/
|
|
||||||
|
|
||||||
When using gevent, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is
|
|
||||||
required.
|
|
||||||
|
|
||||||
.. 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
|
|
||||||
|
|
@ -1,78 +1,39 @@
|
||||||
Deploying to Production
|
.. _deployment:
|
||||||
=======================
|
|
||||||
|
|
||||||
After developing your application, you'll want to make it available
|
Deployment Options
|
||||||
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
|
While lightweight and easy to use, **Flask's built-in server is not suitable
|
||||||
serving your application publicly to millions of users or privately /
|
for production** as it doesn't scale well and by default serves only one
|
||||||
locally to a single user. **Do not use the development server when
|
request at a time. Some of the options available for properly running Flask in
|
||||||
deploying to production. It is intended for use only during local
|
production are documented here.
|
||||||
development. It is not designed to be particularly secure, stable, or
|
|
||||||
efficient.**
|
|
||||||
|
|
||||||
Self-Hosted Options
|
If you want to deploy your Flask application to a WSGI server not listed here,
|
||||||
|
look up the server documentation about how to use a WSGI app with it. Just
|
||||||
|
remember that your :class:`Flask` application object is the actual WSGI
|
||||||
|
application.
|
||||||
|
|
||||||
|
|
||||||
|
Hosted options
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- `Deploying Flask on Heroku <https://devcenter.heroku.com/articles/getting-started-with-python>`_
|
||||||
|
- `Deploying Flask on OpenShift <https://developers.openshift.com/en/python-flask.html>`_
|
||||||
|
- `Deploying Flask on Webfaction <http://flask.pocoo.org/snippets/65/>`_
|
||||||
|
- `Deploying Flask on Google App Engine <https://github.com/kamalgill/flask-appengine-template>`_
|
||||||
|
- `Deploying Flask on AWS Elastic Beanstalk <http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||||
|
- `Sharing your Localhost Server with Localtunnel <http://flask.pocoo.org/snippets/89/>`_
|
||||||
|
- `Deploying on Azure (IIS) <https://azure.microsoft.com/documentation/articles/web-sites-python-configure/>`_
|
||||||
|
- `Deploying on PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||||
|
|
||||||
|
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::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 2
|
||||||
|
|
||||||
gunicorn
|
mod_wsgi
|
||||||
waitress
|
wsgi-standalone
|
||||||
mod_wsgi
|
uwsgi
|
||||||
uwsgi
|
fastcgi
|
||||||
gevent
|
cgi
|
||||||
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.
|
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,217 @@
|
||||||
mod_wsgi
|
.. _mod_wsgi-deployment:
|
||||||
========
|
|
||||||
|
|
||||||
`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server.
|
mod_wsgi (Apache)
|
||||||
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.
|
If you are using the `Apache`_ webserver, consider using `mod_wsgi`_.
|
||||||
* 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
|
.. admonition:: Watch Out
|
||||||
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/
|
Please make sure in advance that any ``app.run()`` calls you might
|
||||||
.. _mod_wsgi: https://modwsgi.readthedocs.io/
|
have in your application file are inside an ``if __name__ ==
|
||||||
.. _Apache httpd: https://httpd.apache.org/
|
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||||
|
not called because this will always start a local WSGI server which
|
||||||
|
we do not want if we deploy that application to mod_wsgi.
|
||||||
|
|
||||||
|
.. _Apache: http://httpd.apache.org/
|
||||||
|
|
||||||
Installing
|
Installing `mod_wsgi`
|
||||||
----------
|
---------------------
|
||||||
|
|
||||||
Installing mod_wsgi requires a compiler and the Apache server and
|
If you don't have `mod_wsgi` installed yet you have to either install it
|
||||||
development headers installed. You will get an error if they are not.
|
using a package manager or compile it yourself. The mod_wsgi
|
||||||
How to install them depends on the OS and package manager that you use.
|
`installation instructions`_ cover source installations on UNIX systems.
|
||||||
|
|
||||||
Create a virtualenv, install your application, then install
|
If you are using Ubuntu/Debian you can apt-get it and activate it as
|
||||||
``mod_wsgi``.
|
follows:
|
||||||
|
|
||||||
.. code-block:: text
|
.. sourcecode:: text
|
||||||
|
|
||||||
$ cd hello-app
|
# apt-get install libapache2-mod-wsgi
|
||||||
$ python -m venv .venv
|
|
||||||
$ . .venv/bin/activate
|
|
||||||
$ pip install . # install your application
|
|
||||||
$ pip install mod_wsgi
|
|
||||||
|
|
||||||
|
If you are using a yum based distribution (Fedora, OpenSUSE, etc..) you
|
||||||
|
can install it as follows:
|
||||||
|
|
||||||
Running
|
.. sourcecode:: text
|
||||||
-------
|
|
||||||
|
|
||||||
The only argument to ``mod_wsgi-express`` specifies a script containing
|
# yum install mod_wsgi
|
||||||
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
|
On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by
|
||||||
:caption: ``wsgi.py``
|
using pkg_add:
|
||||||
|
|
||||||
from hello import app
|
.. sourcecode:: text
|
||||||
|
|
||||||
application = app
|
# pkg install ap22-mod_wsgi2
|
||||||
|
|
||||||
.. code-block:: python
|
If you are using pkgsrc you can install `mod_wsgi` by compiling the
|
||||||
:caption: ``wsgi.py``
|
`www/ap2-wsgi` package.
|
||||||
|
|
||||||
from hello import create_app
|
If you encounter segfaulting child processes after the first apache
|
||||||
|
reload you can safely ignore them. Just restart the server.
|
||||||
|
|
||||||
application = create_app()
|
Creating a `.wsgi` file
|
||||||
|
-----------------------
|
||||||
|
|
||||||
Now run the ``mod_wsgi-express start-server`` command.
|
To run your application you need a :file:`yourapplication.wsgi` file. This file
|
||||||
|
contains the code `mod_wsgi` is executing on startup to get the application
|
||||||
|
object. The object called `application` in that file is then used as
|
||||||
|
application.
|
||||||
|
|
||||||
.. code-block:: text
|
For most applications the following file should be sufficient::
|
||||||
|
|
||||||
$ mod_wsgi-express start-server wsgi.py --processes 4
|
from yourapplication import app as application
|
||||||
|
|
||||||
The ``--processes`` option specifies the number of worker processes to
|
If you don't have a factory function for application creation but a singleton
|
||||||
run; a starting value could be ``CPU * 2``.
|
instance you can directly import that one as `application`.
|
||||||
|
|
||||||
Logs for each request aren't show in the terminal. If an error occurs,
|
Store that file somewhere that you will find it again (e.g.:
|
||||||
its information is written to the error log file shown when starting the
|
:file:`/var/www/yourapplication`) and make sure that `yourapplication` and all
|
||||||
server.
|
the libraries that are in use are on the python load path. If you don't
|
||||||
|
want to install it system wide consider using a `virtual python`_
|
||||||
|
instance. Keep in mind that you will have to actually install your
|
||||||
|
application into the virtualenv as well. Alternatively there is the
|
||||||
|
option to just patch the path in the ``.wsgi`` file before the import::
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '/path/to/the/application')
|
||||||
|
|
||||||
Binding Externally
|
Configuring Apache
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Unlike the other WSGI servers in these docs, mod_wsgi can be run as
|
The last thing you have to do is to create an Apache configuration file
|
||||||
root to bind to privileged ports like 80 and 443. However, it must be
|
for your application. In this example we are telling `mod_wsgi` to
|
||||||
configured to drop permissions to a different user and group for the
|
execute the application under a different user for security reasons:
|
||||||
worker processes.
|
|
||||||
|
|
||||||
For example, if you created a ``hello`` user and group, you should
|
.. sourcecode:: apache
|
||||||
install your virtualenv and application as that user, then tell
|
|
||||||
mod_wsgi to drop to that user after starting.
|
|
||||||
|
|
||||||
.. code-block:: text
|
<VirtualHost *>
|
||||||
|
ServerName example.com
|
||||||
|
|
||||||
$ sudo /home/hello/.venv/bin/mod_wsgi-express start-server \
|
WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5
|
||||||
/home/hello/wsgi.py \
|
WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi
|
||||||
--user hello --group hello --port 80 --processes 4
|
|
||||||
|
<Directory /var/www/yourapplication>
|
||||||
|
WSGIProcessGroup yourapplication
|
||||||
|
WSGIApplicationGroup %{GLOBAL}
|
||||||
|
Order deny,allow
|
||||||
|
Allow from all
|
||||||
|
</Directory>
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
Note: WSGIDaemonProcess isn't implemented in Windows and Apache will
|
||||||
|
refuse to run with the above configuration. On a Windows system, eliminate those lines:
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
<VirtualHost *>
|
||||||
|
ServerName example.com
|
||||||
|
WSGIScriptAlias / C:\yourdir\yourapp.wsgi
|
||||||
|
<Directory C:\yourdir>
|
||||||
|
Order deny,allow
|
||||||
|
Allow from all
|
||||||
|
</Directory>
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
Note: There have been some changes in access control configuration for `Apache 2.4`_.
|
||||||
|
|
||||||
|
.. _Apache 2.4: http://httpd.apache.org/docs/trunk/upgrading.html
|
||||||
|
|
||||||
|
Most notably, the syntax for directory permissions has changed from httpd 2.2
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
Order allow,deny
|
||||||
|
Allow from all
|
||||||
|
|
||||||
|
to httpd 2.4 syntax
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
Require all granted
|
||||||
|
|
||||||
|
|
||||||
|
For more information consult the `mod_wsgi documentation`_.
|
||||||
|
|
||||||
|
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
|
||||||
|
.. _installation instructions: http://modwsgi.readthedocs.io/en/develop/installation.html
|
||||||
|
.. _virtual python: https://pypi.org/project/virtualenv/
|
||||||
|
.. _mod_wsgi documentation: http://modwsgi.readthedocs.io/en/develop/index.html
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
---------------
|
||||||
|
|
||||||
|
If your application does not run, follow this guide to troubleshoot:
|
||||||
|
|
||||||
|
**Problem:** application does not run, errorlog shows SystemExit ignored
|
||||||
|
You have an ``app.run()`` call in your application file that is not
|
||||||
|
guarded by an ``if __name__ == '__main__':`` condition. Either
|
||||||
|
remove that :meth:`~flask.Flask.run` call from the file and move it
|
||||||
|
into a separate :file:`run.py` file or put it into such an if block.
|
||||||
|
|
||||||
|
**Problem:** application gives permission errors
|
||||||
|
Probably caused by your application running as the wrong user. Make
|
||||||
|
sure the folders the application needs access to have the proper
|
||||||
|
privileges set and the application runs as the correct user
|
||||||
|
(``user`` and ``group`` parameter to the `WSGIDaemonProcess`
|
||||||
|
directive)
|
||||||
|
|
||||||
|
**Problem:** application dies with an error on print
|
||||||
|
Keep in mind that mod_wsgi disallows doing anything with
|
||||||
|
:data:`sys.stdout` and :data:`sys.stderr`. You can disable this
|
||||||
|
protection from the config by setting the `WSGIRestrictStdout` to
|
||||||
|
``off``:
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
WSGIRestrictStdout Off
|
||||||
|
|
||||||
|
Alternatively you can also replace the standard out in the .wsgi file
|
||||||
|
with a different stream::
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout = sys.stderr
|
||||||
|
|
||||||
|
**Problem:** accessing resources gives IO errors
|
||||||
|
Your application probably is a single .py file you symlinked into
|
||||||
|
the site-packages folder. Please be aware that this does not work,
|
||||||
|
instead you either have to put the folder into the pythonpath the
|
||||||
|
file is stored in, or convert your application into a package.
|
||||||
|
|
||||||
|
The reason for this is that for non-installed packages, the module
|
||||||
|
filename is used to locate the resources and for symlinks the wrong
|
||||||
|
filename is picked up.
|
||||||
|
|
||||||
|
Support for Automatic Reloading
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
To help deployment tools you can activate support for automatic
|
||||||
|
reloading. Whenever something changes the ``.wsgi`` file, `mod_wsgi` will
|
||||||
|
reload all the daemon processes for us.
|
||||||
|
|
||||||
|
For that, just add the following directive to your `Directory` section:
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
WSGIScriptReloading On
|
||||||
|
|
||||||
|
Working with Virtual Environments
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Virtual environments have the advantage that they never install the
|
||||||
|
required dependencies system wide so you have a better control over what
|
||||||
|
is used where. If you want to use a virtual environment with mod_wsgi
|
||||||
|
you have to modify your ``.wsgi`` file slightly.
|
||||||
|
|
||||||
|
Add the following lines to the top of your ``.wsgi`` file::
|
||||||
|
|
||||||
|
activate_this = '/path/to/env/bin/activate_this.py'
|
||||||
|
execfile(activate_this, dict(__file__=activate_this))
|
||||||
|
|
||||||
|
For Python 3 add the following lines to the top of your ``.wsgi`` file::
|
||||||
|
|
||||||
|
activate_this = '/path/to/env/bin/activate_this.py'
|
||||||
|
with open(activate_this) as file_:
|
||||||
|
exec(file_.read(), dict(__file__=activate_this))
|
||||||
|
|
||||||
|
This sets up the load paths according to the settings of the virtual
|
||||||
|
environment. Keep in mind that the path has to be absolute.
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,143 +1,72 @@
|
||||||
|
.. _deploying-uwsgi:
|
||||||
|
|
||||||
uWSGI
|
uWSGI
|
||||||
=====
|
=====
|
||||||
|
|
||||||
`uWSGI`_ is a fast, compiled server suite with extensive configuration
|
uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
|
||||||
and capabilities beyond a basic server.
|
`cherokee`_; see :ref:`deploying-fastcgi` and :ref:`deploying-wsgi-standalone`
|
||||||
|
for other options. To use your WSGI application with uWSGI protocol you will
|
||||||
|
need a uWSGI server first. uWSGI is both a protocol and an application server;
|
||||||
|
the application server can serve uWSGI, FastCGI, and HTTP protocols.
|
||||||
|
|
||||||
* It can be very performant due to being a compiled program.
|
The most popular uWSGI server is `uwsgi`_, which we will use for this
|
||||||
* It is complex to configure beyond the basic application, and has so
|
guide. Make sure to have it installed to follow along.
|
||||||
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
|
.. admonition:: Watch Out
|
||||||
documentation to understand what features are available.
|
|
||||||
|
|
||||||
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
|
Please make sure in advance that any ``app.run()`` calls you might
|
||||||
|
have in your application file are inside an ``if __name__ ==
|
||||||
|
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||||
|
not called because this will always start a local WSGI server which
|
||||||
|
we do not want if we deploy that application to uWSGI.
|
||||||
|
|
||||||
|
Starting your app with uwsgi
|
||||||
|
----------------------------
|
||||||
|
|
||||||
Installing
|
`uwsgi` is designed to operate on WSGI callables found in python modules.
|
||||||
----------
|
|
||||||
|
|
||||||
uWSGI has multiple ways to install it. The most straightforward is to
|
Given a flask application in myapp.py, use the following command:
|
||||||
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``.
|
.. sourcecode:: text
|
||||||
|
|
||||||
.. code-block:: text
|
$ uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app
|
||||||
|
|
||||||
$ cd hello-app
|
The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi,
|
||||||
$ python -m venv .venv
|
since its smarter about that. It is used together with the ``--mount`` directive
|
||||||
$ . .venv/bin/activate
|
which will make requests to ``/yourapplication`` be directed to ``myapp:app``.
|
||||||
$ pip install . # install your application
|
If your application is accessible at root level, you can use a single ``/``
|
||||||
$ pip install pyuwsgi
|
instead of ``/yourapplication``. ``myapp`` refers to the name of the file of
|
||||||
|
your flask application (without extension) or the module which provides ``app``.
|
||||||
|
``app`` is the callable inside of your application (usually the line reads
|
||||||
|
``app = Flask(__name__)``.
|
||||||
|
|
||||||
If you have a compiler available, you can install the ``uwsgi`` package
|
If you want to deploy your flask application inside of a virtual environment,
|
||||||
instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
|
you need to also add ``--virtualenv /path/to/virtual/environment``. You might
|
||||||
Either method will include SSL support.
|
also need to add ``--plugin python`` or ``--plugin python3`` depending on which
|
||||||
|
python version you use for your project.
|
||||||
|
|
||||||
.. code-block:: text
|
Configuring nginx
|
||||||
|
|
||||||
$ 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 most use cases. If you need numerous,
|
A basic flask nginx configuration looks like this::
|
||||||
long running, concurrent connections, uWSGI provides an asynchronous worker
|
|
||||||
using `gevent`_. This is not the same as Python's ``async/await``, or the ASGI
|
|
||||||
server spec. See :doc:`/gevent` for more information about enabling it in your
|
|
||||||
application.
|
|
||||||
|
|
||||||
.. _gevent: https://www.gevent.org/
|
location = /yourapplication { rewrite ^ /yourapplication/; }
|
||||||
|
location /yourapplication { try_files $uri @yourapplication; }
|
||||||
|
location @yourapplication {
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass unix:/tmp/yourapplication.sock;
|
||||||
|
}
|
||||||
|
|
||||||
When using gevent, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is
|
This configuration binds the application to ``/yourapplication``. If you want
|
||||||
required.
|
to have it in the URL root its a bit simpler::
|
||||||
|
|
||||||
.. code-block:: text
|
location / { try_files $uri @yourapplication; }
|
||||||
|
location @yourapplication {
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass unix:/tmp/yourapplication.sock;
|
||||||
|
}
|
||||||
|
|
||||||
$ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app
|
.. _nginx: http://nginx.org/
|
||||||
|
.. _lighttpd: http://www.lighttpd.net/
|
||||||
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
|
.. _cherokee: http://cherokee-project.com/
|
||||||
*** Operational MODE: async ***
|
.. _uwsgi: http://projects.unbit.it/uwsgi/
|
||||||
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] ***
|
|
||||||
|
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
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.
|
|
||||||
134
docs/deploying/wsgi-standalone.rst
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
.. _deploying-wsgi-standalone:
|
||||||
|
|
||||||
|
Standalone WSGI Containers
|
||||||
|
==========================
|
||||||
|
|
||||||
|
There are popular servers written in Python that contain WSGI applications and
|
||||||
|
serve HTTP. These servers stand alone when they run; you can proxy to them
|
||||||
|
from your web server. Note the section on :ref:`deploying-proxy-setups` if you
|
||||||
|
run into issues.
|
||||||
|
|
||||||
|
Gunicorn
|
||||||
|
--------
|
||||||
|
|
||||||
|
`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork
|
||||||
|
worker model ported from Ruby's Unicorn project. It supports both `eventlet`_
|
||||||
|
and `greenlet`_. Running a Flask application on this server is quite simple::
|
||||||
|
|
||||||
|
gunicorn myproject:app
|
||||||
|
|
||||||
|
`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``.
|
||||||
|
For example, to run a Flask application with 4 worker processes (``-w
|
||||||
|
4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``)::
|
||||||
|
|
||||||
|
gunicorn -w 4 -b 127.0.0.1:4000 myproject:app
|
||||||
|
|
||||||
|
.. _Gunicorn: http://gunicorn.org/
|
||||||
|
.. _eventlet: http://eventlet.net/
|
||||||
|
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||||
|
|
||||||
|
Gevent
|
||||||
|
-------
|
||||||
|
|
||||||
|
`Gevent`_ is a coroutine-based Python networking library that uses
|
||||||
|
`greenlet`_ to provide a high-level synchronous API on top of `libev`_
|
||||||
|
event loop::
|
||||||
|
|
||||||
|
from gevent.wsgi import WSGIServer
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
http_server = WSGIServer(('', 5000), app)
|
||||||
|
http_server.serve_forever()
|
||||||
|
|
||||||
|
.. _Gevent: http://www.gevent.org/
|
||||||
|
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||||
|
.. _libev: http://software.schmorp.de/pkg/libev.html
|
||||||
|
|
||||||
|
Twisted Web
|
||||||
|
-----------
|
||||||
|
|
||||||
|
`Twisted Web`_ is the web server shipped with `Twisted`_, a mature,
|
||||||
|
non-blocking event-driven networking library. Twisted Web comes with a
|
||||||
|
standard WSGI container which can be controlled from the command line using
|
||||||
|
the ``twistd`` utility::
|
||||||
|
|
||||||
|
twistd web --wsgi myproject.app
|
||||||
|
|
||||||
|
This example will run a Flask application called ``app`` from a module named
|
||||||
|
``myproject``.
|
||||||
|
|
||||||
|
Twisted Web supports many flags and options, and the ``twistd`` utility does
|
||||||
|
as well; see ``twistd -h`` and ``twistd web -h`` for more information. For
|
||||||
|
example, to run a Twisted Web server in the foreground, on port 8080, with an
|
||||||
|
application from ``myproject``::
|
||||||
|
|
||||||
|
twistd -n web --port 8080 --wsgi myproject.app
|
||||||
|
|
||||||
|
.. _Twisted: https://twistedmatrix.com/
|
||||||
|
.. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb
|
||||||
|
|
||||||
|
.. _deploying-proxy-setups:
|
||||||
|
|
||||||
|
Proxy Setups
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you deploy your application using one of these servers behind an HTTP proxy
|
||||||
|
you will need to rewrite a few headers in order for the application to work.
|
||||||
|
The two problematic values in the WSGI environment usually are ``REMOTE_ADDR``
|
||||||
|
and ``HTTP_HOST``. You can configure your httpd to pass these headers, or you
|
||||||
|
can fix them in middleware. Werkzeug ships a fixer that will solve some common
|
||||||
|
setups, but you might want to write your own WSGI middleware for specific
|
||||||
|
setups.
|
||||||
|
|
||||||
|
Here's a simple nginx configuration which proxies to an application served on
|
||||||
|
localhost at port 8000, setting appropriate headers:
|
||||||
|
|
||||||
|
.. sourcecode:: nginx
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8000/;
|
||||||
|
proxy_redirect off;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
If your httpd is not providing these headers, the most common setup invokes the
|
||||||
|
host being set from ``X-Forwarded-Host`` and the remote address from
|
||||||
|
``X-Forwarded-For``::
|
||||||
|
|
||||||
|
from werkzeug.contrib.fixers import ProxyFix
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||||
|
|
||||||
|
.. admonition:: Trusting Headers
|
||||||
|
|
||||||
|
Please keep in mind that it is a security issue to use such a middleware in
|
||||||
|
a non-proxy setup because it will blindly trust the incoming headers which
|
||||||
|
might be forged by malicious clients.
|
||||||
|
|
||||||
|
If you want to rewrite the headers from another header, you might want to
|
||||||
|
use a fixer like this::
|
||||||
|
|
||||||
|
class CustomProxyFix(object):
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
host = environ.get('HTTP_X_FHOST', '')
|
||||||
|
if host:
|
||||||
|
environ['HTTP_HOST'] = host
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
app.wsgi_app = CustomProxyFix(app.wsgi_app)
|
||||||
106
docs/design.rst
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _design:
|
||||||
|
|
||||||
Design Decisions in Flask
|
Design Decisions in Flask
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
@ -39,7 +41,7 @@ the time. There are ways to fake multiple applications with a single
|
||||||
application object, like maintaining a stack of applications, but this
|
application object, like maintaining a stack of applications, but this
|
||||||
causes some problems I won't outline here in detail. Now the question is:
|
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
|
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
|
time? A good example for this is unittesting. When you want to test
|
||||||
something it can be very helpful to create a minimal application 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
|
specific behavior. When the application object is deleted everything it
|
||||||
allocated will be freed again.
|
allocated will be freed again.
|
||||||
|
|
@ -74,8 +76,8 @@ there are better ways to do that so that you do not lose the reference
|
||||||
to the application object :meth:`~flask.Flask.wsgi_app`).
|
to the application object :meth:`~flask.Flask.wsgi_app`).
|
||||||
|
|
||||||
Furthermore this design makes it possible to use a factory function to
|
Furthermore this design makes it possible to use a factory function to
|
||||||
create the application which is very helpful for unit testing and similar
|
create the application which is very helpful for unittesting and similar
|
||||||
things (:doc:`/patterns/appfactories`).
|
things (:ref:`app-factories`).
|
||||||
|
|
||||||
The Routing System
|
The Routing System
|
||||||
------------------
|
------------------
|
||||||
|
|
@ -96,10 +98,10 @@ is ambiguous.
|
||||||
One Template Engine
|
One Template Engine
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Flask decides on one template engine: Jinja. Why doesn't Flask have a
|
Flask decides on one template engine: Jinja2. Why doesn't Flask have a
|
||||||
pluggable template engine interface? You can obviously use a different
|
pluggable template engine interface? You can obviously use a different
|
||||||
template engine, but Flask will still configure Jinja for you. While
|
template engine, but Flask will still configure Jinja2 for you. While
|
||||||
that limitation that Jinja is *always* configured will probably go away,
|
that limitation that Jinja2 is *always* configured will probably go away,
|
||||||
the decision to bundle one template engine and use that will not.
|
the decision to bundle one template engine and use that will not.
|
||||||
|
|
||||||
Template engines are like programming languages and each of those engines
|
Template engines are like programming languages and each of those engines
|
||||||
|
|
@ -107,19 +109,19 @@ 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
|
all work the same: you tell the engine to evaluate a template with a set
|
||||||
of variables and take the return value as string.
|
of variables and take the return value as string.
|
||||||
|
|
||||||
But that's about where similarities end. Jinja for example has an
|
But that's about where similarities end. Jinja2 for example has an
|
||||||
extensive filter system, a certain way to do template inheritance,
|
extensive filter system, a certain way to do template inheritance, support
|
||||||
support for reusable blocks (macros) that can be used from inside
|
for reusable blocks (macros) that can be used from inside templates and
|
||||||
templates and also from Python code, supports iterative template
|
also from Python code, uses Unicode for all operations, supports
|
||||||
rendering, configurable syntax and more. On the other hand an engine
|
iterative template rendering, configurable syntax and more. On the other
|
||||||
like Genshi is based on XML stream evaluation, template inheritance by
|
hand an engine like Genshi is based on XML stream evaluation, template
|
||||||
taking the availability of XPath into account and more. Mako on the
|
inheritance by taking the availability of XPath into account and more.
|
||||||
other hand treats templates similar to Python modules.
|
Mako on the other hand treats templates similar to Python modules.
|
||||||
|
|
||||||
When it comes to connecting a template engine with an application or
|
When it comes to connecting a template engine with an application or
|
||||||
framework there is more than just rendering templates. For instance,
|
framework there is more than just rendering templates. For instance,
|
||||||
Flask uses Jinja's extensive autoescaping support. Also it provides
|
Flask uses Jinja2's extensive autoescaping support. Also it provides
|
||||||
ways to access macros from Jinja templates.
|
ways to access macros from Jinja2 templates.
|
||||||
|
|
||||||
A template abstraction layer that would not take the unique features of
|
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
|
the template engines away is a science on its own and a too large
|
||||||
|
|
@ -130,27 +132,11 @@ being present. You can easily use your own templating language, but an
|
||||||
extension could still depend on Jinja itself.
|
extension could still depend on Jinja itself.
|
||||||
|
|
||||||
|
|
||||||
What does "micro" mean?
|
Micro with Dependencies
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
“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
|
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
|
libraries (namely Werkzeug and Jinja2). Why shouldn't it? If we look
|
||||||
over to the Ruby side of web development there we have a protocol very
|
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
|
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
|
looks very much like a WSGI rendition for Ruby. But nearly all
|
||||||
|
|
@ -169,39 +155,22 @@ infrastructure, packages with dependencies are no longer an issue and
|
||||||
there are very few reasons against having libraries that depend on others.
|
there are very few reasons against having libraries that depend on others.
|
||||||
|
|
||||||
|
|
||||||
Context Locals
|
Thread Locals
|
||||||
--------------
|
-------------
|
||||||
|
|
||||||
Flask uses special context locals and proxies to provide access to the
|
Flask uses thread local objects (context local objects in fact, they
|
||||||
current app and request data to any code running during a request, CLI command,
|
support greenlet contexts as well) for request, session and an extra
|
||||||
etc. Context locals are specific to the worker handling the activity, such as a
|
object you can put your own things on (:data:`~flask.g`). Why is that and
|
||||||
thread, process, coroutine, or greenlet.
|
isn't that a bad idea?
|
||||||
|
|
||||||
The context and proxies help solve two development issues: circular imports, and
|
Yes it is usually not such a bright idea to use thread locals. They cause
|
||||||
passing around global data. :data:`.current_app` can be used to access the
|
troubles for servers that are not based on the concept of threads and make
|
||||||
application object without needing to import the app object directly, avoiding
|
large applications harder to maintain. However Flask is just not designed
|
||||||
circular import issues. :data:`.request`, :data:`.session`, and :data:`.g` can
|
for large applications or asynchronous servers. Flask wants to make it
|
||||||
be imported to access the current data for the request, rather than needing to
|
quick and easy to write a traditional web application.
|
||||||
pass them as arguments through every single function in your project.
|
|
||||||
|
|
||||||
|
Also see the :ref:`becomingbig` section of the documentation for some
|
||||||
Async/await and ASGI support
|
inspiration for larger applications based on Flask.
|
||||||
----------------------------
|
|
||||||
|
|
||||||
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
|
What Flask is, What Flask is Not
|
||||||
|
|
@ -209,7 +178,7 @@ What Flask is, What Flask is Not
|
||||||
|
|
||||||
Flask will never have a database layer. It will not have a form library
|
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
|
or anything else in that direction. Flask itself just bridges to Werkzeug
|
||||||
to implement a proper WSGI application and to Jinja to handle templating.
|
to implement a proper WSGI application and to Jinja2 to handle templating.
|
||||||
It also binds to a few common standard library packages such as logging.
|
It also binds to a few common standard library packages such as logging.
|
||||||
Everything else is up for extensions.
|
Everything else is up for extensions.
|
||||||
|
|
||||||
|
|
@ -218,12 +187,5 @@ 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
|
into the core. The majority of web applications will need a template
|
||||||
engine in some sort. However not every application needs a SQL database.
|
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.
|
The idea of Flask is to build a good foundation for all applications.
|
||||||
Everything else is up to you or extensions.
|
Everything else is up to you or extensions.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
Handling Application Errors
|
.. _application-errors:
|
||||||
===========================
|
|
||||||
|
|
||||||
Applications fail, servers fail. Sooner or later you will see an exception
|
Application Errors
|
||||||
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
|
.. versionadded:: 0.3
|
||||||
|
|
||||||
|
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:
|
errors:
|
||||||
|
|
||||||
- the client terminated the request early and the application was still
|
- the client terminated the request early and the application was still
|
||||||
|
|
@ -16,16 +20,13 @@ errors:
|
||||||
- a programming error in a library you are using
|
- a programming error in a library you are using
|
||||||
- network connection of the server to another system failed
|
- 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
|
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
|
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
|
production mode, Flask will display a very simple page for you and log the
|
||||||
page for you and log the exception to the :attr:`~flask.Flask.logger`.
|
exception to the :attr:`~flask.Flask.logger`.
|
||||||
|
|
||||||
But there is more you can do, and we will cover some better setups to deal
|
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.
|
with errors.
|
||||||
|
|
||||||
|
|
||||||
.. _error-logging-tools:
|
|
||||||
|
|
||||||
Error Logging Tools
|
Error Logging Tools
|
||||||
-------------------
|
-------------------
|
||||||
|
|
@ -33,491 +34,361 @@ Error Logging Tools
|
||||||
Sending error mails, even if just for critical ones, can become
|
Sending error mails, even if just for critical ones, can become
|
||||||
overwhelming if enough users are hitting the error and log files are
|
overwhelming if enough users are hitting the error and log files are
|
||||||
typically never looked at. This is why we recommend using `Sentry
|
typically never looked at. This is why we recommend using `Sentry
|
||||||
<https://sentry.io/>`_ for dealing with application errors. It's
|
<http://www.getsentry.com/>`_ for dealing with application errors. It's
|
||||||
available as a source-available project `on GitHub
|
available as an Open Source project `on GitHub
|
||||||
<https://github.com/getsentry/sentry>`_ and is also available as a `hosted version
|
<https://github.com/getsentry/sentry>`__ and is also available as a `hosted version
|
||||||
<https://sentry.io/signup/>`_ which you can try for free. Sentry
|
<https://getsentry.com/signup/>`_ which you can try for free. Sentry
|
||||||
aggregates duplicate errors, captures the full stack trace and local
|
aggregates duplicate errors, captures the full stack trace and local
|
||||||
variables for debugging, and sends you mails based on new errors or
|
variables for debugging, and sends you mails based on new errors or
|
||||||
frequency thresholds.
|
frequency thresholds.
|
||||||
|
|
||||||
To use Sentry you need to install the ``sentry-sdk`` client with extra
|
To use Sentry you need to install the `raven` client::
|
||||||
``flask`` dependencies.
|
|
||||||
|
|
||||||
.. code-block:: text
|
$ pip install raven
|
||||||
|
|
||||||
$ pip install sentry-sdk[flask]
|
And then add this to your Flask app::
|
||||||
|
|
||||||
And then add this to your Flask app:
|
from raven.contrib.flask import Sentry
|
||||||
|
sentry = Sentry(app, dsn='YOUR_DSN_HERE')
|
||||||
|
|
||||||
.. code-block:: python
|
Or if you are using factories you can also init it later::
|
||||||
|
|
||||||
import sentry_sdk
|
from raven.contrib.flask import Sentry
|
||||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
sentry = Sentry(dsn='YOUR_DSN_HERE')
|
||||||
|
|
||||||
sentry_sdk.init('YOUR_DSN_HERE', integrations=[FlaskIntegration()])
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
sentry.init_app(app)
|
||||||
|
...
|
||||||
|
return app
|
||||||
|
|
||||||
The ``YOUR_DSN_HERE`` value needs to be replaced with the DSN value you
|
The `YOUR_DSN_HERE` value needs to be replaced with the DSN value you get
|
||||||
get from your Sentry installation.
|
from your Sentry installation.
|
||||||
|
|
||||||
After installation, failures leading to an Internal Server Error
|
Afterwards failures are automatically reported to Sentry and from there
|
||||||
are automatically reported to Sentry and from there you can
|
you can receive error notifications.
|
||||||
receive error notifications.
|
|
||||||
|
|
||||||
See also:
|
.. _error-handlers:
|
||||||
|
|
||||||
- Sentry also supports catching errors from a worker queue
|
Error handlers
|
||||||
(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.
|
You might want to show custom error pages to the user when an error occurs.
|
||||||
This can be done by registering error handlers.
|
This can be done by registering error handlers.
|
||||||
|
|
||||||
An error handler is a function that returns a response when a type of error is
|
Error handlers are normal :ref:`views` but instead of being registered for
|
||||||
raised, similar to how a view is a function that returns a response when a
|
routes, they are registered for exceptions that are raised while trying to
|
||||||
request URL is matched. It is passed the instance of the error being handled,
|
do something else.
|
||||||
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
|
Registering
|
||||||
```````````
|
```````````
|
||||||
|
|
||||||
Register handlers by decorating a function with
|
Register error handlers using :meth:`~flask.Flask.errorhandler` or
|
||||||
:meth:`~flask.Flask.errorhandler`. Or use
|
:meth:`~flask.Flask.register_error_handler`::
|
||||||
: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)
|
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
||||||
def handle_bad_request(e):
|
def handle_bad_request(e):
|
||||||
return 'bad request!', 400
|
return 'bad request!'
|
||||||
|
|
||||||
# or, without the decorator
|
app.register_error_handler(400, lambda e: 'bad request!')
|
||||||
app.register_error_handler(400, handle_bad_request)
|
|
||||||
|
|
||||||
|
Those two ways are equivalent, but the first one is more clear and leaves
|
||||||
|
you with a function to call on your whim (and in tests). Note that
|
||||||
:exc:`werkzeug.exceptions.HTTPException` subclasses like
|
:exc:`werkzeug.exceptions.HTTPException` subclasses like
|
||||||
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
|
:exc:`~werkzeug.exceptions.BadRequest` from the example and their HTTP codes
|
||||||
when registering handlers. (``BadRequest.code == 400``)
|
are interchangeable when handed to the registration methods or decorator
|
||||||
|
(``BadRequest.code == 400``).
|
||||||
|
|
||||||
Non-standard HTTP codes cannot be registered by code because they are not known
|
You are however not limited to :exc:`~werkzeug.exceptions.HTTPException`
|
||||||
by Werkzeug. Instead, define a subclass of
|
or HTTP status codes but can register a handler for every exception class you
|
||||||
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
|
like.
|
||||||
register and raise that exception class.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. versionchanged:: 0.11
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
Errorhandlers are now prioritized by specificity of the exception classes
|
||||||
|
they are registered for instead of the order they are registered in.
|
||||||
|
|
||||||
Handling
|
Handling
|
||||||
````````
|
````````
|
||||||
|
|
||||||
When building a Flask application you *will* run into exceptions. If some part
|
Once an exception instance is raised, its class hierarchy is traversed,
|
||||||
of your code breaks while handling a request (and you have no error handlers
|
and searched for in the exception classes for which handlers are registered.
|
||||||
registered), a "500 Internal Server Error"
|
The most specific handler is selected.
|
||||||
(:exc:`~werkzeug.exceptions.InternalServerError`) will be returned by default.
|
|
||||||
Similarly, "404 Not Found"
|
E.g. if an instance of :exc:`ConnectionRefusedError` is raised, and a handler
|
||||||
(:exc:`~werkzeug.exceptions.NotFound`) error will occur if a request is sent to an unregistered route.
|
is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`,
|
||||||
If a route receives an unallowed request method, a "405 Method Not Allowed"
|
the more specific :exc:`ConnectionRefusedError` handler is called on the
|
||||||
(:exc:`~werkzeug.exceptions.MethodNotAllowed`) will be raised. These are all
|
exception instance, and its response is shown to the user.
|
||||||
subclasses of :class:`~werkzeug.exceptions.HTTPException` and are provided by
|
|
||||||
default in Flask.
|
Error Mails
|
||||||
|
-----------
|
||||||
Flask gives you the ability to raise any HTTP exception registered by
|
|
||||||
Werkzeug. However, the default HTTP exceptions return simple exception
|
If the application runs in production mode (which it will do on your
|
||||||
pages. You might want to show custom error pages to the user when an error occurs.
|
server) you might not see any log messages. The reason for that is that
|
||||||
This can be done by registering error handlers.
|
Flask by default will just report to the WSGI error stream or stderr
|
||||||
|
(depending on what's available). Where this ends up is sometimes hard to
|
||||||
When Flask catches an exception while handling a request, it is first looked up by code.
|
find. Often it's in your webserver's log files.
|
||||||
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
|
I can pretty much promise you however that if you only use a logfile for
|
||||||
generic message about their code, while other exceptions are converted to a
|
the application errors you will never look at it except for debugging an
|
||||||
generic "500 Internal Server Error".
|
issue when a user reported it for you. What you probably want instead is
|
||||||
|
a mail the second the exception happened. Then you get an alert and you
|
||||||
For example, if an instance of :exc:`ConnectionRefusedError` is raised,
|
can do something about it.
|
||||||
and a handler is registered for :exc:`ConnectionError` and
|
|
||||||
:exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError`
|
Flask uses the Python builtin logging system, and it can actually send
|
||||||
handler is called with the exception instance to generate the response.
|
you mails for errors which is probably what you want. Here is how you can
|
||||||
|
configure the Flask logger to send you mails for exceptions::
|
||||||
Handlers registered on the blueprint take precedence over those registered
|
|
||||||
globally on the application, assuming a blueprint is handling the request that
|
ADMINS = ['yourname@example.com']
|
||||||
raises the exception. However, the blueprint cannot handle 404 routing errors
|
if not app.debug:
|
||||||
because the 404 occurs at the routing level before the blueprint can be
|
import logging
|
||||||
determined.
|
from logging.handlers import SMTPHandler
|
||||||
|
mail_handler = SMTPHandler('127.0.0.1',
|
||||||
|
'server-error@example.com',
|
||||||
Generic Exception Handlers
|
ADMINS, 'YourApplication Failed')
|
||||||
``````````````````````````
|
mail_handler.setLevel(logging.ERROR)
|
||||||
|
app.logger.addHandler(mail_handler)
|
||||||
It is possible to register error handlers for very generic base classes
|
|
||||||
such as ``HTTPException`` or even ``Exception``. However, be aware that
|
So what just happened? We created a new
|
||||||
these will catch more than you might expect.
|
:class:`~logging.handlers.SMTPHandler` that will send mails with the mail
|
||||||
|
server listening on ``127.0.0.1`` to all the `ADMINS` from the address
|
||||||
For example, an error handler for ``HTTPException`` might be useful for turning
|
*server-error@example.com* with the subject "YourApplication Failed". If
|
||||||
the default HTML errors pages into JSON. However, this
|
your mail server requires credentials, these can also be provided. For
|
||||||
handler will trigger for things you don't cause directly, such as 404
|
that check out the documentation for the
|
||||||
and 405 errors during routing. Be sure to craft your handler carefully
|
:class:`~logging.handlers.SMTPHandler`.
|
||||||
so you don't lose information about the HTTP error.
|
|
||||||
|
We also tell the handler to only send errors and more critical messages.
|
||||||
.. code-block:: python
|
Because we certainly don't want to get a mail for warnings or other
|
||||||
|
useless logs that might happen during request handling.
|
||||||
from flask import json
|
|
||||||
from werkzeug.exceptions import HTTPException
|
Before you run that in production, please also look at :ref:`logformat` to
|
||||||
|
put more information into that error mail. That will save you from a lot
|
||||||
@app.errorhandler(HTTPException)
|
of frustration.
|
||||||
def handle_exception(e):
|
|
||||||
"""Return JSON instead of HTML for HTTP errors."""
|
|
||||||
# start with the correct headers and status code from the error
|
Logging to a File
|
||||||
response = e.get_response()
|
-----------------
|
||||||
# replace the body with JSON
|
|
||||||
response.data = json.dumps({
|
Even if you get mails, you probably also want to log warnings. It's a
|
||||||
"code": e.code,
|
good idea to keep as much information around that might be required to
|
||||||
"name": e.name,
|
debug a problem. By default as of Flask 0.11, errors are logged to your
|
||||||
"description": e.description,
|
webserver's log automatically. Warnings however are not. Please note
|
||||||
})
|
that Flask itself will not issue any warnings in the core system, so it's
|
||||||
response.content_type = "application/json"
|
your responsibility to warn in the code if something seems odd.
|
||||||
return response
|
|
||||||
|
There are a couple of handlers provided by the logging system out of the
|
||||||
An error handler for ``Exception`` might seem useful for changing how
|
box but not all of them are useful for basic error logging. The most
|
||||||
all errors, even unhandled ones, are presented to the user. However,
|
interesting are probably the following:
|
||||||
this is similar to doing ``except Exception:`` in Python, it will
|
|
||||||
capture *all* otherwise unhandled errors, including all HTTP status
|
- :class:`~logging.FileHandler` - logs messages to a file on the
|
||||||
codes.
|
filesystem.
|
||||||
|
- :class:`~logging.handlers.RotatingFileHandler` - logs messages to a file
|
||||||
In most cases it will be safer to register handlers for more
|
on the filesystem and will rotate after a certain number of messages.
|
||||||
specific exceptions. Since ``HTTPException`` instances are valid WSGI
|
- :class:`~logging.handlers.NTEventLogHandler` - will log to the system
|
||||||
responses, you could also pass them through directly.
|
event log of a Windows system. If you are deploying on a Windows box,
|
||||||
|
this is what you want to use.
|
||||||
.. code-block:: python
|
- :class:`~logging.handlers.SysLogHandler` - sends logs to a UNIX
|
||||||
|
syslog.
|
||||||
from werkzeug.exceptions import HTTPException
|
|
||||||
|
Once you picked your log handler, do like you did with the SMTP handler
|
||||||
@app.errorhandler(Exception)
|
above, just make sure to use a lower setting (I would recommend
|
||||||
def handle_exception(e):
|
`WARNING`)::
|
||||||
# pass through HTTP errors
|
|
||||||
if isinstance(e, HTTPException):
|
if not app.debug:
|
||||||
return e
|
import logging
|
||||||
|
from themodule import TheHandlerYouWant
|
||||||
# now you're handling non-HTTP exceptions only
|
file_handler = TheHandlerYouWant(...)
|
||||||
return render_template("500_generic.html", e=e), 500
|
file_handler.setLevel(logging.WARNING)
|
||||||
|
app.logger.addHandler(file_handler)
|
||||||
Error handlers still respect the exception class hierarchy. If you
|
|
||||||
register handlers for both ``HTTPException`` and ``Exception``, the
|
.. _logformat:
|
||||||
``Exception`` handler will not handle ``HTTPException`` subclasses
|
|
||||||
because the ``HTTPException`` handler is more specific.
|
Controlling the Log Format
|
||||||
|
--------------------------
|
||||||
|
|
||||||
Unhandled Exceptions
|
By default a handler will only write the message string into a file or
|
||||||
````````````````````
|
send you that message as mail. A log record stores more information,
|
||||||
|
and it makes a lot of sense to configure your logger to also contain that
|
||||||
When there is no error handler registered for an exception, a 500
|
information so that you have a better idea of why that error happened, and
|
||||||
Internal Server Error will be returned instead. See
|
more importantly, where it did.
|
||||||
:meth:`flask.Flask.handle_exception` for information about this
|
|
||||||
behavior.
|
A formatter can be instantiated with a format string. Note that
|
||||||
|
tracebacks are appended to the log entry automatically. You don't have to
|
||||||
If there is an error handler registered for ``InternalServerError``,
|
do that in the log formatter format string.
|
||||||
this will be invoked. As of Flask 1.1.0, this error handler will always
|
|
||||||
be passed an instance of ``InternalServerError``, not the original
|
Here are some example setups:
|
||||||
unhandled error.
|
|
||||||
|
Email
|
||||||
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
|
from logging import Formatter
|
||||||
interactive debugger will be shown.
|
mail_handler.setFormatter(Formatter('''
|
||||||
|
Message type: %(levelname)s
|
||||||
|
Location: %(pathname)s:%(lineno)d
|
||||||
Custom Error Pages
|
Module: %(module)s
|
||||||
------------------
|
Function: %(funcName)s
|
||||||
|
Time: %(asctime)s
|
||||||
Sometimes when building a Flask application, you might want to raise a
|
|
||||||
:exc:`~werkzeug.exceptions.HTTPException` to signal to the user that
|
Message:
|
||||||
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
|
%(message)s
|
||||||
werkzeug as desired. It will also provide a plain black and white error page
|
'''))
|
||||||
for you with a basic description, but nothing fancy.
|
|
||||||
|
File logging
|
||||||
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
|
from logging import Formatter
|
||||||
username and we can't find it, we raise a "404 Not Found".
|
file_handler.setFormatter(Formatter(
|
||||||
|
'%(asctime)s %(levelname)s: %(message)s '
|
||||||
.. code-block:: python
|
'[in %(pathname)s:%(lineno)d]'
|
||||||
|
))
|
||||||
from flask import abort, render_template, request
|
|
||||||
|
|
||||||
# a username needs to be supplied in the query args
|
Complex Log Formatting
|
||||||
# a successful request would be like /profile?username=jack
|
``````````````````````
|
||||||
@app.route("/profile")
|
|
||||||
def user_profile():
|
Here is a list of useful formatting variables for the format string. Note
|
||||||
username = request.arg.get("username")
|
that this list is not complete, consult the official documentation of the
|
||||||
# if a username isn't supplied in the request, return a 400 bad request
|
:mod:`logging` package for a full list.
|
||||||
if username is None:
|
|
||||||
abort(400)
|
.. tabularcolumns:: |p{3cm}|p{12cm}|
|
||||||
|
|
||||||
user = get_user(username=username)
|
+------------------+----------------------------------------------------+
|
||||||
# if a user can't be found by their username, return 404 not found
|
| Format | Description |
|
||||||
if user is None:
|
+==================+====================================================+
|
||||||
abort(404)
|
| ``%(levelname)s``| Text logging level for the message |
|
||||||
|
| | (``'DEBUG'``, ``'INFO'``, ``'WARNING'``, |
|
||||||
return render_template("profile.html", user=user)
|
| | ``'ERROR'``, ``'CRITICAL'``). |
|
||||||
|
+------------------+----------------------------------------------------+
|
||||||
Here is another example implementation for a "404 Page Not Found" exception:
|
| ``%(pathname)s`` | Full pathname of the source file where the |
|
||||||
|
| | logging call was issued (if available). |
|
||||||
.. code-block:: python
|
+------------------+----------------------------------------------------+
|
||||||
|
| ``%(filename)s`` | Filename portion of pathname. |
|
||||||
from flask import render_template
|
+------------------+----------------------------------------------------+
|
||||||
|
| ``%(module)s`` | Module (name portion of filename). |
|
||||||
@app.errorhandler(404)
|
+------------------+----------------------------------------------------+
|
||||||
def page_not_found(e):
|
| ``%(funcName)s`` | Name of function containing the logging call. |
|
||||||
# note that we set the 404 status explicitly
|
+------------------+----------------------------------------------------+
|
||||||
return render_template('404.html'), 404
|
| ``%(lineno)d`` | Source line number where the logging call was |
|
||||||
|
| | issued (if available). |
|
||||||
When using :doc:`/patterns/appfactories`:
|
+------------------+----------------------------------------------------+
|
||||||
|
| ``%(asctime)s`` | Human-readable time when the |
|
||||||
.. code-block:: python
|
| | :class:`~logging.LogRecord` was created. |
|
||||||
|
| | By default this is of the form |
|
||||||
from flask import Flask, render_template
|
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
|
||||||
|
| | the comma are millisecond portion of the time). |
|
||||||
def page_not_found(e):
|
| | This can be changed by subclassing the formatter |
|
||||||
return render_template('404.html'), 404
|
| | and overriding the |
|
||||||
|
| | :meth:`~logging.Formatter.formatTime` method. |
|
||||||
def create_app(config_filename):
|
+------------------+----------------------------------------------------+
|
||||||
app = Flask(__name__)
|
| ``%(message)s`` | The logged message, computed as ``msg % args`` |
|
||||||
app.register_error_handler(404, page_not_found)
|
+------------------+----------------------------------------------------+
|
||||||
return app
|
|
||||||
|
If you want to further customize the formatting, you can subclass the
|
||||||
An example template might be this:
|
formatter. The formatter has three interesting methods:
|
||||||
|
|
||||||
.. code-block:: html+jinja
|
:meth:`~logging.Formatter.format`:
|
||||||
|
handles the actual formatting. It is passed a
|
||||||
{% extends "layout.html" %}
|
:class:`~logging.LogRecord` object and has to return the formatted
|
||||||
{% block title %}Page Not Found{% endblock %}
|
string.
|
||||||
{% block body %}
|
:meth:`~logging.Formatter.formatTime`:
|
||||||
<h1>Page Not Found</h1>
|
called for `asctime` formatting. If you want a different time format
|
||||||
<p>What you were looking for is just not there.
|
you can override this method.
|
||||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
:meth:`~logging.Formatter.formatException`
|
||||||
{% endblock %}
|
called for exception formatting. It is passed an :attr:`~sys.exc_info`
|
||||||
|
tuple and has to return a string. The default is usually fine, you
|
||||||
|
don't have to override it.
|
||||||
Further Examples
|
|
||||||
````````````````
|
For more information, head over to the official documentation.
|
||||||
|
|
||||||
The above examples wouldn't actually be an improvement on the default
|
|
||||||
exception pages. We can create a custom 500.html template like this:
|
Other Libraries
|
||||||
|
---------------
|
||||||
.. code-block:: html+jinja
|
|
||||||
|
So far we only configured the logger your application created itself.
|
||||||
{% extends "layout.html" %}
|
Other libraries might log themselves as well. For example, SQLAlchemy uses
|
||||||
{% block title %}Internal Server Error{% endblock %}
|
logging heavily in its core. While there is a method to configure all
|
||||||
{% block body %}
|
loggers at once in the :mod:`logging` package, I would not recommend using
|
||||||
<h1>Internal Server Error</h1>
|
it. There might be a situation in which you want to have multiple
|
||||||
<p>Oops... we seem to have made a mistake, sorry!</p>
|
separate applications running side by side in the same Python interpreter
|
||||||
<p><a href="{{ url_for('index') }}">Go somewhere nice instead</a>
|
and then it becomes impossible to have different logging setups for those.
|
||||||
{% endblock %}
|
|
||||||
|
Instead, I would recommend figuring out which loggers you are interested
|
||||||
It can be implemented by rendering the template on "500 Internal Server Error":
|
in, getting the loggers with the :func:`~logging.getLogger` function and
|
||||||
|
iterating over them to attach handlers::
|
||||||
.. code-block:: python
|
|
||||||
|
from logging import getLogger
|
||||||
from flask import render_template
|
loggers = [app.logger, getLogger('sqlalchemy'),
|
||||||
|
getLogger('otherlibrary')]
|
||||||
@app.errorhandler(500)
|
for logger in loggers:
|
||||||
def internal_server_error(e):
|
logger.addHandler(mail_handler)
|
||||||
# note that we set the 500 status explicitly
|
logger.addHandler(file_handler)
|
||||||
return render_template('500.html'), 500
|
|
||||||
|
|
||||||
When using :doc:`/patterns/appfactories`:
|
Debugging Application Errors
|
||||||
|
============================
|
||||||
.. code-block:: python
|
|
||||||
|
For production applications, configure your application with logging and
|
||||||
from flask import Flask, render_template
|
notifications as described in :ref:`application-errors`. This section provides
|
||||||
|
pointers when debugging deployment configuration and digging deeper with a
|
||||||
def internal_server_error(e):
|
full-featured Python debugger.
|
||||||
return render_template('500.html'), 500
|
|
||||||
|
|
||||||
def create_app():
|
When in Doubt, Run Manually
|
||||||
app = Flask(__name__)
|
---------------------------
|
||||||
app.register_error_handler(500, internal_server_error)
|
|
||||||
return app
|
Having problems getting your application configured for production? If you
|
||||||
|
have shell access to your host, verify that you can run your application
|
||||||
When using :doc:`/blueprints`:
|
manually from the shell in the deployment environment. Be sure to run under
|
||||||
|
the same user account as the configured deployment to troubleshoot permission
|
||||||
.. code-block:: python
|
issues. You can use Flask's builtin development server with `debug=True` on
|
||||||
|
your production host, which is helpful in catching configuration issues, but
|
||||||
from flask import Blueprint
|
**be sure to do this temporarily in a controlled environment.** Do not run in
|
||||||
|
production with `debug=True`.
|
||||||
blog = Blueprint('blog', __name__)
|
|
||||||
|
|
||||||
# as a decorator
|
.. _working-with-debuggers:
|
||||||
@blog.errorhandler(500)
|
|
||||||
def internal_server_error(e):
|
Working with Debuggers
|
||||||
return render_template('500.html'), 500
|
----------------------
|
||||||
|
|
||||||
# or with register_error_handler
|
To dig deeper, possibly to trace code execution, Flask provides a debugger out
|
||||||
blog.register_error_handler(500, internal_server_error)
|
of the box (see :ref:`debug-mode`). If you would like to use another Python
|
||||||
|
debugger, note that debuggers interfere with each other. You have to set some
|
||||||
|
options in order to use your favorite debugger:
|
||||||
Blueprint Error Handlers
|
|
||||||
------------------------
|
* ``debug`` - whether to enable debug mode and catch exceptions
|
||||||
|
* ``use_debugger`` - whether to use the internal Flask debugger
|
||||||
In :doc:`/blueprints`, most error handlers will work as expected.
|
* ``use_reloader`` - whether to reload and fork the process on exception
|
||||||
However, there is a caveat concerning handlers for 404 and 405
|
|
||||||
exceptions. These error handlers are only invoked from an appropriate
|
``debug`` must be True (i.e., exceptions must be caught) in order for the other
|
||||||
``raise`` statement or a call to ``abort`` in another of the blueprint's
|
two options to have any value.
|
||||||
view functions; they are not invoked by, e.g., an invalid URL access.
|
|
||||||
|
If you're using Aptana/Eclipse for debugging you'll need to set both
|
||||||
This is because the blueprint does not "own" a certain URL space, so
|
``use_debugger`` and ``use_reloader`` to False.
|
||||||
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
|
A possible useful pattern for configuration is to set the following in your
|
||||||
execute different handling strategies for these errors based on URL
|
config.yaml (change the block as appropriate for your application, of course)::
|
||||||
prefixes, they may be defined at the application level using the
|
|
||||||
``request`` proxy object.
|
FLASK:
|
||||||
|
DEBUG: True
|
||||||
.. code-block:: python
|
DEBUG_WITH_APTANA: True
|
||||||
|
|
||||||
from flask import jsonify, render_template
|
Then in your application's entry-point (main.py), you could have something like::
|
||||||
|
|
||||||
# at the application level
|
if __name__ == "__main__":
|
||||||
# not the blueprint level
|
# To allow aptana to receive errors, set use_debugger=False
|
||||||
@app.errorhandler(404)
|
app = create_app(config="config.yaml")
|
||||||
def page_not_found(e):
|
|
||||||
# if a request is in our blog URL space
|
if app.debug: use_debugger = True
|
||||||
if request.path.startswith('/blog/'):
|
try:
|
||||||
# we return a custom blog 404 page
|
# Disable Flask's debugger if external debugger is requested
|
||||||
return render_template("blog/404.html"), 404
|
use_debugger = not(app.config.get('DEBUG_WITH_APTANA'))
|
||||||
else:
|
except:
|
||||||
# otherwise we return our generic site-wide 404 page
|
pass
|
||||||
return render_template("404.html"), 404
|
app.run(use_debugger=use_debugger, debug=app.debug,
|
||||||
|
use_reloader=use_debugger, host='0.0.0.0')
|
||||||
@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.
|
|
||||||
|
|
|
||||||
|
|
@ -1,305 +1,418 @@
|
||||||
|
.. _extension-dev:
|
||||||
|
|
||||||
Flask Extension Development
|
Flask Extension Development
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
.. currentmodule:: flask
|
Flask, being a microframework, often requires some repetitive steps to get
|
||||||
|
a third party library working. Because very often these steps could be
|
||||||
|
abstracted to support multiple projects the `Flask Extension Registry`_
|
||||||
|
was created.
|
||||||
|
|
||||||
Extensions are extra packages that add functionality to a Flask
|
If you want to create your own Flask extension for something that does not
|
||||||
application. While `PyPI`_ contains many Flask extensions, you may not
|
exist yet, this guide to extension development will help you get your
|
||||||
find one that fits your need. If this is the case, you can create your
|
extension running in no time and to feel like users would expect your
|
||||||
own, and publish it for others to use as well.
|
extension to behave.
|
||||||
|
|
||||||
This guide will show how to create a Flask extension, and some of the
|
.. _Flask Extension Registry: http://flask.pocoo.org/extensions/
|
||||||
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
|
Anatomy of an Extension
|
||||||
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
|
Extensions are all located in a package called ``flask_something``
|
||||||
using one extension won't feel completely lost with another. This can
|
where "something" is the name of the library you want to bridge. So for
|
||||||
only work if collaboration happens early.
|
example if you plan to add support for a library named `simplexml` to
|
||||||
|
Flask, you would name your extension's package ``flask_simplexml``.
|
||||||
|
|
||||||
|
The name of the actual extension (the human readable name) however would
|
||||||
|
be something like "Flask-SimpleXML". Make sure to include the name
|
||||||
|
"Flask" somewhere in that name and that you check the capitalization.
|
||||||
|
This is how users can then register dependencies to your extension in
|
||||||
|
their :file:`setup.py` files.
|
||||||
|
|
||||||
|
Flask sets up a redirect package called :data:`flask.ext` where users
|
||||||
|
should import the extensions from. If you for instance have a package
|
||||||
|
called ``flask_something`` users would import it as
|
||||||
|
``flask.ext.something``. This is done to transition from the old
|
||||||
|
namespace packages. See :ref:`ext-import-transition` for more details.
|
||||||
|
|
||||||
|
But what do extensions look like themselves? An extension has to ensure
|
||||||
|
that it works with multiple Flask application instances at once. This is
|
||||||
|
a requirement because many people will use patterns like the
|
||||||
|
:ref:`app-factories` pattern to create their application as needed to aid
|
||||||
|
unittests and to support multiple configurations. Because of that it is
|
||||||
|
crucial that your application supports that kind of behavior.
|
||||||
|
|
||||||
|
Most importantly the extension must be shipped with a :file:`setup.py` file and
|
||||||
|
registered on PyPI. Also the development checkout link should work so
|
||||||
|
that people can easily install the development version into their
|
||||||
|
virtualenv without having to download the library by hand.
|
||||||
|
|
||||||
|
Flask extensions must be licensed under a BSD, MIT or more liberal license
|
||||||
|
to be able to be enlisted in the Flask Extension Registry. Keep in mind
|
||||||
|
that the Flask Extension Registry is a moderated place and libraries will
|
||||||
|
be reviewed upfront if they behave as required.
|
||||||
|
|
||||||
|
"Hello Flaskext!"
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
So let's get started with creating such a Flask extension. The extension
|
||||||
|
we want to create here will provide very basic support for SQLite3.
|
||||||
|
|
||||||
|
First we create the following folder structure::
|
||||||
|
|
||||||
|
flask-sqlite3/
|
||||||
|
flask_sqlite3.py
|
||||||
|
LICENSE
|
||||||
|
README
|
||||||
|
|
||||||
|
Here's the contents of the most important files:
|
||||||
|
|
||||||
|
setup.py
|
||||||
|
````````
|
||||||
|
|
||||||
|
The next file that is absolutely required is the :file:`setup.py` file which is
|
||||||
|
used to install your Flask extension. The following contents are
|
||||||
|
something you can work with::
|
||||||
|
|
||||||
|
"""
|
||||||
|
Flask-SQLite3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
This is the description for that library
|
||||||
|
"""
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
Naming
|
setup(
|
||||||
------
|
name='Flask-SQLite3',
|
||||||
|
version='1.0',
|
||||||
|
url='http://example.com/flask-sqlite3/',
|
||||||
|
license='BSD',
|
||||||
|
author='Your Name',
|
||||||
|
author_email='your-email@example.com',
|
||||||
|
description='Very short description',
|
||||||
|
long_description=__doc__,
|
||||||
|
py_modules=['flask_sqlite3'],
|
||||||
|
# if you would be using a package instead use packages instead
|
||||||
|
# of py_modules:
|
||||||
|
# packages=['flask_sqlite3'],
|
||||||
|
zip_safe=False,
|
||||||
|
include_package_data=True,
|
||||||
|
platforms='any',
|
||||||
|
install_requires=[
|
||||||
|
'Flask'
|
||||||
|
],
|
||||||
|
classifiers=[
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||||
|
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
A Flask extension typically has ``flask`` in its name as a prefix or
|
That's a lot of code but you can really just copy/paste that from existing
|
||||||
suffix. If it wraps another library, it should include the library name
|
extensions and adapt.
|
||||||
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
|
flask_sqlite3.py
|
||||||
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:
|
Now this is where your extension code goes. But how exactly should such
|
||||||
|
an extension look like? What are the best practices? Continue reading
|
||||||
|
for some insight.
|
||||||
|
|
||||||
- ``Flask-Name`` imported as ``flask_name``
|
Initializing Extensions
|
||||||
- ``flask-name-lower`` imported as ``flask_name_lower``
|
-----------------------
|
||||||
- ``Flask-ComboName`` imported as ``flask_comboname``
|
|
||||||
- ``Name-Flask`` imported as ``name_flask``
|
Many extensions will need some kind of initialization step. For example,
|
||||||
|
consider an application that's currently connecting to SQLite like the
|
||||||
|
documentation suggests (:ref:`sqlite3`). So how does the extension
|
||||||
|
know the name of the application object?
|
||||||
|
|
||||||
|
Quite simple: you pass it to it.
|
||||||
|
|
||||||
|
There are two recommended ways for an extension to initialize:
|
||||||
|
|
||||||
|
initialization functions:
|
||||||
|
|
||||||
|
If your extension is called `helloworld` you might have a function
|
||||||
|
called ``init_helloworld(app[, extra_args])`` that initializes the
|
||||||
|
extension for that application. It could attach before / after
|
||||||
|
handlers etc.
|
||||||
|
|
||||||
|
classes:
|
||||||
|
|
||||||
|
Classes work mostly like initialization functions but can later be
|
||||||
|
used to further change the behavior. For an example look at how the
|
||||||
|
`OAuth extension`_ works: there is an `OAuth` object that provides
|
||||||
|
some helper functions like `OAuth.remote_app` to create a reference to
|
||||||
|
a remote application that uses OAuth.
|
||||||
|
|
||||||
|
What to use depends on what you have in mind. For the SQLite 3 extension
|
||||||
|
we will use the class-based approach because it will provide users with an
|
||||||
|
object that handles opening and closing database connections.
|
||||||
|
|
||||||
|
What's important about classes is that they encourage to be shared around
|
||||||
|
on module level. In that case, the object itself must not under any
|
||||||
|
circumstances store any application specific state and must be shareable
|
||||||
|
between different application.
|
||||||
|
|
||||||
|
The Extension Code
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Here's the contents of the `flask_sqlite3.py` for copy/paste::
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
# Find the stack on which we want to store the database connection.
|
||||||
|
# Starting with Flask 0.9, the _app_ctx_stack is the correct one,
|
||||||
|
# before that we need to use the _request_ctx_stack.
|
||||||
|
try:
|
||||||
|
from flask import _app_ctx_stack as stack
|
||||||
|
except ImportError:
|
||||||
|
from flask import _request_ctx_stack as stack
|
||||||
|
|
||||||
|
|
||||||
The Extension Class and Initialization
|
class SQLite3(object):
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
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):
|
def __init__(self, app=None):
|
||||||
|
self.app = app
|
||||||
if app is not None:
|
if app is not None:
|
||||||
self.init_app(app)
|
self.init_app(app)
|
||||||
|
|
||||||
def init_app(self, app):
|
def init_app(self, app):
|
||||||
app.before_request(...)
|
app.config.setdefault('SQLITE3_DATABASE', ':memory:')
|
||||||
|
# Use the newstyle teardown_appcontext if it's available,
|
||||||
|
# otherwise fall back to the request context
|
||||||
|
if hasattr(app, 'teardown_appcontext'):
|
||||||
|
app.teardown_appcontext(self.teardown)
|
||||||
|
else:
|
||||||
|
app.teardown_request(self.teardown)
|
||||||
|
|
||||||
It is important that the app is not stored on the extension, don't do
|
def connect(self):
|
||||||
``self.app = app``. The only time the extension should have direct
|
return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])
|
||||||
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,
|
def teardown(self, exception):
|
||||||
avoids circular import issues when importing the extension instance
|
ctx = stack.top
|
||||||
elsewhere in a user's code, and makes testing with different
|
if hasattr(ctx, 'sqlite3_db'):
|
||||||
configurations easier.
|
ctx.sqlite3_db.close()
|
||||||
|
|
||||||
.. code-block:: python
|
@property
|
||||||
|
def connection(self):
|
||||||
hello = HelloExtension()
|
ctx = stack.top
|
||||||
|
if ctx is not None:
|
||||||
def create_app():
|
if not hasattr(ctx, 'sqlite3_db'):
|
||||||
app = Flask(__name__)
|
ctx.sqlite3_db = self.connect()
|
||||||
hello.init_app(app)
|
return ctx.sqlite3_db
|
||||||
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
|
So here's what these lines of code do:
|
||||||
---------------
|
|
||||||
|
|
||||||
There are many ways that an extension can add behavior. Any setup
|
1. The ``__init__`` method takes an optional app object and, if supplied, will
|
||||||
methods that are available on the :class:`Flask` object can be used
|
call ``init_app``.
|
||||||
during an extension's ``init_app`` method.
|
2. The ``init_app`` method exists so that the ``SQLite3`` object can be
|
||||||
|
instantiated without requiring an app object. This method supports the
|
||||||
|
factory pattern for creating applications. The ``init_app`` will set the
|
||||||
|
configuration for the database, defaulting to an in memory database if
|
||||||
|
no configuration is supplied. In addition, the ``init_app`` method attaches
|
||||||
|
the ``teardown`` handler. It will try to use the newstyle app context
|
||||||
|
handler and if it does not exist, falls back to the request context
|
||||||
|
one.
|
||||||
|
3. Next, we define a ``connect`` method that opens a database connection.
|
||||||
|
4. Finally, we add a ``connection`` property that on first access opens
|
||||||
|
the database connection and stores it on the context. This is also
|
||||||
|
the recommended way to handling resources: fetch resources lazily the
|
||||||
|
first time they are used.
|
||||||
|
|
||||||
A common pattern is to use :meth:`~Flask.before_request` to initialize
|
Note here that we're attaching our database connection to the top
|
||||||
some data or a connection at the beginning of each request, then
|
application context via ``_app_ctx_stack.top``. Extensions should use
|
||||||
:meth:`~Flask.teardown_request` to clean it up at the end. This can be
|
the top context for storing their own information with a sufficiently
|
||||||
stored on :data:`.g`, discussed more below.
|
complex name. Note that we're falling back to the
|
||||||
|
``_request_ctx_stack.top`` if the application is using an older
|
||||||
|
version of Flask that does not support it.
|
||||||
|
|
||||||
A more lazy approach is to provide a method that initializes and caches
|
So why did we decide on a class-based approach here? Because using our
|
||||||
the data or connection. For example, a ``ext.get_db`` method could
|
extension looks something like this::
|
||||||
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
|
from flask import Flask
|
||||||
might want to add some specific views as well. In this case, you could
|
from flask_sqlite3 import SQLite3
|
||||||
define a :class:`Blueprint`, then call :meth:`~Flask.register_blueprint`
|
|
||||||
during ``init_app`` to add the blueprint to the app.
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_pyfile('the-config.cfg')
|
||||||
|
db = SQLite3(app)
|
||||||
|
|
||||||
Configuration Techniques
|
You can then use the database from views like this::
|
||||||
------------------------
|
|
||||||
|
|
||||||
There can be multiple levels and sources of configuration for an
|
@app.route('/')
|
||||||
extension. You should consider what parts of your extension fall into
|
def show_all():
|
||||||
each one.
|
cur = db.connection.cursor()
|
||||||
|
cur.execute(...)
|
||||||
|
|
||||||
- Configuration per application instance, through ``app.config``
|
Likewise if you are outside of a request but you are using Flask 0.9 or
|
||||||
values. This is configuration that could reasonably change for each
|
later with the app context support, you can use the database in the same
|
||||||
deployment of an application. A common example is a URL to an
|
way::
|
||||||
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.
|
with app.app_context():
|
||||||
|
cur = db.connection.cursor()
|
||||||
|
cur.execute(...)
|
||||||
|
|
||||||
It's up to you to decide what configuration is appropriate for your
|
At the end of the ``with`` block the teardown handles will be executed
|
||||||
extension, based on what you need and what you want to support.
|
automatically.
|
||||||
|
|
||||||
Configuration should not be changed after the application setup phase is
|
Additionally, the ``init_app`` method is used to support the factory pattern
|
||||||
complete and the server begins handling requests. Configuration is
|
for creating apps::
|
||||||
global, any changes to it are not guaranteed to be visible to other
|
|
||||||
workers.
|
|
||||||
|
|
||||||
|
db = Sqlite3()
|
||||||
Data During a Request
|
# Then later on.
|
||||||
---------------------
|
app = create_app('the-config.cfg')
|
||||||
|
|
||||||
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)
|
db.init_app(app)
|
||||||
blog.init_app(app)
|
|
||||||
|
|
||||||
Another technique could be to use an attribute on the extension, such as
|
Keep in mind that supporting this factory pattern for creating apps is required
|
||||||
``self.post_model`` from above. Add the extension to ``app.extensions``
|
for approved flask extensions (described below).
|
||||||
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
|
.. admonition:: Note on ``init_app``
|
||||||
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
|
As you noticed, ``init_app`` does not assign ``app`` to ``self``. This
|
||||||
perfect solution here, only different strategies and tradeoffs depending
|
is intentional! Class based Flask extensions must only store the
|
||||||
on your needs and how much customization you want to offer. Luckily,
|
application on the object when the application was passed to the
|
||||||
this sort of resource dependency is not a common need for most
|
constructor. This tells the extension: I am not interested in using
|
||||||
extensions. Remember, if you need help with design, ask on our
|
multiple applications.
|
||||||
`Discord Chat`_ or `GitHub Discussions`_.
|
|
||||||
|
When the extension needs to find the current application and it does
|
||||||
|
not have a reference to it, it must either use the
|
||||||
|
:data:`~flask.current_app` context local or change the API in a way
|
||||||
|
that you can pass the application explicitly.
|
||||||
|
|
||||||
|
|
||||||
Recommended Extension Guidelines
|
Using _app_ctx_stack
|
||||||
--------------------------------
|
--------------------
|
||||||
|
|
||||||
Flask previously had the concept of "approved extensions", where the
|
In the example above, before every request, a ``sqlite3_db`` variable is
|
||||||
Flask maintainers evaluated the quality, support, and compatibility of
|
assigned to ``_app_ctx_stack.top``. In a view function, this variable is
|
||||||
the extensions before listing them. While the list became too difficult
|
accessible using the ``connection`` property of ``SQLite3``. During the
|
||||||
to maintain over time, the guidelines are still relevant to all
|
teardown of a request, the ``sqlite3_db`` connection is closed. By using
|
||||||
extensions maintained and developed today, as they help the Flask
|
this pattern, the *same* connection to the sqlite3 database is accessible
|
||||||
ecosystem remain consistent and compatible.
|
to anything that needs it for the duration of the request.
|
||||||
|
|
||||||
1. An extension requires a maintainer. In the event an extension author
|
If the :data:`~flask._app_ctx_stack` does not exist because the user uses
|
||||||
would like to move beyond the project, the project should find a new
|
an old version of Flask, it is recommended to fall back to
|
||||||
maintainer and transfer access to the repository, documentation,
|
:data:`~flask._request_ctx_stack` which is bound to a request.
|
||||||
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
|
Teardown Behavior
|
||||||
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
|
*This is only relevant if you want to support Flask 0.6 and older*
|
||||||
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
|
Due to the change in Flask 0.7 regarding functions that are run at the end
|
||||||
.. _Discord Chat: https://discord.gg/pallets
|
of the request your extension will have to be extra careful there if it
|
||||||
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
|
wants to continue to support older versions of Flask. The following
|
||||||
.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/
|
pattern is a good way to support both::
|
||||||
.. _Pallets-Eco: https://github.com/pallets-eco
|
|
||||||
.. _EOL calendar: https://devguide.python.org/versions/
|
def close_connection(response):
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
ctx.sqlite3_db.close()
|
||||||
|
return response
|
||||||
|
|
||||||
|
if hasattr(app, 'teardown_request'):
|
||||||
|
app.teardown_request(close_connection)
|
||||||
|
else:
|
||||||
|
app.after_request(close_connection)
|
||||||
|
|
||||||
|
Strictly speaking the above code is wrong, because teardown functions are
|
||||||
|
passed the exception and typically don't return anything. However because
|
||||||
|
the return value is discarded this will just work assuming that the code
|
||||||
|
in between does not touch the passed parameter.
|
||||||
|
|
||||||
|
Learn from Others
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
This documentation only touches the bare minimum for extension
|
||||||
|
development. If you want to learn more, it's a very good idea to check
|
||||||
|
out existing extensions on the `Flask Extension Registry`_. If you feel
|
||||||
|
lost there is still the `mailinglist`_ and the `IRC channel`_ to get some
|
||||||
|
ideas for nice looking APIs. Especially if you do something nobody before
|
||||||
|
you did, it might be a very good idea to get some more input. This not
|
||||||
|
only to get an idea about what people might want to have from an
|
||||||
|
extension, but also to avoid having multiple developers working on pretty
|
||||||
|
much the same side by side.
|
||||||
|
|
||||||
|
Remember: good API design is hard, so introduce your project on the
|
||||||
|
mailinglist, and let other developers give you a helping hand with
|
||||||
|
designing the API.
|
||||||
|
|
||||||
|
The best Flask extensions are extensions that share common idioms for the
|
||||||
|
API. And this can only work if collaboration happens early.
|
||||||
|
|
||||||
|
Approved Extensions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Flask also has the concept of approved extensions. Approved extensions
|
||||||
|
are tested as part of Flask itself to ensure extensions do not break on
|
||||||
|
new releases. These approved extensions are listed on the `Flask
|
||||||
|
Extension Registry`_ and marked appropriately. If you want your own
|
||||||
|
extension to be approved you have to follow these guidelines:
|
||||||
|
|
||||||
|
0. An approved Flask extension requires a maintainer. In the event an
|
||||||
|
extension author would like to move beyond the project, the project should
|
||||||
|
find a new maintainer including full source hosting transition and PyPI
|
||||||
|
access. If no maintainer is available, give access to the Flask core team.
|
||||||
|
1. An approved Flask extension must provide exactly one package or module
|
||||||
|
named ``flask_extensionname``.
|
||||||
|
2. It must ship a testing suite that can either be invoked with ``make test``
|
||||||
|
or ``python setup.py test``. For test suites invoked with ``make
|
||||||
|
test`` the extension has to ensure that all dependencies for the test
|
||||||
|
are installed automatically. If tests are invoked with ``python setup.py
|
||||||
|
test``, test dependencies can be specified in the :file:`setup.py` file. The
|
||||||
|
test suite also has to be part of the distribution.
|
||||||
|
3. APIs of approved extensions will be checked for the following
|
||||||
|
characteristics:
|
||||||
|
|
||||||
|
- an approved extension has to support multiple applications
|
||||||
|
running in the same Python process.
|
||||||
|
- it must be possible to use the factory pattern for creating
|
||||||
|
applications.
|
||||||
|
|
||||||
|
4. The license must be BSD/MIT/WTFPL licensed.
|
||||||
|
5. The naming scheme for official extensions is *Flask-ExtensionName* or
|
||||||
|
*ExtensionName-Flask*.
|
||||||
|
6. Approved extensions must define all their dependencies in the
|
||||||
|
:file:`setup.py` file unless a dependency cannot be met because it is not
|
||||||
|
available on PyPI.
|
||||||
|
7. The extension must have documentation that uses one of the two Flask
|
||||||
|
themes for Sphinx documentation.
|
||||||
|
8. The setup.py description (and thus the PyPI description) has to
|
||||||
|
link to the documentation, website (if there is one) and there
|
||||||
|
must be a link to automatically install the development version
|
||||||
|
(``PackageName==dev``).
|
||||||
|
9. The ``zip_safe`` flag in the setup script must be set to ``False``,
|
||||||
|
even if the extension would be safe for zipping.
|
||||||
|
10. An extension currently has to support Python 2.6 as well as
|
||||||
|
Python 2.7
|
||||||
|
|
||||||
|
|
||||||
|
.. _ext-import-transition:
|
||||||
|
|
||||||
|
Extension Import Transition
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
In early versions of Flask we recommended using namespace packages for Flask
|
||||||
|
extensions, of the form ``flaskext.foo``. This turned out to be problematic in
|
||||||
|
practice because it meant that multiple ``flaskext`` packages coexist.
|
||||||
|
Consequently we have recommended to name extensions ``flask_foo`` over
|
||||||
|
``flaskext.foo`` for a long time.
|
||||||
|
|
||||||
|
Flask 0.8 introduced a redirect import system as a compatibility aid for app
|
||||||
|
developers: Importing ``flask.ext.foo`` would try ``flask_foo`` and
|
||||||
|
``flaskext.foo`` in that order.
|
||||||
|
|
||||||
|
As of Flask 0.11, most Flask extensions have transitioned to the new naming
|
||||||
|
schema. The ``flask.ext.foo`` compatibility alias is still in Flask 0.11 but is
|
||||||
|
now deprecated -- you should use ``flask_foo``.
|
||||||
|
|
||||||
|
|
||||||
|
.. _OAuth extension: http://pythonhosted.org/Flask-OAuth/
|
||||||
|
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
||||||
|
.. _IRC channel: http://flask.pocoo.org/community/irc/
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,35 @@
|
||||||
Extensions
|
.. _extensions:
|
||||||
==========
|
|
||||||
|
|
||||||
Extensions are extra packages that add functionality to a Flask
|
Flask Extensions
|
||||||
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.
|
|
||||||
|
|
||||||
|
Flask extensions extend the functionality of Flask in various different
|
||||||
|
ways. For instance they add support for databases and other common tasks.
|
||||||
|
|
||||||
Finding Extensions
|
Finding Extensions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Flask extensions are usually named "Flask-Foo" or "Foo-Flask". You can
|
Flask extensions are listed on the `Flask Extension Registry`_ and can be
|
||||||
search PyPI for packages tagged with `Framework :: Flask <pypi_>`_.
|
downloaded with :command:`easy_install` or :command:`pip`. If you add a Flask extension
|
||||||
|
as dependency to your :file:`requirements.txt` or :file:`setup.py` file they are
|
||||||
|
usually installed with a simple command or when your application installs.
|
||||||
|
|
||||||
Using Extensions
|
Using Extensions
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Consult each extension's documentation for installation, configuration,
|
Extensions typically have documentation that goes along that shows how to
|
||||||
and usage instructions. Generally, extensions pull their own
|
use it. There are no general rules in how extensions are supposed to
|
||||||
configuration from :attr:`app.config <flask.Flask.config>` and are
|
behave but they are imported from common locations. If you have an
|
||||||
passed an application instance during initialization. For example,
|
extension called ``Flask-Foo`` or ``Foo-Flask`` it should be always
|
||||||
an extension called "Flask-Foo" might be used like this::
|
importable from ``flask_foo``::
|
||||||
|
|
||||||
from flask_foo import Foo
|
|
||||||
|
|
||||||
foo = Foo()
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config.update(
|
|
||||||
FOO_BAR='baz',
|
|
||||||
FOO_SPAM='eggs',
|
|
||||||
)
|
|
||||||
|
|
||||||
foo.init_app(app)
|
|
||||||
|
|
||||||
|
import flask_foo
|
||||||
|
|
||||||
Building Extensions
|
Building Extensions
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
While `PyPI <pypi_>`_ contains many Flask extensions, you may not find
|
While `Flask Extension Registry`_ contains many Flask extensions, you may not find
|
||||||
an extension that fits your need. If this is the case, you can create
|
an extension that fits your need. If this is the case, you can always create your own.
|
||||||
your own, and publish it for others to use as well. Read
|
Consider reading :ref:`extension-dev` to develop your own Flask extension.
|
||||||
:doc:`extensiondev` to develop your own Flask extension.
|
|
||||||
|
|
||||||
|
.. _Flask Extension Registry: http://flask.pocoo.org/extensions/
|
||||||
.. _pypi: https://pypi.org/search/?c=Framework+%3A%3A+Flask
|
|
||||||
|
|
|
||||||
118
docs/flaskstyle.sty
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
\definecolor{TitleColor}{rgb}{0,0,0}
|
||||||
|
\definecolor{InnerLinkColor}{rgb}{0,0,0}
|
||||||
|
|
||||||
|
\renewcommand{\maketitle}{%
|
||||||
|
\begin{titlepage}%
|
||||||
|
\let\footnotesize\small
|
||||||
|
\let\footnoterule\relax
|
||||||
|
\ifsphinxpdfoutput
|
||||||
|
\begingroup
|
||||||
|
% This \def is required to deal with multi-line authors; it
|
||||||
|
% changes \\ to ', ' (comma-space), making it pass muster for
|
||||||
|
% generating document info in the PDF file.
|
||||||
|
\def\\{, }
|
||||||
|
\pdfinfo{
|
||||||
|
/Author (\@author)
|
||||||
|
/Title (\@title)
|
||||||
|
}
|
||||||
|
\endgroup
|
||||||
|
\fi
|
||||||
|
\begin{flushright}%
|
||||||
|
%\sphinxlogo%
|
||||||
|
{\center
|
||||||
|
\vspace*{3cm}
|
||||||
|
\includegraphics{logo.pdf}
|
||||||
|
\vspace{3cm}
|
||||||
|
\par
|
||||||
|
{\rm\Huge \@title \par}%
|
||||||
|
{\em\LARGE \py@release\releaseinfo \par}
|
||||||
|
{\large
|
||||||
|
\@date \par
|
||||||
|
\py@authoraddress \par
|
||||||
|
}}%
|
||||||
|
\end{flushright}%\par
|
||||||
|
\@thanks
|
||||||
|
\end{titlepage}%
|
||||||
|
\cleardoublepage%
|
||||||
|
\setcounter{footnote}{0}%
|
||||||
|
\let\thanks\relax\let\maketitle\relax
|
||||||
|
%\gdef\@thanks{}\gdef\@author{}\gdef\@title{}
|
||||||
|
}
|
||||||
|
|
||||||
|
\fancypagestyle{normal}{
|
||||||
|
\fancyhf{}
|
||||||
|
\fancyfoot[LE,RO]{{\thepage}}
|
||||||
|
\fancyfoot[LO]{{\nouppercase{\rightmark}}}
|
||||||
|
\fancyfoot[RE]{{\nouppercase{\leftmark}}}
|
||||||
|
\fancyhead[LE,RO]{{ \@title, \py@release}}
|
||||||
|
\renewcommand{\headrulewidth}{0.4pt}
|
||||||
|
\renewcommand{\footrulewidth}{0.4pt}
|
||||||
|
}
|
||||||
|
|
||||||
|
\fancypagestyle{plain}{
|
||||||
|
\fancyhf{}
|
||||||
|
\fancyfoot[LE,RO]{{\thepage}}
|
||||||
|
\renewcommand{\headrulewidth}{0pt}
|
||||||
|
\renewcommand{\footrulewidth}{0.4pt}
|
||||||
|
}
|
||||||
|
|
||||||
|
\titleformat{\section}{\Large}%
|
||||||
|
{\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor}
|
||||||
|
\titleformat{\subsection}{\large}%
|
||||||
|
{\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor}
|
||||||
|
\titleformat{\subsubsection}{}%
|
||||||
|
{\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor}
|
||||||
|
\titleformat{\paragraph}{\large}%
|
||||||
|
{\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor}
|
||||||
|
|
||||||
|
\ChNameVar{\raggedleft\normalsize}
|
||||||
|
\ChNumVar{\raggedleft \bfseries\Large}
|
||||||
|
\ChTitleVar{\raggedleft \rm\Huge}
|
||||||
|
|
||||||
|
\renewcommand\thepart{\@Roman\c@part}
|
||||||
|
\renewcommand\part{%
|
||||||
|
\pagestyle{plain}
|
||||||
|
\if@noskipsec \leavevmode \fi
|
||||||
|
\cleardoublepage
|
||||||
|
\vspace*{6cm}%
|
||||||
|
\@afterindentfalse
|
||||||
|
\secdef\@part\@spart}
|
||||||
|
|
||||||
|
\def\@part[#1]#2{%
|
||||||
|
\ifnum \c@secnumdepth >\m@ne
|
||||||
|
\refstepcounter{part}%
|
||||||
|
\addcontentsline{toc}{part}{\thepart\hspace{1em}#1}%
|
||||||
|
\else
|
||||||
|
\addcontentsline{toc}{part}{#1}%
|
||||||
|
\fi
|
||||||
|
{\parindent \z@ %\center
|
||||||
|
\interlinepenalty \@M
|
||||||
|
\normalfont
|
||||||
|
\ifnum \c@secnumdepth >\m@ne
|
||||||
|
\rm\Large \partname~\thepart
|
||||||
|
\par\nobreak
|
||||||
|
\fi
|
||||||
|
\MakeUppercase{\rm\Huge #2}%
|
||||||
|
\markboth{}{}\par}%
|
||||||
|
\nobreak
|
||||||
|
\vskip 8ex
|
||||||
|
\@afterheading}
|
||||||
|
\def\@spart#1{%
|
||||||
|
{\parindent \z@ %\center
|
||||||
|
\interlinepenalty \@M
|
||||||
|
\normalfont
|
||||||
|
\huge \bfseries #1\par}%
|
||||||
|
\nobreak
|
||||||
|
\vskip 3ex
|
||||||
|
\@afterheading}
|
||||||
|
|
||||||
|
% use inconsolata font
|
||||||
|
\usepackage{inconsolata}
|
||||||
|
|
||||||
|
% fix single quotes, for inconsolata. (does not work)
|
||||||
|
%%\usepackage{textcomp}
|
||||||
|
%%\begingroup
|
||||||
|
%% \catcode`'=\active
|
||||||
|
%% \g@addto@macro\@noligs{\let'\textsinglequote}
|
||||||
|
%% \endgroup
|
||||||
|
%%\endinput
|
||||||
57
docs/foreword.rst
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
Foreword
|
||||||
|
========
|
||||||
|
|
||||||
|
Read this before you get started with Flask. This hopefully answers some
|
||||||
|
questions about the purpose and goals of the project, and when you
|
||||||
|
should or should not be using it.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Configuration and Conventions
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Flask has many configuration values, with sensible defaults, and a few
|
||||||
|
conventions when getting started. By convention, templates and static files are
|
||||||
|
stored in subdirectories within the application's Python source tree, with the
|
||||||
|
names :file:`templates` and :file:`static` respectively. While this can be changed, you
|
||||||
|
usually don't have to, especially when getting started.
|
||||||
|
|
||||||
|
Growing with Flask
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Once you have Flask up and running, you'll find a variety of extensions
|
||||||
|
available in the community to integrate your project for production. The Flask
|
||||||
|
core team reviews extensions and ensures approved extensions do not break with
|
||||||
|
future releases.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Flask includes many hooks to customize its behavior. Should you need more
|
||||||
|
customization, the Flask class is built for subclassing. If you are interested
|
||||||
|
in that, check out the :ref:`becomingbig` chapter. If you are curious about
|
||||||
|
the Flask design principles, head over to the section about :ref:`design`.
|
||||||
|
|
||||||
|
Continue to :ref:`installation`, the :ref:`quickstart`, or the
|
||||||
|
:ref:`advanced_foreword`.
|
||||||
125
docs/gevent.rst
|
|
@ -1,125 +0,0 @@
|
||||||
Async with Gevent
|
|
||||||
=================
|
|
||||||
|
|
||||||
`Gevent`_ patches Python's standard library to run within special async workers
|
|
||||||
called `greenlets`_. Gevent has existed since long before Python's native
|
|
||||||
asyncio was available, and Flask has always worked with it.
|
|
||||||
|
|
||||||
.. _gevent: https://www.gevent.org
|
|
||||||
.. _greenlets: https://greenlet.readthedocs.io
|
|
||||||
|
|
||||||
Gevent is a reliable way to handle numerous, long lived, concurrent connections,
|
|
||||||
and to achieve similar capabilities to ASGI and asyncio. This works without
|
|
||||||
needing to write ``async def`` or ``await`` anywhere, but relies on gevent and
|
|
||||||
greenlet's low level manipulation of the Python interpreter.
|
|
||||||
|
|
||||||
Deciding whether you should use gevent with Flask, or `Quart`_, or something
|
|
||||||
else, is ultimately up to understanding the specific needs of your project.
|
|
||||||
|
|
||||||
.. _quart: https://quart.palletsprojects.com
|
|
||||||
|
|
||||||
|
|
||||||
Enabling gevent
|
|
||||||
---------------
|
|
||||||
|
|
||||||
You need to apply gevent's patching as early as possible in your code. This
|
|
||||||
enables gevent's underlying event loop and converts many Python internals to run
|
|
||||||
inside it. Add the following at the top of your project's module or top
|
|
||||||
``__init__.py``:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import gevent.monkey
|
|
||||||
gevent.monkey.patch_all()
|
|
||||||
|
|
||||||
When deploying in production, use :doc:`/deploying/gunicorn` or
|
|
||||||
:doc:`/deploying/uwsgi` with a gevent worker, as described on those pages.
|
|
||||||
|
|
||||||
To run concurrent tasks within your own code, such as views, use
|
|
||||||
|gevent.spawn|_:
|
|
||||||
|
|
||||||
.. |gevent.spawn| replace:: ``gevent.spawn()``
|
|
||||||
.. _gevent.spawn: https://www.gevent.org/api/gevent.html#gevent.spawn
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@app.post("/send")
|
|
||||||
def send_email():
|
|
||||||
gevent.spawn(email.send, to="example@example.example", text="example")
|
|
||||||
return "Email is being sent."
|
|
||||||
|
|
||||||
If you need to access :data:`request` or other Flask context globals within the
|
|
||||||
spawned function, decorate the function with :func:`.stream_with_context` or
|
|
||||||
:func:`.copy_current_request_context`. Prefer passing the exact data you need
|
|
||||||
when spawning the function, rather than using the decorators.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
When using gevent, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7
|
|
||||||
is required.
|
|
||||||
|
|
||||||
|
|
||||||
.. _gevent-asyncio:
|
|
||||||
|
|
||||||
Combining with ``async``/``await``
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Gevent's patching does not interact well with Flask's built-in asyncio support.
|
|
||||||
If you want to use Gevent and asyncio in the same app, you'll need to override
|
|
||||||
:meth:`flask.Flask.async_to_sync` to run async functions inside gevent.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import gevent.monkey
|
|
||||||
gevent.monkey.patch_all()
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from flask import Flask, request
|
|
||||||
|
|
||||||
loop = asyncio.EventLoop()
|
|
||||||
gevent.spawn(loop.run_forever)
|
|
||||||
|
|
||||||
class GeventFlask(Flask):
|
|
||||||
def async_to_sync(self, func):
|
|
||||||
def run(*args, **kwargs):
|
|
||||||
coro = func(*args, **kwargs)
|
|
||||||
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
||||||
return future.result()
|
|
||||||
|
|
||||||
return run
|
|
||||||
|
|
||||||
app = GeventFlask(__name__)
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
async def greet():
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
return f"Hello, {request.args.get("name", "World")}!"
|
|
||||||
|
|
||||||
This starts an asyncio event loop in a gevent worker. Async functions are
|
|
||||||
scheduled on that event loop. This may still have limitations, and may need to
|
|
||||||
be modified further when using other asyncio implementations.
|
|
||||||
|
|
||||||
|
|
||||||
libuv
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
`libuv`_ is another event loop implementation that `gevent supports`_. There's
|
|
||||||
also a project called `uvloop`_ that enables libuv in asyncio. If you want to
|
|
||||||
use libuv, use gevent's support, not uvloop. It may be possible to further
|
|
||||||
modify the ``async_to_sync`` code from the previous section to work with uvloop,
|
|
||||||
but that's not currently known.
|
|
||||||
|
|
||||||
.. _libuv: https://libuv.org/
|
|
||||||
.. _gevent supports: https://www.gevent.org/loop_impls.html
|
|
||||||
.. _uvloop: https://uvloop.readthedocs.io/
|
|
||||||
|
|
||||||
To enable gevent's libuv support, add the following at the *very* top of your
|
|
||||||
code, before ``gevent.monkey.patch_all()``:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import gevent
|
|
||||||
gevent.config.loop = "libuv"
|
|
||||||
|
|
||||||
import gevent.monkey
|
|
||||||
gevent.monkey.patch_all()
|
|
||||||
207
docs/htmlfaq.rst
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
HTML/XHTML FAQ
|
||||||
|
==============
|
||||||
|
|
||||||
|
The Flask documentation and example applications are using HTML5. You
|
||||||
|
may notice that in many situations, when end tags are optional they are
|
||||||
|
not used, so that the HTML is cleaner and faster to load. Because there
|
||||||
|
is much confusion about HTML and XHTML among developers, this document tries
|
||||||
|
to answer some of the major questions.
|
||||||
|
|
||||||
|
|
||||||
|
History of XHTML
|
||||||
|
----------------
|
||||||
|
|
||||||
|
For a while, it appeared that HTML was about to be replaced by XHTML.
|
||||||
|
However, barely any websites on the Internet are actual XHTML (which is
|
||||||
|
HTML processed using XML rules). There are a couple of major reasons
|
||||||
|
why this is the case. One of them is Internet Explorer's lack of proper
|
||||||
|
XHTML support. The XHTML spec states that XHTML must be served with the MIME
|
||||||
|
type :mimetype:`application/xhtml+xml`, but Internet Explorer refuses to read files
|
||||||
|
with that MIME type.
|
||||||
|
While it is relatively easy to configure Web servers to serve XHTML properly,
|
||||||
|
few people do. This is likely because properly using XHTML can be quite
|
||||||
|
painful.
|
||||||
|
|
||||||
|
One of the most important causes of pain is XML's draconian (strict and
|
||||||
|
ruthless) error handling. When an XML parsing error is encountered,
|
||||||
|
the browser is supposed to show the user an ugly error message, instead
|
||||||
|
of attempting to recover from the error and display what it can. Most of
|
||||||
|
the (X)HTML generation on the web is based on non-XML template engines
|
||||||
|
(such as Jinja, the one used in Flask) which do not protect you from
|
||||||
|
accidentally creating invalid XHTML. There are XML based template engines,
|
||||||
|
such as Kid and the popular Genshi, but they often come with a larger
|
||||||
|
runtime overhead and are not as straightforward to use because they have
|
||||||
|
to obey XML rules.
|
||||||
|
|
||||||
|
The majority of users, however, assumed they were properly using XHTML.
|
||||||
|
They wrote an XHTML doctype at the top of the document and self-closed all
|
||||||
|
the necessary tags (``<br>`` becomes ``<br/>`` or ``<br></br>`` in XHTML).
|
||||||
|
However, even if the document properly validates as XHTML, what really
|
||||||
|
determines XHTML/HTML processing in browsers is the MIME type, which as
|
||||||
|
said before is often not set properly. So the valid XHTML was being treated
|
||||||
|
as invalid HTML.
|
||||||
|
|
||||||
|
XHTML also changed the way JavaScript is used. To properly work with XHTML,
|
||||||
|
programmers have to use the namespaced DOM interface with the XHTML
|
||||||
|
namespace to query for HTML elements.
|
||||||
|
|
||||||
|
History of HTML5
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Development of the HTML5 specification was started in 2004 under the name
|
||||||
|
"Web Applications 1.0" by the Web Hypertext Application Technology Working
|
||||||
|
Group, or WHATWG (which was formed by the major browser vendors Apple,
|
||||||
|
Mozilla, and Opera) with the goal of writing a new and improved HTML
|
||||||
|
specification, based on existing browser behavior instead of unrealistic
|
||||||
|
and backwards-incompatible specifications.
|
||||||
|
|
||||||
|
For example, in HTML4 ``<title/Hello/`` theoretically parses exactly the
|
||||||
|
same as ``<title>Hello</title>``. However, since people were using
|
||||||
|
XHTML-like tags along the lines of ``<link />``, browser vendors implemented
|
||||||
|
the XHTML syntax over the syntax defined by the specification.
|
||||||
|
|
||||||
|
In 2007, the specification was adopted as the basis of a new HTML
|
||||||
|
specification under the umbrella of the W3C, known as HTML5. Currently,
|
||||||
|
it appears that XHTML is losing traction, as the XHTML 2 working group has
|
||||||
|
been disbanded and HTML5 is being implemented by all major browser vendors.
|
||||||
|
|
||||||
|
HTML versus XHTML
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The following table gives you a quick overview of features available in
|
||||||
|
HTML 4.01, XHTML 1.1 and HTML5. (XHTML 1.0 is not included, as it was
|
||||||
|
superseded by XHTML 1.1 and the barely-used XHTML5.)
|
||||||
|
|
||||||
|
.. tabularcolumns:: |p{9cm}|p{2cm}|p{2cm}|p{2cm}|
|
||||||
|
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| | HTML4.01 | XHTML1.1 | HTML5 |
|
||||||
|
+=========================================+==========+==========+==========+
|
||||||
|
| ``<tag/value/`` == ``<tag>value</tag>`` | |Y| [1]_ | |N| | |N| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| ``<br/>`` supported | |N| | |Y| | |Y| [2]_ |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| ``<script/>`` supported | |N| | |Y| | |N| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| should be served as `text/html` | |Y| | |N| [3]_ | |Y| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| should be served as | |N| | |Y| | |N| |
|
||||||
|
| `application/xhtml+xml` | | | |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| strict error handling | |N| | |Y| | |N| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| inline SVG | |N| | |Y| | |Y| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| inline MathML | |N| | |Y| | |Y| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| ``<video>`` tag | |N| | |N| | |Y| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| ``<audio>`` tag | |N| | |N| | |Y| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
| New semantic tags like ``<article>`` | |N| | |N| | |Y| |
|
||||||
|
+-----------------------------------------+----------+----------+----------+
|
||||||
|
|
||||||
|
.. [1] This is an obscure feature inherited from SGML. It is usually not
|
||||||
|
supported by browsers, for reasons detailed above.
|
||||||
|
.. [2] This is for compatibility with server code that generates XHTML for
|
||||||
|
tags such as ``<br>``. It should not be used in new code.
|
||||||
|
.. [3] XHTML 1.0 is the last XHTML standard that allows to be served
|
||||||
|
as `text/html` for backwards compatibility reasons.
|
||||||
|
|
||||||
|
.. |Y| image:: _static/yes.png
|
||||||
|
:alt: Yes
|
||||||
|
.. |N| image:: _static/no.png
|
||||||
|
:alt: No
|
||||||
|
|
||||||
|
What does "strict" mean?
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
HTML5 has strictly defined parsing rules, but it also specifies exactly
|
||||||
|
how a browser should react to parsing errors - unlike XHTML, which simply
|
||||||
|
states parsing should abort. Some people are confused by apparently
|
||||||
|
invalid syntax that still generates the expected results (for example,
|
||||||
|
missing end tags or unquoted attribute values).
|
||||||
|
|
||||||
|
Some of these work because of the lenient error handling most browsers use
|
||||||
|
when they encounter a markup error, others are actually specified. The
|
||||||
|
following constructs are optional in HTML5 by standard, but have to be
|
||||||
|
supported by browsers:
|
||||||
|
|
||||||
|
- Wrapping the document in an ``<html>`` tag
|
||||||
|
- Wrapping header elements in ``<head>`` or the body elements in
|
||||||
|
``<body>``
|
||||||
|
- Closing the ``<p>``, ``<li>``, ``<dt>``, ``<dd>``, ``<tr>``,
|
||||||
|
``<td>``, ``<th>``, ``<tbody>``, ``<thead>``, or ``<tfoot>`` tags.
|
||||||
|
- Quoting attributes, so long as they contain no whitespace or
|
||||||
|
special characters (like ``<``, ``>``, ``'``, or ``"``).
|
||||||
|
- Requiring boolean attributes to have a value.
|
||||||
|
|
||||||
|
This means the following page in HTML5 is perfectly valid:
|
||||||
|
|
||||||
|
.. sourcecode:: html
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<title>Hello HTML5</title>
|
||||||
|
<div class=header>
|
||||||
|
<h1>Hello HTML5</h1>
|
||||||
|
<p class=tagline>HTML5 is awesome
|
||||||
|
</div>
|
||||||
|
<ul class=nav>
|
||||||
|
<li><a href=/index>Index</a>
|
||||||
|
<li><a href=/downloads>Downloads</a>
|
||||||
|
<li><a href=/about>About</a>
|
||||||
|
</ul>
|
||||||
|
<div class=body>
|
||||||
|
<h2>HTML5 is probably the future</h2>
|
||||||
|
<p>
|
||||||
|
There might be some other things around but in terms of
|
||||||
|
browser vendor support, HTML5 is hard to beat.
|
||||||
|
<dl>
|
||||||
|
<dt>Key 1
|
||||||
|
<dd>Value 1
|
||||||
|
<dt>Key 2
|
||||||
|
<dd>Value 2
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
New technologies in HTML5
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
HTML5 adds many new features that make Web applications easier to write
|
||||||
|
and to use.
|
||||||
|
|
||||||
|
- The ``<audio>`` and ``<video>`` tags provide a way to embed audio and
|
||||||
|
video without complicated add-ons like QuickTime or Flash.
|
||||||
|
- Semantic elements like ``<article>``, ``<header>``, ``<nav>``, and
|
||||||
|
``<time>`` that make content easier to understand.
|
||||||
|
- The ``<canvas>`` tag, which supports a powerful drawing API, reducing
|
||||||
|
the need for server-generated images to present data graphically.
|
||||||
|
- New form control types like ``<input type="date">`` that allow user
|
||||||
|
agents to make entering and validating values easier.
|
||||||
|
- Advanced JavaScript APIs like Web Storage, Web Workers, Web Sockets,
|
||||||
|
geolocation, and offline applications.
|
||||||
|
|
||||||
|
Many other features have been added, as well. A good guide to new features
|
||||||
|
in HTML5 is Mark Pilgrim's soon-to-be-published book, `Dive Into HTML5`_.
|
||||||
|
Not all of them are supported in browsers yet, however, so use caution.
|
||||||
|
|
||||||
|
.. _Dive Into HTML5: http://diveintohtml5.info/
|
||||||
|
|
||||||
|
What should be used?
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Currently, the answer is HTML5. There are very few reasons to use XHTML
|
||||||
|
considering the latest developments in Web browsers. To summarize the
|
||||||
|
reasons given above:
|
||||||
|
|
||||||
|
- Internet Explorer (which, sadly, currently leads in market share)
|
||||||
|
has poor support for XHTML.
|
||||||
|
- Many JavaScript libraries also do not support XHTML, due to the more
|
||||||
|
complicated namespacing API it requires.
|
||||||
|
- HTML5 adds several new features, including semantic tags and the
|
||||||
|
long-awaited ``<audio>`` and ``<video>`` tags.
|
||||||
|
- It has the support of most browser vendors behind it.
|
||||||
|
- It is much easier to write, and more compact.
|
||||||
|
|
||||||
|
For most applications, it is undoubtedly better to use HTML5 than XHTML.
|
||||||
101
docs/index.rst
|
|
@ -1,89 +1,32 @@
|
||||||
.. rst-class:: hide-header
|
:orphan:
|
||||||
|
|
||||||
Welcome to Flask
|
Welcome to Flask
|
||||||
================
|
================
|
||||||
|
|
||||||
.. image:: _static/flask-name.svg
|
.. image:: _static/logo-full.png
|
||||||
:align: center
|
:alt: Flask: web development, one drop at a time
|
||||||
:height: 200px
|
:class: floatingflask
|
||||||
|
:align: right
|
||||||
|
|
||||||
Welcome to Flask's documentation. Flask is a lightweight WSGI web application framework.
|
Welcome to Flask's documentation. This documentation is divided into
|
||||||
It is designed to make getting started quick and easy, with the ability to scale up to
|
different parts. I recommend that you get started with
|
||||||
complex applications.
|
:ref:`installation` and then head over to the :ref:`quickstart`.
|
||||||
|
Besides the quickstart, there is also a more detailed :ref:`tutorial` that
|
||||||
|
shows how to create a complete (albeit small) application with Flask. If
|
||||||
|
you'd rather dive into the internals of Flask, check out
|
||||||
|
the :ref:`api` documentation. Common patterns are described in the
|
||||||
|
:ref:`patterns` section.
|
||||||
|
|
||||||
Get started with :doc:`installation`
|
Flask depends on two external libraries: the `Jinja2`_ template
|
||||||
and then get an overview with the :doc:`quickstart`. There is also a
|
engine and the `Werkzeug`_ WSGI toolkit. These libraries are not documented
|
||||||
more detailed :doc:`tutorial/index` that shows how to create a small but
|
here. If you want to dive into their documentation, check out the
|
||||||
complete application with Flask. Common patterns are described in the
|
following links:
|
||||||
: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
|
- `Jinja2 Documentation <http://jinja.pocoo.org/docs>`_
|
||||||
`Click`_ CLI toolkit. Be sure to check their documentation as well as Flask's when
|
- `Werkzeug Documentation <http://werkzeug.pocoo.org/docs>`_
|
||||||
looking for information.
|
|
||||||
|
|
||||||
.. _Werkzeug: https://werkzeug.palletsprojects.com
|
|
||||||
.. _Jinja: https://jinja.palletsprojects.com
|
|
||||||
.. _Click: https://click.palletsprojects.com
|
|
||||||
|
|
||||||
|
|
||||||
User's Guide
|
.. _Jinja2: http://jinja.pocoo.org/
|
||||||
------------
|
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||||
|
|
||||||
Flask provides configuration and conventions, with sensible defaults, to get started.
|
.. include:: contents.rst.inc
|
||||||
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
|
|
||||||
gevent
|
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -1,143 +1,165 @@
|
||||||
|
.. _installation:
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
|
Flask depends on some external libraries, like `Werkzeug
|
||||||
|
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/>`_.
|
||||||
|
Werkzeug is a toolkit for WSGI, the standard Python interface between web
|
||||||
|
applications and a variety of servers for both development and deployment.
|
||||||
|
Jinja2 renders templates.
|
||||||
|
|
||||||
Python Version
|
So how do you get all that on your computer quickly? There are many ways you
|
||||||
--------------
|
could do that, but the most kick-ass method is virtualenv, so let's have a look
|
||||||
|
at that first.
|
||||||
|
|
||||||
We recommend using the latest version of Python. Flask supports Python 3.10 and newer.
|
You will need Python 2.6 or newer to get started, so be sure to have an
|
||||||
|
up-to-date Python 2.x installation. For using Flask with Python 3 have a
|
||||||
|
look at :ref:`python3-support`.
|
||||||
|
|
||||||
|
.. _virtualenv:
|
||||||
|
|
||||||
Dependencies
|
virtualenv
|
||||||
------------
|
----------
|
||||||
|
|
||||||
These distributions will be installed automatically when installing Flask.
|
Virtualenv is probably what you want to use during development, and if you have
|
||||||
|
shell access to your production machines, you'll probably want to use it there,
|
||||||
|
too.
|
||||||
|
|
||||||
* `Werkzeug`_ implements WSGI, the standard Python interface between
|
What problem does virtualenv solve? If you like Python as much as I do,
|
||||||
applications and servers.
|
chances are you want to use it for other projects besides Flask-based web
|
||||||
* `Jinja`_ is a template language that renders the pages your application
|
applications. But the more projects you have, the more likely it is that you
|
||||||
serves.
|
will be working with different versions of Python itself, or at least different
|
||||||
* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering
|
versions of Python libraries. Let's face it: quite often libraries break
|
||||||
templates to avoid injection attacks.
|
backwards compatibility, and it's unlikely that any serious application will
|
||||||
* `ItsDangerous`_ securely signs data to ensure its integrity. This is used
|
have zero dependencies. So what do you do if two or more of your projects have
|
||||||
to protect Flask's session cookie.
|
conflicting dependencies?
|
||||||
* `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/
|
Virtualenv to the rescue! Virtualenv enables multiple side-by-side
|
||||||
.. _Jinja: https://palletsprojects.com/p/jinja/
|
installations of Python, one for each project. It doesn't actually install
|
||||||
.. _MarkupSafe: https://palletsprojects.com/p/markupsafe/
|
separate copies of Python, but it does provide a clever way to keep different
|
||||||
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
|
project environments isolated. Let's see how virtualenv works.
|
||||||
.. _Click: https://palletsprojects.com/p/click/
|
|
||||||
.. _Blinker: https://blinker.readthedocs.io/
|
|
||||||
|
|
||||||
|
If you are on Mac OS X or Linux, chances are that the following
|
||||||
|
command will work for you::
|
||||||
|
|
||||||
Optional dependencies
|
$ sudo pip install virtualenv
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
These distributions will not be installed automatically. Flask will detect and
|
It will probably install virtualenv on your system. Maybe it's even
|
||||||
use them if you install them.
|
in your package manager. If you use Ubuntu, try::
|
||||||
|
|
||||||
* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask``
|
$ sudo apt-get install python-virtualenv
|
||||||
commands.
|
|
||||||
* `Watchdog`_ provides a faster, more efficient reloader for the development
|
|
||||||
server.
|
|
||||||
|
|
||||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
If you are on Windows and don't have the ``easy_install`` command, you must
|
||||||
.. _watchdog: https://pythonhosted.org/watchdog/
|
install it first. Check the :ref:`windows-easy-install` section for more
|
||||||
|
information about how to do that. Once you have it installed, run the same
|
||||||
|
commands as above, but without the ``sudo`` prefix.
|
||||||
|
|
||||||
|
Once you have virtualenv installed, just fire up a shell and create
|
||||||
|
your own environment. I usually create a project folder and a :file:`venv`
|
||||||
|
folder within::
|
||||||
|
|
||||||
greenlet
|
$ mkdir myproject
|
||||||
~~~~~~~~
|
$ cd myproject
|
||||||
|
$ virtualenv venv
|
||||||
|
New python executable in venv/bin/python
|
||||||
|
Installing setuptools, pip............done.
|
||||||
|
|
||||||
You may choose to use :doc:`/gevent` with your application. In this case,
|
Now, whenever you want to work on a project, you only have to activate the
|
||||||
greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is required.
|
corresponding environment. On OS X and Linux, do the following::
|
||||||
|
|
||||||
These are not minimum supported versions, they only indicate the first
|
$ . venv/bin/activate
|
||||||
versions that added necessary features. You should use the latest
|
|
||||||
versions of each.
|
|
||||||
|
|
||||||
|
If you are a Windows user, the following command is for you::
|
||||||
|
|
||||||
Virtual environments
|
$ venv\Scripts\activate
|
||||||
--------------------
|
|
||||||
|
|
||||||
Use a virtual environment to manage the dependencies for your project, both in
|
Either way, you should now be using your virtualenv (notice how the prompt of
|
||||||
development and in production.
|
your shell has changed to show the active environment).
|
||||||
|
|
||||||
What problem does a virtual environment solve? The more Python projects you
|
And if you want to go back to the real world, use the following command::
|
||||||
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
|
$ deactivate
|
||||||
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
|
After doing this, the prompt of your shell should be as familiar as before.
|
||||||
environments.
|
|
||||||
|
|
||||||
|
Now, let's move on. Enter the following command to get Flask activated in your
|
||||||
.. _install-create-env:
|
virtualenv::
|
||||||
|
|
||||||
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
|
$ pip install Flask
|
||||||
|
|
||||||
Flask is now installed. Check out the :doc:`/quickstart` or go to the
|
A few seconds later and you are good to go.
|
||||||
:doc:`Documentation Overview </index>`.
|
|
||||||
|
|
||||||
|
System-Wide Installation
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
This is possible as well, though I do not recommend it. Just run
|
||||||
|
``pip`` with root privileges::
|
||||||
|
|
||||||
|
$ sudo pip install Flask
|
||||||
|
|
||||||
|
(On Windows systems, run it in a command-prompt window with administrator
|
||||||
|
privileges, and leave out ``sudo``.)
|
||||||
|
|
||||||
|
|
||||||
|
Living on the Edge
|
||||||
|
------------------
|
||||||
|
|
||||||
|
If you want to work with the latest version of Flask, there are two ways: you
|
||||||
|
can either let ``pip`` pull in the development version, or you can tell
|
||||||
|
it to operate on a git checkout. Either way, virtualenv is recommended.
|
||||||
|
|
||||||
|
Get the git checkout in a new virtualenv and run in development mode::
|
||||||
|
|
||||||
|
$ git clone http://github.com/pallets/flask.git
|
||||||
|
Initialized empty Git repository in ~/dev/flask/.git/
|
||||||
|
$ cd flask
|
||||||
|
$ virtualenv venv
|
||||||
|
New python executable in venv/bin/python
|
||||||
|
Installing setuptools, pip............done.
|
||||||
|
$ . venv/bin/activate
|
||||||
|
$ python setup.py develop
|
||||||
|
...
|
||||||
|
Finished processing dependencies for Flask
|
||||||
|
|
||||||
|
This will pull in the dependencies and activate the git head as the current
|
||||||
|
version inside the virtualenv. Then all you have to do is run ``git pull
|
||||||
|
origin`` to update to the latest version.
|
||||||
|
|
||||||
|
.. _windows-easy-install:
|
||||||
|
|
||||||
|
`pip` and `setuptools` on Windows
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools``
|
||||||
|
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial
|
||||||
|
package you will need is pip - this will let you install
|
||||||
|
anything else (like virtualenv). Fortunately there is a "bootstrap script"
|
||||||
|
you can run to install.
|
||||||
|
|
||||||
|
If you don't currently have ``pip``, then `get-pip.py` will install it for you.
|
||||||
|
|
||||||
|
`get-pip.py`_
|
||||||
|
|
||||||
|
It should be double-clickable once you download it. If you already have ``pip``,
|
||||||
|
you can upgrade them by running::
|
||||||
|
|
||||||
|
> pip install --upgrade pip setuptools
|
||||||
|
|
||||||
|
Most often, once you pull up a command prompt you want to be able to type ``pip``
|
||||||
|
and ``python`` which will run those things, but this might not automatically happen
|
||||||
|
on Windows, because it doesn't know where those executables are (give either a try!).
|
||||||
|
|
||||||
|
To fix this, you should be able to navigate to your Python install directory
|
||||||
|
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
|
||||||
|
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
|
||||||
|
check that you can now just type ``python`` to bring up the interpreter.
|
||||||
|
|
||||||
|
Finally, to install `virtualenv`_, you can simply run::
|
||||||
|
|
||||||
|
> pip install virtualenv
|
||||||
|
|
||||||
|
Then you can be off on your way following the installation instructions above.
|
||||||
|
|
||||||
|
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
||||||
|
|
|
||||||
6
docs/latexindex.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
:orphan:
|
||||||
|
|
||||||
|
Flask Documentation
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. include:: contents.rst.inc
|
||||||
|
|
@ -1,5 +1,48 @@
|
||||||
BSD-3-Clause License
|
License
|
||||||
====================
|
=======
|
||||||
|
|
||||||
.. literalinclude:: ../LICENSE.txt
|
Flask is licensed under a three clause BSD License. It basically means:
|
||||||
:language: text
|
do whatever you want with it as long as the copyright in Flask sticks
|
||||||
|
around, the conditions are not modified and the disclaimer is present.
|
||||||
|
Furthermore you must not use the names of the authors to promote derivatives
|
||||||
|
of the software without written consent.
|
||||||
|
|
||||||
|
The full license text can be found below (:ref:`flask-license`). For the
|
||||||
|
documentation and artwork different licenses apply.
|
||||||
|
|
||||||
|
.. _authors:
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. include:: ../AUTHORS
|
||||||
|
|
||||||
|
General License Definitions
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The following section contains the full license texts for Flask and the
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
- "AUTHORS" hereby refers to all the authors listed in the
|
||||||
|
:ref:`authors` section.
|
||||||
|
|
||||||
|
- The ":ref:`flask-license`" applies to all the source code shipped as
|
||||||
|
part of Flask (Flask itself as well as the examples and the unittests)
|
||||||
|
as well as documentation.
|
||||||
|
|
||||||
|
- The ":ref:`artwork-license`" applies to the project's Horn-Logo.
|
||||||
|
|
||||||
|
.. _flask-license:
|
||||||
|
|
||||||
|
Flask License
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. include:: ../LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
.. _artwork-license:
|
||||||
|
|
||||||
|
Flask Artwork License
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. include:: ../artwork/LICENSE
|
||||||
|
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
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.
|
|
||||||
183
docs/logging.rst
|
|
@ -1,183 +0,0 @@
|
||||||
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.
|
|
||||||
BIN
docs/logo.pdf
Normal file
|
|
@ -9,6 +9,7 @@ if "%SPHINXBUILD%" == "" (
|
||||||
)
|
)
|
||||||
set SOURCEDIR=.
|
set SOURCEDIR=.
|
||||||
set BUILDDIR=_build
|
set BUILDDIR=_build
|
||||||
|
set SPHINXPROJ=Flask
|
||||||
|
|
||||||
if "%1" == "" goto help
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
|
@ -25,11 +26,11 @@ if errorlevel 9009 (
|
||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
goto end
|
goto end
|
||||||
|
|
||||||
:help
|
:help
|
||||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
|
|
||||||
:end
|
:end
|
||||||
popd
|
popd
|
||||||
|
|
|
||||||
63
docs/patterns/apierrors.rst
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
Implementing API Exceptions
|
||||||
|
===========================
|
||||||
|
|
||||||
|
It's very common to implement RESTful APIs on top of Flask. One of the
|
||||||
|
first things that developers run into is the realization that the builtin
|
||||||
|
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.
|
||||||
|
|
||||||
|
The better solution than using ``abort`` to signal errors for invalid API
|
||||||
|
usage is to implement your own exception type and install an error handler
|
||||||
|
for it that produces the errors in the format the user is expecting.
|
||||||
|
|
||||||
|
Simple Exception Class
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The basic idea is to introduce a new exception 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::
|
||||||
|
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
class InvalidUsage(Exception):
|
||||||
|
status_code = 400
|
||||||
|
|
||||||
|
def __init__(self, message, status_code=None, payload=None):
|
||||||
|
Exception.__init__(self)
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Registering an Error Handler
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
At that point views can raise that error, but it would immediately result
|
||||||
|
in an internal server error. The reason for this is that there is no
|
||||||
|
handler registered for this error class. That however is easy to add::
|
||||||
|
|
||||||
|
@app.errorhandler(InvalidUsage)
|
||||||
|
def handle_invalid_usage(error):
|
||||||
|
response = jsonify(error.to_dict())
|
||||||
|
response.status_code = error.status_code
|
||||||
|
return response
|
||||||
|
|
||||||
|
Usage in Views
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Here is how a view can use that functionality::
|
||||||
|
|
||||||
|
@app.route('/foo')
|
||||||
|
def get_foo():
|
||||||
|
raise InvalidUsage('This view is gone', status_code=410)
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _app-dispatch:
|
||||||
|
|
||||||
Application Dispatching
|
Application Dispatching
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
|
@ -8,30 +10,44 @@ 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
|
you want. The usefulness of this depends on how the applications work
|
||||||
internally.
|
internally.
|
||||||
|
|
||||||
The fundamental difference from :doc:`packages` is that in this case you
|
The fundamental difference from the :ref:`module approach
|
||||||
are running the same or different Flask applications that are entirely
|
<larger-applications>` is that in this case you are running the same or
|
||||||
isolated from each other. They run different configurations and are
|
different Flask applications that are entirely isolated from each other.
|
||||||
dispatched on the WSGI level.
|
They run different configurations and are dispatched on the WSGI level.
|
||||||
|
|
||||||
|
|
||||||
Working with this Document
|
Working with this Document
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Each of the techniques and examples below results in an ``application``
|
Each of the techniques and examples below results in an ``application`` object
|
||||||
object that can be run with any WSGI server. For development, use the
|
that can be run with any WSGI server. For production, see :ref:`deployment`.
|
||||||
``flask run`` command to start a development server. For production, see
|
For development, Werkzeug provides a builtin server for development available
|
||||||
:doc:`/deploying/index`.
|
at :func:`werkzeug.serving.run_simple`::
|
||||||
|
|
||||||
.. code-block:: python
|
from werkzeug.serving import run_simple
|
||||||
|
run_simple('localhost', 5000, application, use_reloader=True)
|
||||||
|
|
||||||
|
Note that :func:`run_simple <werkzeug.serving.run_simple>` is not intended for
|
||||||
|
use in production. Use a :ref:`full-blown WSGI server <deployment>`.
|
||||||
|
|
||||||
|
In order to use the interactive debugger, debugging must be enabled both on
|
||||||
|
the application and the simple server. Here is the "hello world" example with
|
||||||
|
debugging and :func:`run_simple <werkzeug.serving.run_simple>`::
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from werkzeug.serving import run_simple
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.debug = True
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def hello_world():
|
def hello_world():
|
||||||
return 'Hello World!'
|
return 'Hello World!'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_simple('localhost', 5000, app,
|
||||||
|
use_reloader=True, use_debugger=True, use_evalex=True)
|
||||||
|
|
||||||
|
|
||||||
Combining Applications
|
Combining Applications
|
||||||
----------------------
|
----------------------
|
||||||
|
|
@ -44,16 +60,14 @@ are combined by the dispatcher middleware into a larger one that is
|
||||||
dispatched based on prefix.
|
dispatched based on prefix.
|
||||||
|
|
||||||
For example you could have your main application run on ``/`` and your
|
For example you could have your main application run on ``/`` and your
|
||||||
backend interface on ``/backend``.
|
backend interface on ``/backend``::
|
||||||
|
|
||||||
.. code-block:: python
|
from werkzeug.wsgi import DispatcherMiddleware
|
||||||
|
|
||||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
|
||||||
from frontend_app import application as frontend
|
from frontend_app import application as frontend
|
||||||
from backend_app import application as backend
|
from backend_app import application as backend
|
||||||
|
|
||||||
application = DispatcherMiddleware(frontend, {
|
application = DispatcherMiddleware(frontend, {
|
||||||
'/backend': backend
|
'/backend': backend
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -65,7 +79,7 @@ with different configurations. Assuming the application is created inside
|
||||||
a function and you can call that function to instantiate it, that is
|
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
|
really easy to implement. In order to develop your application to support
|
||||||
creating new instances in functions have a look at the
|
creating new instances in functions have a look at the
|
||||||
:doc:`appfactories` pattern.
|
:ref:`app-factories` pattern.
|
||||||
|
|
||||||
A very common example would be creating applications per subdomain. For
|
A very common example would be creating applications per subdomain. For
|
||||||
instance you configure your webserver to dispatch all requests for all
|
instance you configure your webserver to dispatch all requests for all
|
||||||
|
|
@ -77,13 +91,11 @@ the dynamic application creation.
|
||||||
The perfect level for abstraction in that regard is the WSGI layer. You
|
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
|
write your own WSGI application that looks at the request that comes and
|
||||||
delegates it to your Flask application. If that application does not
|
delegates it to your Flask application. If that application does not
|
||||||
exist yet, it is dynamically created and remembered.
|
exist yet, it is dynamically created and remembered::
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
class SubdomainDispatcher:
|
class SubdomainDispatcher(object):
|
||||||
|
|
||||||
def __init__(self, domain, create_app):
|
def __init__(self, domain, create_app):
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
|
|
@ -107,9 +119,7 @@ exist yet, it is dynamically created and remembered.
|
||||||
return app(environ, start_response)
|
return app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
This dispatcher can then be used like this:
|
This dispatcher can then be used like this::
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from myapplication import create_app, get_user_for_subdomain
|
from myapplication import create_app, get_user_for_subdomain
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
@ -135,14 +145,12 @@ Dispatch by Path
|
||||||
|
|
||||||
Dispatching by a path on the URL is very similar. Instead of looking at
|
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
|
the ``Host`` header to figure out the subdomain one simply looks at the
|
||||||
request path up to the first slash.
|
request path up to the first slash::
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from wsgiref.util import shift_path_info
|
from werkzeug.wsgi import pop_path_info, peek_path_info
|
||||||
|
|
||||||
class PathDispatcher:
|
class PathDispatcher(object):
|
||||||
|
|
||||||
def __init__(self, default_app, create_app):
|
def __init__(self, default_app, create_app):
|
||||||
self.default_app = default_app
|
self.default_app = default_app
|
||||||
|
|
@ -160,24 +168,15 @@ request path up to the first slash.
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
app = self.get_application(_peek_path_info(environ))
|
app = self.get_application(peek_path_info(environ))
|
||||||
if app is not None:
|
if app is not None:
|
||||||
shift_path_info(environ)
|
pop_path_info(environ)
|
||||||
else:
|
else:
|
||||||
app = self.default_app
|
app = self.default_app
|
||||||
return app(environ, start_response)
|
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
|
The big difference between this and the subdomain one is that this one
|
||||||
falls back to another application if the creator function returns ``None``.
|
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
|
from myapplication import create_app, default_app, get_user_for_prefix
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
.. _app-factories:
|
||||||
|
|
||||||
Application Factories
|
Application Factories
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
If you are already using packages and blueprints for your application
|
If you are already using packages and blueprints for your application
|
||||||
(:doc:`/blueprints`) there are a couple of really nice ways to further improve
|
(:ref:`blueprints`) there are a couple of really nice ways to further improve
|
||||||
the experience. A common pattern is creating the application object when
|
the experience. A common pattern is creating the application object when
|
||||||
the blueprint is imported. But if you move the creation of this object
|
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.
|
into a function, you can then create multiple instances of this app later.
|
||||||
|
|
@ -58,7 +60,7 @@ Factories & Extensions
|
||||||
It's preferable to create your extensions and app factories so that the
|
It's preferable to create your extensions and app factories so that the
|
||||||
extension object does not initially get bound to the application.
|
extension object does not initially get bound to the application.
|
||||||
|
|
||||||
Using `Flask-SQLAlchemy <https://flask-sqlalchemy.palletsprojects.com/>`_,
|
Using `Flask-SQLAlchemy <http://pythonhosted.org/Flask-SQLAlchemy/>`_,
|
||||||
as an example, you should not do something along those lines::
|
as an example, you should not do something along those lines::
|
||||||
|
|
||||||
def create_app(config_filename):
|
def create_app(config_filename):
|
||||||
|
|
@ -87,32 +89,28 @@ For more information about the design of extensions refer to :doc:`/extensiondev
|
||||||
Using Applications
|
Using Applications
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
To run such an application, you can use the :command:`flask` command:
|
So to use such an application you then have to create the application
|
||||||
|
first in a separate file otherwise the :command:`flask` command won't be able
|
||||||
|
to find it. Here an example :file:`exampleapp.py` file that creates such
|
||||||
|
an application::
|
||||||
|
|
||||||
.. code-block:: text
|
from yourapplication import create_app
|
||||||
|
app = create_app('/path/to/config.cfg')
|
||||||
|
|
||||||
$ flask --app hello run
|
It can then be used with the :command:`flask` command::
|
||||||
|
|
||||||
Flask will automatically detect the factory if it is named
|
export FLASK_APP=exampleapp
|
||||||
``create_app`` or ``make_app`` in ``hello``. You can also pass arguments
|
flask run
|
||||||
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
|
Factory Improvements
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
The factory function above is not very clever, but you can improve it.
|
The factory function from above is not very clever so far, you can improve
|
||||||
The following changes are straightforward to implement:
|
it. The following changes are straightforward and possible:
|
||||||
|
|
||||||
1. Make it possible to pass in configuration values for unit tests so that
|
1. make it possible to pass in configuration values for unittests so that
|
||||||
you don't have to create config files on the filesystem.
|
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
|
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
|
that you have a place to modify attributes of the application (like
|
||||||
hooking in before/after request handlers etc.)
|
hooking in before / after request handlers etc.)
|
||||||
3. Add in WSGI middlewares when the application is being created if necessary.
|
3. Add in WSGI middlewares when the application is creating if necessary.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _caching-pattern:
|
||||||
|
|
||||||
Caching
|
Caching
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|
@ -8,9 +10,60 @@ 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
|
you actually put the result of that calculation into a cache for some
|
||||||
time.
|
time.
|
||||||
|
|
||||||
Flask itself does not provide caching for you, but `Flask-Caching`_, an
|
Flask itself does not provide caching for you, but Werkzeug, one of the
|
||||||
extension for Flask does. Flask-Caching supports various backends, and it is
|
libraries it is based on, has some very basic cache support. It supports
|
||||||
even possible to develop your own caching backend.
|
multiple cache backends, normally you want to use a memcached server.
|
||||||
|
|
||||||
|
Setting up a Cache
|
||||||
|
------------------
|
||||||
|
|
||||||
.. _Flask-Caching: https://flask-caching.readthedocs.io/en/latest/
|
You create a cache object once and keep it around, similar to how
|
||||||
|
:class:`~flask.Flask` objects are created. If you are using the
|
||||||
|
development server you can create a
|
||||||
|
:class:`~werkzeug.contrib.cache.SimpleCache` object, that one is a simple
|
||||||
|
cache that keeps the item stored in the memory of the Python interpreter::
|
||||||
|
|
||||||
|
from werkzeug.contrib.cache import SimpleCache
|
||||||
|
cache = SimpleCache()
|
||||||
|
|
||||||
|
If you want to use memcached, make sure to have one of the memcache modules
|
||||||
|
supported (you get them from `PyPI <https://pypi.python.org/pypi>`_) and a
|
||||||
|
memcached server running somewhere. This is how you connect to such an
|
||||||
|
memcached server then::
|
||||||
|
|
||||||
|
from werkzeug.contrib.cache import MemcachedCache
|
||||||
|
cache = MemcachedCache(['127.0.0.1:11211'])
|
||||||
|
|
||||||
|
If you are using App Engine, you can connect to the App Engine memcache
|
||||||
|
server easily::
|
||||||
|
|
||||||
|
from werkzeug.contrib.cache import GAEMemcachedCache
|
||||||
|
cache = GAEMemcachedCache()
|
||||||
|
|
||||||
|
Using a Cache
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Now how can one use such a cache? There are two very important
|
||||||
|
operations: :meth:`~werkzeug.contrib.cache.BaseCache.get` and
|
||||||
|
:meth:`~werkzeug.contrib.cache.BaseCache.set`. This is how to use them:
|
||||||
|
|
||||||
|
To get an item from the cache call
|
||||||
|
:meth:`~werkzeug.contrib.cache.BaseCache.get` with a string as key name.
|
||||||
|
If something is in the cache, it is returned. Otherwise that function
|
||||||
|
will return ``None``::
|
||||||
|
|
||||||
|
rv = cache.get('my-item')
|
||||||
|
|
||||||
|
To add items to the cache, use the :meth:`~werkzeug.contrib.cache.BaseCache.set`
|
||||||
|
method instead. The first argument is the key and the second the value
|
||||||
|
that should be set. Also a timeout can be provided after which the cache
|
||||||
|
will automatically remove item.
|
||||||
|
|
||||||
|
Here a full example how this looks like normally::
|
||||||
|
|
||||||
|
def get_my_item():
|
||||||
|
rv = cache.get('my-item')
|
||||||
|
if rv is None:
|
||||||
|
rv = calculate_value()
|
||||||
|
cache.set('my-item', rv, timeout=5 * 60)
|
||||||
|
return rv
|
||||||
|
|
|
||||||
|
|
@ -1,242 +1,93 @@
|
||||||
Background Tasks with Celery
|
Celery Based Background Tasks
|
||||||
============================
|
=============================
|
||||||
|
|
||||||
If your application has a long running task, such as processing some uploaded data or
|
Celery is a task queue for Python with batteries included. It used to
|
||||||
sending email, you don't want to wait for it to finish during a request. Instead, use a
|
have a Flask integration but it became unnecessary after some
|
||||||
task queue to send the necessary data to another process that will run the task in the
|
restructuring of the internals of Celery with Version 3. This guide fills
|
||||||
background while the request returns immediately.
|
in the blanks in how to properly use Celery with Flask but assumes that
|
||||||
|
you generally already read the `First Steps with Celery
|
||||||
|
<http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html>`_
|
||||||
|
guide in the official Celery documentation.
|
||||||
|
|
||||||
`Celery`_ is a powerful task queue that can be used for simple background tasks as well
|
Installing Celery
|
||||||
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
|
Celery is on the Python Package Index (PyPI), so it can be installed with
|
||||||
.. _First Steps with Celery: https://celery.readthedocs.io/en/latest/getting-started/first-steps-with-celery.html
|
standard Python tools like :command:`pip` or :command:`easy_install`::
|
||||||
|
|
||||||
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
|
$ pip install celery
|
||||||
|
|
||||||
|
Configuring Celery
|
||||||
|
------------------
|
||||||
|
|
||||||
Integrate Celery with Flask
|
The first thing you need is a Celery instance, this is called the celery
|
||||||
---------------------------
|
application. It serves the same purpose as the :class:`~flask.Flask`
|
||||||
|
object in Flask, just for Celery. Since this instance is used as the
|
||||||
|
entry-point for everything you want to do in Celery, like creating tasks
|
||||||
|
and managing workers, it must be possible for other modules to import it.
|
||||||
|
|
||||||
You can use Celery without any integration with Flask, but it's convenient to configure
|
For instance you can place this in a ``tasks`` module. While you can use
|
||||||
it through Flask's config, and to let tasks access the Flask application.
|
Celery without any reconfiguration with Flask, it becomes a bit nicer by
|
||||||
|
subclassing tasks and adding support for Flask's application contexts and
|
||||||
|
hooking it up with the Flask configuration.
|
||||||
|
|
||||||
Celery uses similar ideas to Flask, with a ``Celery`` app object that has configuration
|
This is all that is necessary to properly integrate Celery with Flask::
|
||||||
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
|
||||||
|
|
||||||
from celery import Celery, Task
|
def make_celery(app):
|
||||||
|
celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'],
|
||||||
def celery_init_app(app: Flask) -> Celery:
|
broker=app.config['CELERY_BROKER_URL'])
|
||||||
class FlaskTask(Task):
|
celery.conf.update(app.config)
|
||||||
def __call__(self, *args: object, **kwargs: object) -> object:
|
TaskBase = celery.Task
|
||||||
|
class ContextTask(TaskBase):
|
||||||
|
abstract = True
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
return self.run(*args, **kwargs)
|
return TaskBase.__call__(self, *args, **kwargs)
|
||||||
|
celery.Task = ContextTask
|
||||||
|
return celery
|
||||||
|
|
||||||
celery_app = Celery(app.name, task_cls=FlaskTask)
|
The function creates a new Celery object, configures it with the broker
|
||||||
celery_app.config_from_object(app.config["CELERY"])
|
from the application config, updates the rest of the Celery config from
|
||||||
celery_app.set_default()
|
the Flask config and then creates a subclass of the task that wraps the
|
||||||
app.extensions["celery"] = celery_app
|
task execution in an application context.
|
||||||
return celery_app
|
|
||||||
|
|
||||||
This creates and returns a ``Celery`` app object. Celery `configuration`_ is taken from
|
Minimal Example
|
||||||
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
|
With what we have above this is the minimal example of using Celery with
|
||||||
|
Flask::
|
||||||
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
|
from flask import Flask
|
||||||
|
|
||||||
app = Flask(__name__)
|
flask_app = Flask(__name__)
|
||||||
app.config.from_mapping(
|
flask_app.config.update(
|
||||||
CELERY=dict(
|
CELERY_BROKER_URL='redis://localhost:6379',
|
||||||
broker_url="redis://localhost",
|
CELERY_RESULT_BACKEND='redis://localhost:6379'
|
||||||
result_backend="redis://localhost",
|
|
||||||
task_ignore_result=True,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
celery_app = celery_init_app(app)
|
celery = make_celery(flask_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
|
@celery.task()
|
||||||
-------------------
|
def add_together(a, b):
|
||||||
|
|
||||||
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
|
return a + b
|
||||||
|
|
||||||
Earlier, we configured Celery to ignore task results by default. Since we want to know
|
This task can now be called in the background:
|
||||||
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.
|
|
||||||
|
|
||||||
|
>>> result = add_together.delay(23, 42)
|
||||||
|
>>> result.wait()
|
||||||
|
65
|
||||||
|
|
||||||
Calling Tasks
|
Running the Celery Worker
|
||||||
-------------
|
-------------------------
|
||||||
|
|
||||||
The decorated function becomes a task object with methods to call it in the background.
|
Now if you jumped in and already executed the above code you will be
|
||||||
The simplest way is to use the ``delay(*args, **kwargs)`` method. See Celery's docs for
|
disappointed to learn that your ``.wait()`` will never actually return.
|
||||||
more methods.
|
That's because you also need to run celery. You can do that by running
|
||||||
|
celery as a worker::
|
||||||
|
|
||||||
A Celery worker must be running to run the task. Starting a worker is shown in the
|
$ celery -A your_application.celery worker
|
||||||
previous sections.
|
|
||||||
|
|
||||||
.. code-block:: python
|
The ``your_application`` string has to point to your application's package
|
||||||
|
or module that creates the `celery` object.
|
||||||
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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,73 @@
|
||||||
|
.. _deferred-callbacks:
|
||||||
|
|
||||||
Deferred Request Callbacks
|
Deferred Request Callbacks
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
One of the design principles of Flask is that response objects are created and
|
One of the design principles of Flask is that response objects are created
|
||||||
passed down a chain of potential callbacks that can modify them or replace
|
and passed down a chain of potential callbacks that can modify them or
|
||||||
them. When the request handling starts, there is no response object yet. It is
|
replace them. When the request handling starts, there is no response
|
||||||
created as necessary either by a view function or by some other component in
|
object yet. It is created as necessary either by a view function or by
|
||||||
the system.
|
some other component in the system.
|
||||||
|
|
||||||
What happens if you want to modify the response at a point where the response
|
But what happens if you want to modify the response at a point where the
|
||||||
does not exist yet? A common example for that would be a
|
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
|
before-request function that wants to set a cookie on the response object.
|
||||||
response object.
|
|
||||||
|
|
||||||
One way is to avoid the situation. Very often that is possible. For instance
|
One way is to avoid the situation. Very often that is possible. For
|
||||||
you can try to move that logic into a :meth:`~flask.Flask.after_request`
|
instance you can try to move that logic into an after-request callback
|
||||||
callback instead. However, sometimes moving code there makes it
|
instead. Sometimes however moving that code there is just not a very
|
||||||
more complicated or awkward to reason about.
|
pleasant experience or makes code look very awkward.
|
||||||
|
|
||||||
As an alternative, you can use :func:`~flask.after_this_request` to register
|
As an alternative possibility you can attach a bunch of callback functions
|
||||||
callbacks that will execute after only the current request. This way you can
|
to the :data:`~flask.g` object and call them at the end of the request.
|
||||||
defer code execution from anywhere in the application, based on the current
|
This way you can defer code execution from anywhere in the application.
|
||||||
request.
|
|
||||||
|
|
||||||
|
The Decorator
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The following decorator is the key. It registers a function on a list on
|
||||||
|
the :data:`~flask.g` object::
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
|
||||||
|
def after_this_request(f):
|
||||||
|
if not hasattr(g, 'after_request_callbacks'):
|
||||||
|
g.after_request_callbacks = []
|
||||||
|
g.after_request_callbacks.append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
Calling the Deferred
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Now you can use the `after_this_request` decorator to mark a function to
|
||||||
|
be called at the end of the request. But we still need to call them. For
|
||||||
|
this the following function needs to be registered as
|
||||||
|
:meth:`~flask.Flask.after_request` callback::
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def call_after_request_callbacks(response):
|
||||||
|
for callback in getattr(g, 'after_request_callbacks', ()):
|
||||||
|
callback(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
A Practical Example
|
||||||
|
-------------------
|
||||||
|
|
||||||
At any time during a request, we can register a function to be called at the
|
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
|
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::
|
user in a cookie in the before-request function::
|
||||||
|
|
||||||
from flask import request, after_this_request
|
from flask import request
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def detect_user_language():
|
def detect_user_language():
|
||||||
language = request.cookies.get('user_lang')
|
language = request.cookies.get('user_lang')
|
||||||
|
|
||||||
if language is None:
|
if language is None:
|
||||||
language = guess_language_from_request()
|
language = guess_language_from_request()
|
||||||
|
|
||||||
# when the response exists, set a cookie with the language
|
|
||||||
@after_this_request
|
@after_this_request
|
||||||
def remember_language(response):
|
def remember_language(response):
|
||||||
response.set_cookie('user_lang', language)
|
response.set_cookie('user_lang', language)
|
||||||
return response
|
|
||||||
|
|
||||||
g.language = language
|
g.language = language
|
||||||
|
|
|
||||||
177
docs/patterns/distribute.rst
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
.. _distribute-deployment:
|
||||||
|
|
||||||
|
Deploying with Setuptools
|
||||||
|
=========================
|
||||||
|
|
||||||
|
`Setuptools`_, is an extension library that is commonly used to
|
||||||
|
distribute Python libraries and extensions. It extends distutils, a basic
|
||||||
|
module installation system shipped with Python to also support various more
|
||||||
|
complex constructs that make larger applications easier to distribute:
|
||||||
|
|
||||||
|
- **support for dependencies**: a library or application can declare a
|
||||||
|
list of other libraries it depends on which will be installed
|
||||||
|
automatically for you.
|
||||||
|
- **package registry**: setuptools registers your package with your
|
||||||
|
Python installation. This makes it possible to query information
|
||||||
|
provided by one package from another package. The best known feature of
|
||||||
|
this system is the entry point support which allows one package to
|
||||||
|
declare an "entry point" that another package can hook into to extend the
|
||||||
|
other package.
|
||||||
|
- **installation manager**: :command:`pip` can install other libraries for you.
|
||||||
|
|
||||||
|
If you have Python 2 (>=2.7.9) or Python 3 (>=3.4) installed from python.org,
|
||||||
|
you will already have pip and setuptools on your system. Otherwise, you
|
||||||
|
will need to install them yourself.
|
||||||
|
|
||||||
|
Flask itself, and all the libraries you can find on PyPI are distributed with
|
||||||
|
either setuptools or distutils.
|
||||||
|
|
||||||
|
In this case we assume your application is called
|
||||||
|
:file:`yourapplication.py` and you are not using a module, but a :ref:`package
|
||||||
|
<larger-applications>`. If you have not yet converted your application into
|
||||||
|
a package, head over to the :ref:`larger-applications` pattern to see
|
||||||
|
how this can be done.
|
||||||
|
|
||||||
|
A working deployment with setuptools is the first step into more complex
|
||||||
|
and more automated deployment scenarios. If you want to fully automate
|
||||||
|
the process, also read the :ref:`fabric-deployment` chapter.
|
||||||
|
|
||||||
|
Basic Setup Script
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Because you have Flask installed, you have setuptools available on your system.
|
||||||
|
Flask already depends upon setuptools.
|
||||||
|
|
||||||
|
Standard disclaimer applies: :ref:`you better use a virtualenv
|
||||||
|
<virtualenv>`.
|
||||||
|
|
||||||
|
Your setup code always goes into a file named :file:`setup.py` next to your
|
||||||
|
application. The name of the file is only convention, but because
|
||||||
|
everybody will look for a file with that name, you better not change it.
|
||||||
|
|
||||||
|
A basic :file:`setup.py` file for a Flask application looks like this::
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='Your Application',
|
||||||
|
version='1.0',
|
||||||
|
long_description=__doc__,
|
||||||
|
packages=['yourapplication'],
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
install_requires=['Flask']
|
||||||
|
)
|
||||||
|
|
||||||
|
Please keep in mind that you have to list subpackages explicitly. If you
|
||||||
|
want setuptools to lookup the packages for you automatically, you can use
|
||||||
|
the ``find_packages`` function::
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
...
|
||||||
|
packages=find_packages()
|
||||||
|
)
|
||||||
|
|
||||||
|
Most parameters to the ``setup`` function should be self explanatory,
|
||||||
|
``include_package_data`` and ``zip_safe`` might not be.
|
||||||
|
``include_package_data`` tells setuptools to look for a :file:`MANIFEST.in` file
|
||||||
|
and install all the entries that match as package data. We will use this
|
||||||
|
to distribute the static files and templates along with the Python module
|
||||||
|
(see :ref:`distributing-resources`). The ``zip_safe`` flag can be used to
|
||||||
|
force or prevent zip Archive creation. In general you probably don't want
|
||||||
|
your packages to be installed as zip files because some tools do not
|
||||||
|
support them and they make debugging a lot harder.
|
||||||
|
|
||||||
|
|
||||||
|
Tagging Builds
|
||||||
|
--------------
|
||||||
|
|
||||||
|
It is useful to distinguish between release and development builds. Add a
|
||||||
|
:file:`setup.cfg` file to configure these options.
|
||||||
|
|
||||||
|
[egg_info]
|
||||||
|
tag_build = .dev
|
||||||
|
tag_date = 1
|
||||||
|
|
||||||
|
[aliases]
|
||||||
|
release = egg_info -RDb ''
|
||||||
|
|
||||||
|
Running ``python setup.py sdist`` will create a development package
|
||||||
|
with ".dev" and the current date appended: ``flaskr-1.0.dev20160314.tar.gz``.
|
||||||
|
Running ``python setup.py release sdist`` will create a release package
|
||||||
|
with only the version: ``flaskr-1.0.tar.gz``.
|
||||||
|
|
||||||
|
|
||||||
|
.. _distributing-resources:
|
||||||
|
|
||||||
|
Distributing Resources
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
If you try to install the package you just created, you will notice that
|
||||||
|
folders like :file:`static` or :file:`templates` are not installed for you. The
|
||||||
|
reason for this is that setuptools does not know which files to add for
|
||||||
|
you. What you should do, is to create a :file:`MANIFEST.in` file next to your
|
||||||
|
:file:`setup.py` file. This file lists all the files that should be added to
|
||||||
|
your tarball::
|
||||||
|
|
||||||
|
recursive-include yourapplication/templates *
|
||||||
|
recursive-include yourapplication/static *
|
||||||
|
|
||||||
|
Don't forget that even if you enlist them in your :file:`MANIFEST.in` file, they
|
||||||
|
won't be installed for you unless you set the `include_package_data`
|
||||||
|
parameter of the ``setup`` function to ``True``!
|
||||||
|
|
||||||
|
|
||||||
|
Declaring Dependencies
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Dependencies are declared in the ``install_requires`` parameter as a list.
|
||||||
|
Each item in that list is the name of a package that should be pulled from
|
||||||
|
PyPI on installation. By default it will always use the most recent
|
||||||
|
version, but you can also provide minimum and maximum version
|
||||||
|
requirements. Here some examples::
|
||||||
|
|
||||||
|
install_requires=[
|
||||||
|
'Flask>=0.2',
|
||||||
|
'SQLAlchemy>=0.6',
|
||||||
|
'BrokenPackage>=0.7,<=1.0'
|
||||||
|
]
|
||||||
|
|
||||||
|
As mentioned earlier, dependencies are pulled from PyPI. What if you
|
||||||
|
want to depend on a package that cannot be found on PyPI and won't be
|
||||||
|
because it is an internal package you don't want to share with anyone?
|
||||||
|
Just do it as if there was a PyPI entry and provide a list of
|
||||||
|
alternative locations where setuptools should look for tarballs::
|
||||||
|
|
||||||
|
dependency_links=['http://example.com/yourfiles']
|
||||||
|
|
||||||
|
Make sure that page has a directory listing and the links on the page are
|
||||||
|
pointing to the actual tarballs with their correct filenames as this is
|
||||||
|
how setuptools will find the files. If you have an internal company
|
||||||
|
server that contains the packages, provide the URL to that server.
|
||||||
|
|
||||||
|
|
||||||
|
Installing / Developing
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
To install your application (ideally into a virtualenv) just run the
|
||||||
|
:file:`setup.py` script with the ``install`` parameter. It will install your
|
||||||
|
application into the virtualenv's site-packages folder and also download
|
||||||
|
and install all dependencies::
|
||||||
|
|
||||||
|
$ python setup.py install
|
||||||
|
|
||||||
|
If you are developing on the package and also want the requirements to be
|
||||||
|
installed, you can use the ``develop`` command instead::
|
||||||
|
|
||||||
|
$ python setup.py develop
|
||||||
|
|
||||||
|
This has the advantage of just installing a link to the site-packages
|
||||||
|
folder instead of copying the data over. You can then continue to work on
|
||||||
|
the code without having to run ``install`` again after each change.
|
||||||
|
|
||||||
|
|
||||||
|
.. _pip: https://pypi.org/project/pip/
|
||||||
|
.. _Setuptools: https://pythonhosted.org/setuptools
|
||||||
83
docs/patterns/errorpages.rst
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
.. _errorpages:
|
||||||
|
|
||||||
|
Custom Error Pages
|
||||||
|
==================
|
||||||
|
|
||||||
|
Flask comes with a handy :func:`~flask.abort` function that aborts a
|
||||||
|
request with an HTTP error code early. 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.
|
||||||
|
|
||||||
|
Common Error Codes
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The following error codes are some that are often displayed to the user,
|
||||||
|
even if the application behaves correctly:
|
||||||
|
|
||||||
|
*404 Not Found*
|
||||||
|
The good old "chap, you made a mistake typing that URL" message. So
|
||||||
|
common that even novices to the internet know that 404 means: damn,
|
||||||
|
the thing I was looking for is not there. It's a very good idea to
|
||||||
|
make sure there is actually something useful on a 404 page, at least a
|
||||||
|
link back to the index.
|
||||||
|
|
||||||
|
*403 Forbidden*
|
||||||
|
If you have some kind of access control on your website, you will have
|
||||||
|
to send a 403 code for disallowed resources. So make sure the user
|
||||||
|
is not lost when they try to access a forbidden resource.
|
||||||
|
|
||||||
|
*410 Gone*
|
||||||
|
Did you know that there the "404 Not Found" has a brother named "410
|
||||||
|
Gone"? Few people actually implement that, but the idea is that
|
||||||
|
resources that previously existed and got deleted answer with 410
|
||||||
|
instead of 404. If you are not deleting documents permanently from
|
||||||
|
the database but just mark them as deleted, do the user a favour and
|
||||||
|
use the 410 code instead and display a message that what they were
|
||||||
|
looking for was deleted for all eternity.
|
||||||
|
|
||||||
|
*500 Internal Server Error*
|
||||||
|
Usually happens on programming errors or if the server is overloaded.
|
||||||
|
A terribly good idea is to have a nice page there, because your
|
||||||
|
application *will* fail sooner or later (see also:
|
||||||
|
:ref:`application-errors`).
|
||||||
|
|
||||||
|
|
||||||
|
Error Handlers
|
||||||
|
--------------
|
||||||
|
|
||||||
|
An error handler is a function, just like a view function, but it is
|
||||||
|
called when an error happens and is passed that error. The error is most
|
||||||
|
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it
|
||||||
|
can be a different error: a handler for internal server errors will be
|
||||||
|
passed other exception instances as well if they are uncaught.
|
||||||
|
|
||||||
|
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
||||||
|
decorator and the error code of the exception. Keep in mind that Flask
|
||||||
|
will *not* set the error code for you, so make sure to also provide the
|
||||||
|
HTTP status code when returning a response.
|
||||||
|
|
||||||
|
Please note that if you add an error handler for "500 Internal Server
|
||||||
|
Error", Flask will not trigger it if it's running in Debug mode.
|
||||||
|
|
||||||
|
Here an example implementation for a "404 Page Not Found" exception::
|
||||||
|
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(e):
|
||||||
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
An example template might be this:
|
||||||
|
|
||||||
|
.. sourcecode:: 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 %}
|
||||||
|
|
||||||
186
docs/patterns/fabric.rst
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
.. _fabric-deployment:
|
||||||
|
|
||||||
|
Deploying with Fabric
|
||||||
|
=====================
|
||||||
|
|
||||||
|
`Fabric`_ is a tool for Python similar to Makefiles but with the ability
|
||||||
|
to execute commands on a remote server. In combination with a properly
|
||||||
|
set up Python package (:ref:`larger-applications`) and a good concept for
|
||||||
|
configurations (:ref:`config`) it is very easy to deploy Flask
|
||||||
|
applications to external servers.
|
||||||
|
|
||||||
|
Before we get started, here a quick checklist of things we have to ensure
|
||||||
|
upfront:
|
||||||
|
|
||||||
|
- Fabric 1.0 has to be installed locally. This tutorial assumes the
|
||||||
|
latest version of Fabric.
|
||||||
|
- The application already has to be a package and requires a working
|
||||||
|
:file:`setup.py` file (:ref:`distribute-deployment`).
|
||||||
|
- In the following example we are using `mod_wsgi` for the remote
|
||||||
|
servers. You can of course use your own favourite server there, but
|
||||||
|
for this example we chose Apache + `mod_wsgi` because it's very easy
|
||||||
|
to setup and has a simple way to reload applications without root
|
||||||
|
access.
|
||||||
|
|
||||||
|
Creating the first Fabfile
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
A fabfile is what controls what Fabric executes. It is named :file:`fabfile.py`
|
||||||
|
and executed by the `fab` command. All the functions defined in that file
|
||||||
|
will show up as `fab` subcommands. They are executed on one or more
|
||||||
|
hosts. These hosts can be defined either in the fabfile or on the command
|
||||||
|
line. In this case we will add them to the fabfile.
|
||||||
|
|
||||||
|
This is a basic first example that has the ability to upload the current
|
||||||
|
source code to the server and install it into a pre-existing
|
||||||
|
virtual environment::
|
||||||
|
|
||||||
|
from fabric.api import *
|
||||||
|
|
||||||
|
# the user to use for the remote commands
|
||||||
|
env.user = 'appuser'
|
||||||
|
# the servers where the commands are executed
|
||||||
|
env.hosts = ['server1.example.com', 'server2.example.com']
|
||||||
|
|
||||||
|
def pack():
|
||||||
|
# build the package
|
||||||
|
local('python setup.py sdist --formats=gztar', capture=False)
|
||||||
|
|
||||||
|
def deploy():
|
||||||
|
# figure out the package name and version
|
||||||
|
dist = local('python setup.py --fullname', capture=True).strip()
|
||||||
|
filename = '%s.tar.gz' % dist
|
||||||
|
|
||||||
|
# upload the package to the temporary folder on the server
|
||||||
|
put('dist/%s' % filename, '/tmp/%s' % filename)
|
||||||
|
|
||||||
|
# install the package in the application's virtualenv with pip
|
||||||
|
run('/var/www/yourapplication/env/bin/pip install /tmp/%s' % filename)
|
||||||
|
|
||||||
|
# remove the uploaded package
|
||||||
|
run('rm -r /tmp/%s' % filename)
|
||||||
|
|
||||||
|
# touch the .wsgi file to trigger a reload in mod_wsgi
|
||||||
|
run('touch /var/www/yourapplication.wsgi')
|
||||||
|
|
||||||
|
Running Fabfiles
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Now how do you execute that fabfile? You use the `fab` command. To
|
||||||
|
deploy the current version of the code on the remote server you would use
|
||||||
|
this command::
|
||||||
|
|
||||||
|
$ fab pack deploy
|
||||||
|
|
||||||
|
However this requires that our server already has the
|
||||||
|
:file:`/var/www/yourapplication` folder created and
|
||||||
|
:file:`/var/www/yourapplication/env` to be a virtual environment. Furthermore
|
||||||
|
are we not creating the configuration or ``.wsgi`` file on the server. So
|
||||||
|
how do we bootstrap a new server into our infrastructure?
|
||||||
|
|
||||||
|
This now depends on the number of servers we want to set up. If we just
|
||||||
|
have one application server (which the majority of applications will
|
||||||
|
have), creating a command in the fabfile for this is overkill. But
|
||||||
|
obviously you can do that. In that case you would probably call it
|
||||||
|
`setup` or `bootstrap` and then pass the servername explicitly on the
|
||||||
|
command line::
|
||||||
|
|
||||||
|
$ fab -H newserver.example.com bootstrap
|
||||||
|
|
||||||
|
To setup a new server you would roughly do these steps:
|
||||||
|
|
||||||
|
1. Create the directory structure in :file:`/var/www`::
|
||||||
|
|
||||||
|
$ mkdir /var/www/yourapplication
|
||||||
|
$ cd /var/www/yourapplication
|
||||||
|
$ virtualenv --distribute env
|
||||||
|
|
||||||
|
2. Upload a new :file:`application.wsgi` file to the server and the
|
||||||
|
configuration file for the application (eg: :file:`application.cfg`)
|
||||||
|
|
||||||
|
3. Create a new Apache config for ``yourapplication`` and activate it.
|
||||||
|
Make sure to activate watching for changes of the ``.wsgi`` file so
|
||||||
|
that we can automatically reload the application by touching it.
|
||||||
|
(See :ref:`mod_wsgi-deployment` for more information)
|
||||||
|
|
||||||
|
So now the question is, where do the :file:`application.wsgi` and
|
||||||
|
:file:`application.cfg` files come from?
|
||||||
|
|
||||||
|
The WSGI File
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The WSGI file has to import the application and also to set an environment
|
||||||
|
variable so that the application knows where to look for the config. This
|
||||||
|
is a short example that does exactly that::
|
||||||
|
|
||||||
|
import os
|
||||||
|
os.environ['YOURAPPLICATION_CONFIG'] = '/var/www/yourapplication/application.cfg'
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
The application itself then has to initialize itself like this to look for
|
||||||
|
the config at that environment variable::
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('yourapplication.default_config')
|
||||||
|
app.config.from_envvar('YOURAPPLICATION_CONFIG')
|
||||||
|
|
||||||
|
This approach is explained in detail in the :ref:`config` section of the
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
The Configuration File
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Now as mentioned above, the application will find the correct
|
||||||
|
configuration file by looking up the ``YOURAPPLICATION_CONFIG`` environment
|
||||||
|
variable. So we have to put the configuration in a place where the
|
||||||
|
application will able to find it. Configuration files have the unfriendly
|
||||||
|
quality of being different on all computers, so you do not version them
|
||||||
|
usually.
|
||||||
|
|
||||||
|
A popular approach is to store configuration files for different servers
|
||||||
|
in a separate version control repository and check them out on all
|
||||||
|
servers. Then symlink the file that is active for the server into the
|
||||||
|
location where it's expected (eg: :file:`/var/www/yourapplication`).
|
||||||
|
|
||||||
|
Either way, in our case here we only expect one or two servers and we can
|
||||||
|
upload them ahead of time by hand.
|
||||||
|
|
||||||
|
|
||||||
|
First Deployment
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Now we can do our first deployment. We have set up the servers so that
|
||||||
|
they have their virtual environments and activated apache configs. Now we
|
||||||
|
can pack up the application and deploy it::
|
||||||
|
|
||||||
|
$ fab pack deploy
|
||||||
|
|
||||||
|
Fabric will now connect to all servers and run the commands as written
|
||||||
|
down in the fabfile. First it will execute pack so that we have our
|
||||||
|
tarball ready and then it will execute deploy and upload the source code
|
||||||
|
to all servers and install it there. Thanks to the :file:`setup.py` file we
|
||||||
|
will automatically pull in the required libraries into our virtual
|
||||||
|
environment.
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
----------
|
||||||
|
|
||||||
|
From that point onwards there is so much that can be done to make
|
||||||
|
deployment actually fun:
|
||||||
|
|
||||||
|
- Create a `bootstrap` command that initializes new servers. It could
|
||||||
|
initialize a new virtual environment, setup apache appropriately etc.
|
||||||
|
- Put configuration files into a separate version control repository
|
||||||
|
and symlink the active configs into place.
|
||||||
|
- You could also put your application code into a repository and check
|
||||||
|
out the latest version on the server and then install. That way you
|
||||||
|
can also easily go back to older versions.
|
||||||
|
- hook in testing functionality so that you can deploy to an external
|
||||||
|
server and run the test suite.
|
||||||
|
|
||||||
|
Working with Fabric is fun and you will notice that it's quite magical to
|
||||||
|
type ``fab deploy`` and see your application being deployed automatically
|
||||||
|
to one or more remote servers.
|
||||||
|
|
||||||
|
|
||||||
|
.. _Fabric: http://www.fabfile.org/
|
||||||
|
|
@ -24,11 +24,8 @@ the root path of the domain you either need to configure the web server to
|
||||||
serve the icon at the root or if you can't do that you're out of luck. If
|
serve the icon at the root or if you can't do that you're out of luck. If
|
||||||
however your application is the root you can simply route a redirect::
|
however your application is the root you can simply route a redirect::
|
||||||
|
|
||||||
app.add_url_rule(
|
app.add_url_rule('/favicon.ico',
|
||||||
"/favicon.ico",
|
redirect_to=url_for('static', filename='favicon.ico'))
|
||||||
endpoint="favicon",
|
|
||||||
redirect_to=url_for("static", filename="favicon.ico"),
|
|
||||||
)
|
|
||||||
|
|
||||||
If you want to save the extra redirect request you can also write a view
|
If you want to save the extra redirect request you can also write a view
|
||||||
using :func:`~flask.send_from_directory`::
|
using :func:`~flask.send_from_directory`::
|
||||||
|
|
@ -52,5 +49,5 @@ web server's documentation.
|
||||||
See also
|
See also
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* The `Favicon <https://en.wikipedia.org/wiki/Favicon>`_ article on
|
* The `Favicon <http://en.wikipedia.org/wiki/Favicon>`_ article on
|
||||||
Wikipedia
|
Wikipedia
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _uploading-files:
|
||||||
|
|
||||||
Uploading Files
|
Uploading Files
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|
@ -19,11 +21,11 @@ specific upload folder and displays a file to the user. Let's look at the
|
||||||
bootstrapping code for our application::
|
bootstrapping code for our application::
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from flask import Flask, flash, request, redirect, url_for
|
from flask import Flask, request, redirect, url_for
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||||
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
|
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||||
|
|
@ -37,7 +39,7 @@ 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
|
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
|
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
|
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
|
:ref:`xss`). Also make sure to disallow ``.php`` files if the server
|
||||||
executes them, but who has PHP installed on their server, right? :)
|
executes them, but who has PHP installed on their server, right? :)
|
||||||
|
|
||||||
Next the functions that check if an extension is valid and that uploads
|
Next the functions that check if an extension is valid and that uploads
|
||||||
|
|
@ -55,22 +57,23 @@ the file and redirects the user to the URL for the uploaded file::
|
||||||
flash('No file part')
|
flash('No file part')
|
||||||
return redirect(request.url)
|
return redirect(request.url)
|
||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
# If the user does not select a file, the browser submits an
|
# if user does not select file, browser also
|
||||||
# empty file without a filename.
|
# submit a empty part without filename
|
||||||
if file.filename == '':
|
if file.filename == '':
|
||||||
flash('No selected file')
|
flash('No selected file')
|
||||||
return redirect(request.url)
|
return redirect(request.url)
|
||||||
if file and allowed_file(file.filename):
|
if file and allowed_file(file.filename):
|
||||||
filename = secure_filename(file.filename)
|
filename = secure_filename(file.filename)
|
||||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
||||||
return redirect(url_for('download_file', name=filename))
|
return redirect(url_for('uploaded_file',
|
||||||
|
filename=filename))
|
||||||
return '''
|
return '''
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<title>Upload new File</title>
|
<title>Upload new File</title>
|
||||||
<h1>Upload new File</h1>
|
<h1>Upload new File</h1>
|
||||||
<form method=post enctype=multipart/form-data>
|
<form method=post enctype=multipart/form-data>
|
||||||
<input type=file name=file>
|
<p><input type=file name=file>
|
||||||
<input type=submit value=Upload>
|
<input type=submit value=Upload>
|
||||||
</form>
|
</form>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
@ -101,28 +104,31 @@ before storing it directly on the filesystem.
|
||||||
>>> secure_filename('../../../../home/username/.bashrc')
|
>>> secure_filename('../../../../home/username/.bashrc')
|
||||||
'home_username_.bashrc'
|
'home_username_.bashrc'
|
||||||
|
|
||||||
We want to be able to serve the uploaded files so they can be downloaded
|
Now one last thing is missing: the serving of the uploaded files. In the
|
||||||
by users. We'll define a ``download_file`` view to serve files in the
|
:func:`upload_file()` we redirect the user to
|
||||||
upload folder by name. ``url_for("download_file", name=name)`` generates
|
``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``.
|
||||||
download URLs.
|
So we write the :func:`uploaded_file` function to return the file of that name. As
|
||||||
|
of Flask 0.5 we can use a function that does that for us::
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from flask import send_from_directory
|
from flask import send_from_directory
|
||||||
|
|
||||||
@app.route('/uploads/<name>')
|
@app.route('/uploads/<filename>')
|
||||||
def download_file(name):
|
def uploaded_file(filename):
|
||||||
return send_from_directory(app.config["UPLOAD_FOLDER"], name)
|
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
||||||
|
filename)
|
||||||
|
|
||||||
If you're using middleware or the HTTP server to serve files, you can
|
Alternatively you can register `uploaded_file` as `build_only` rule and
|
||||||
register the ``download_file`` endpoint as ``build_only`` so ``url_for``
|
use the :class:`~werkzeug.wsgi.SharedDataMiddleware`. This also works with
|
||||||
will work without a view function.
|
older versions of Flask::
|
||||||
|
|
||||||
.. code-block:: python
|
from werkzeug import SharedDataMiddleware
|
||||||
|
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
|
||||||
|
build_only=True)
|
||||||
|
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
|
||||||
|
'/uploads': app.config['UPLOAD_FOLDER']
|
||||||
|
})
|
||||||
|
|
||||||
app.add_url_rule(
|
If you now run the application everything should work as expected.
|
||||||
"/uploads/<name>", endpoint="download_file", build_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
Improving Uploads
|
Improving Uploads
|
||||||
|
|
@ -131,28 +137,22 @@ Improving Uploads
|
||||||
.. versionadded:: 0.6
|
.. versionadded:: 0.6
|
||||||
|
|
||||||
So how exactly does Flask handle uploads? Well it will store them in the
|
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
|
webserver's memory if the files are reasonable small otherwise in a
|
||||||
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
||||||
do you specify the maximum file size after which an upload is aborted? By
|
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
|
default Flask will happily accept file uploads to an unlimited amount of
|
||||||
memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH``
|
memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH``
|
||||||
config key::
|
config key::
|
||||||
|
|
||||||
from flask import Flask, Request
|
from flask import Flask, Request
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000
|
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
||||||
|
|
||||||
The code above will limit the maximum allowed payload to 16 megabytes.
|
The code above will limited the maximum allowed payload to 16 megabytes.
|
||||||
If a larger file is transmitted, Flask will raise a
|
If a larger file is transmitted, Flask will raise an
|
||||||
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception.
|
: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
|
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
|
as well by subclassing the request object. For more information on that
|
||||||
consult the Werkzeug documentation on file handling.
|
consult the Werkzeug documentation on file handling.
|
||||||
|
|
@ -163,9 +163,10 @@ Upload Progress Bars
|
||||||
|
|
||||||
A while ago many developers had the idea to read the incoming file in
|
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
|
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
|
poll the progress with JavaScript from the client. Long story short: the
|
||||||
server every 5 seconds how much it has transmitted, but this is
|
client asks the server every 5 seconds how much it has transmitted
|
||||||
something it should already know.
|
already. Do you realize the irony? The client is asking for something it
|
||||||
|
should already know.
|
||||||
|
|
||||||
An Easier Solution
|
An Easier Solution
|
||||||
------------------
|
------------------
|
||||||
|
|
@ -175,8 +176,9 @@ are JavaScript libraries like jQuery_ that have form plugins to ease the
|
||||||
construction of progress bar.
|
construction of progress bar.
|
||||||
|
|
||||||
Because the common pattern for file uploads exists almost unchanged in all
|
Because the common pattern for file uploads exists almost unchanged in all
|
||||||
applications dealing with uploads, there are also some Flask extensions that
|
applications dealing with uploads, there is also a Flask extension called
|
||||||
implement a full fledged upload mechanism that allows controlling which
|
`Flask-Uploads`_ that implements a full fledged upload mechanism with white and
|
||||||
file extensions are allowed to be uploaded.
|
blacklisting of extensions and more.
|
||||||
|
|
||||||
.. _jQuery: https://jquery.com/
|
.. _jQuery: https://jquery.com/
|
||||||
|
.. _Flask-Uploads: http://pythonhosted.org/Flask-Uploads/
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _message-flashing-pattern:
|
||||||
|
|
||||||
Message Flashing
|
Message Flashing
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
@ -20,7 +22,7 @@ So here is a full example::
|
||||||
request, url_for
|
request, url_for
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
|
app.secret_key = 'some_secret'
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -101,7 +103,7 @@ error messages could be displayed with a red background.
|
||||||
To flash a message with a different category, just use the second argument
|
To flash a message with a different category, just use the second argument
|
||||||
to the :func:`~flask.flash` function::
|
to the :func:`~flask.flash` function::
|
||||||
|
|
||||||
flash('Invalid password provided', 'error')
|
flash(u'Invalid password provided', 'error')
|
||||||
|
|
||||||
Inside the template you then have to tell the
|
Inside the template you then have to tell the
|
||||||
:func:`~flask.get_flashed_messages` function to also return the
|
:func:`~flask.get_flashed_messages` function to also return the
|
||||||
|
|
|
||||||